これは以前に投稿された質問の続きです:
サイコロの側面を模倣するために、1から6などの特定の範囲内から乱数を生成できるようにしたいと考えています。
これを行うにはどうすればよいですか?
これは以前に投稿された質問の続きです:
サイコロの側面を模倣するために、1から6などの特定の範囲内から乱数を生成できるようにしたいと考えています。
これを行うにはどうすればよいですか?
回答:
これまでのすべての答えは数学的に間違っています。返す間隔rand() % N
の範囲の長さを2の累乗で除算し[0, N)
ない限り、範囲内の数値N
がrand()
返されることはありません。さらに、の係数rand()
が独立しているかどうかはわかり0, 1, 2, ...
ません。均一であるが非常にランダムではない可能性があります。妥当であると思われる唯一の仮定はrand()
、ポアソン分布を出力することです。同じサイズの2つの重複しない部分区間は、等しく可能性が高く、独立しています。値の有限セットの場合、これは均一な分布を意味し、の値rand()
が適切に分散されることも保証します。
つまり、範囲を変更する唯一の正しい方法は、範囲rand()
をボックスに分割することです。たとえば、のRAND_MAX == 11
範囲が1..6
必要な場合は、{0,1}
1 {2,3}
から2に割り当てる必要があります。これらはばらばらで、同じサイズの間隔であるため、均一かつ独立して分散されます。
浮動小数点除算を使用するという提案は数学的にもっともらしいですが、原則として丸めの問題に悩まされています。おそらくdouble
、それを機能させるのに十分な精度があります。おそらくない。私にはわかりませんし、理解する必要もありません。いずれにせよ、答えはシステムに依存します。
正しい方法は、整数演算を使用することです。つまり、次のようなものが必要です。
#include <stdlib.h> // For random(), RAND_MAX
// Assumes 0 <= max <= RAND_MAX
// Returns in the closed interval [0, max]
long random_at_most(long max) {
unsigned long
// max <= RAND_MAX < ULONG_MAX, so this is okay.
num_bins = (unsigned long) max + 1,
num_rand = (unsigned long) RAND_MAX + 1,
bin_size = num_rand / num_bins,
defect = num_rand % num_bins;
long x;
do {
x = random();
}
// This is carefully written not to overflow
while (num_rand - defect <= (unsigned long)x);
// Truncated division is intentional
return x/bin_size;
}
ループは、完全に均一な分布を得るために必要です。たとえば、0から2までの乱数が与えられ、0から1までの乱数のみが必要な場合は、2が得られなくなるまでプルし続けます。これが等しい確率で0または1を与えることを確認するのは難しくありません。この方法は、コードが異なっていても、nosが回答で示したリンクにも記載されています。(のmanページに記載されているように)より良いディストリビューションを持っているrandom()
のでrand()
、私は使用していますrand()
。
デフォルトの範囲外のランダムな値を取得したい場合は、注意が必要です[0, RAND_MAX]
。おそらく、最も好都合には、関数を定義することであるrandom_extended()
引っ張るn
ビット(使用random_at_most()
中)と戻り[0, 2**n)
、その後、適用random_at_most()
とrandom_extended()
の代わりにrandom()
(と2**n - 1
の代わりにRAND_MAX
)未満のランダムな値を引っ張って2**n
このようなを保持することができ、数値型であると仮定すると、価値。最後に、もちろん、負の値を含むを[min, max]
使用して値を取得できmin + random_at_most(max - min)
ます。
max - min > RAND_MAX
に機能しないことです。これは、前述の問題よりも深刻です(たとえば、VC ++にはRAND_MAX
32767しかない)。
do {} while()
。
@Ryan Reichの回答に続き、私は私のクリーンアップしたバージョンを提供すると思いました。最初の境界チェックは2番目の境界チェックを前提として不要であり、再帰的ではなく反復的にしました。これは、範囲[MIN、MAX]の値を返すmax >= min
と1+max-min < RAND_MAX
。
unsigned int rand_interval(unsigned int min, unsigned int max)
{
int r;
const unsigned int range = 1 + max - min;
const unsigned int buckets = RAND_MAX / range;
const unsigned int limit = buckets * range;
/* Create equal size buckets all in a row, then fire randomly towards
* the buckets until you land in one of them. All buckets are equally
* likely. If you land off the end of the line of buckets, try again. */
do
{
r = rand();
} while (r >= limit);
return min + (r / buckets);
}
limit
、int(およびオプションでbucket
)を作成することで簡単に解決できます。編集:提案を送信して編集しました。RAND_MAX / range
INT_MAX
buckets * range
RAND_MAX
範囲の最大値と最小値がわかっていて、その範囲内に含まれる数値を生成する場合の数式は次のとおりです。
r = (rand() % (max + 1 - min)) + min
int
オーバーフローする可能性がありmax+1-min
ます。
unsigned int
randr(unsigned int min, unsigned int max)
{
double scaled = (double)rand()/RAND_MAX;
return (max - min +1)*scaled + min;
}
その他のオプションについては、こちらをご覧ください。
(((max-min+1)*rand())/RAND_MAX)+min
おそらくまったく同じ分布を得ることができます(RAND_MAXがintに比べて十分小さいため、オーバーフローしないと仮定します)。
max + 1
場合rand() == RAND_MAX
、またはrand()
非常に近い場合にが返される可能性がRAND_MAX
あり、浮動小数点エラーが最終結果を超えmax + 1
ます。安全のために、結果を返す前に結果が範囲内であることを確認する必要があります。
RAND_MAX + 1.0
ます。max + 1
ただし、これが戻りを防ぐのに十分かどうかはまだわかりません。特に、+ min
最後max + 1
にrand()の大きな値が生成される可能性のあるラウンドが含まれています。このアプローチを完全に破棄し、整数演算を使用する方が安全です。
RAND_MAX
するRAND_MAX+1.0
ようにに置き換えられた場合、+ min
整数演算を使用して行われる限り、これは安全であると私は信じています return (unsigned int)((max - min + 1) * scaled) + min
。(非自明な)理由は、IEEE 754算術演算を想定し、ラウンド半に-でも、(そしてまた、そのことでmax - min + 1
、二重のように正確に表現され、それは典型的なマシン上で真のでしょう)、それは常に本当x * scaled < x
のために任意の正のdouble x
と任意のdouble scaled
を満たす0.0 <= scaled && scaled < 1.0
。
randr(0, UINT_MAX)
:常に0を生成
あなたはただやってみませんか?
srand(time(NULL));
int r = ( rand() % 6 ) + 1;
%
モジュラス演算子です。基本的には、6で割って余りを0〜5で返します。
rand()
ジェネレーターの状態の下位ビットが含まれる場所で使用されているlibcを見せてください(LCGを使用している場合)。私はこれまでこれを見たことはありません。それらのすべて(はい、RAND_MAXが32767であるMSVCを含む)は下位ビットを削除します。係数を使用することは、他の理由で推奨されません。つまり、係数が小さいほど、分布が歪むためです。
バイアスの問題は理解しているが、拒否ベースのメソッドの予測できない実行時間に耐えられない人のために、このシリーズは、[0, n-1]
間隔の中で次第にバイアスの少ないランダムな整数を生成します。
r = n / 2;
r = (rand() * n + r) / (RAND_MAX + 1);
r = (rand() * n + r) / (RAND_MAX + 1);
r = (rand() * n + r) / (RAND_MAX + 1);
...
これは、高精度の固定小数点乱数i * log_2(RAND_MAX + 1)
ビット(ここi
で、は反復数)を合成し、による長い乗算を実行することによって行われn
ます。
と比較してビット数が十分に多いn
場合、バイアスは非常に小さくなります。
かどうかは関係ありませんRAND_MAX + 1
未満であるn
(のように、この質問)、またはそれは2の累乗でない場合は、しかし場合は注意がオーバーフロー、整数避けるようにしなければならないRAND_MAX * n
大きさです。
RAND_MAX
であることが多いINT_MAX
ため、RAND_MAX + 1
-> UB(INT_MINのように)
RAND_MAX * n
が大きい場合は整数オーバーフローを回避するように注意する必要がある」という意味です。要件に適したタイプを使用するように調整する必要があります。
RAND_MAX
はINT_MAX
「はい」であることが多いですが、16ビットシステムのみです。合理的に近代的なアーキテクチャはINT_MAX
2 ^ 32/2およびRAND_MAX
2 ^ 16/2に配置されます。これは誤った仮定ですか?
int
コンパイラ、私が見つかりました。RAND_MAX == 32767
1にし、RAND_MAX == 2147483647
他の上。私の全体的な経験(数十年)はそれ RAND_MAX == INT_MAX
よりも頻繁です。そのため、合理的に現代的な32ビットアーキテクチャにRAND_MAX
at があることには同意できません2^16 / 2
。C仕様ではが許可さ32767 <= RAND_MAX <= INT_MAX
れているので、傾向ではなくとにかくそれをコーディングします。
モジュロバイアス(他の回答で推奨)を回避するために、常に以下を使用できます。
arc4random_uniform(MAX-MIN)+MIN
「MAX」は上限、「MIN」は下限です。たとえば、10〜20の数値の場合:
arc4random_uniform(20-10)+10
arc4random_uniform(10)+10
シンプルなソリューションで、「rand()%N」を使用するよりも優れています。
#include <bsd/stdlib.h>
最初にする必要があることに注意する価値があります。また、MinGWまたはCygWinなしでWindowsでこれを取得する方法はありますか?
以下は、Ryan Reichのソリューションよりも少し単純なアルゴリズムです。
/// Begin and end are *inclusive*; => [begin, end]
uint32_t getRandInterval(uint32_t begin, uint32_t end) {
uint32_t range = (end - begin) + 1;
uint32_t limit = ((uint64_t)RAND_MAX + 1) - (((uint64_t)RAND_MAX + 1) % range);
/* Imagine range-sized buckets all in a row, then fire randomly towards
* the buckets until you land in one of them. All buckets are equally
* likely. If you land off the end of the line of buckets, try again. */
uint32_t randVal = rand();
while (randVal >= limit) randVal = rand();
/// Return the position you hit in the bucket + begin as random number
return (randVal % range) + begin;
}
Example (RAND_MAX := 16, begin := 2, end := 7)
=> range := 6 (1 + end - begin)
=> limit := 12 (RAND_MAX + 1) - ((RAND_MAX + 1) % range)
The limit is always a multiple of the range,
so we can split it into range-sized buckets:
Possible-rand-output: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Buckets: [0, 1, 2, 3, 4, 5][0, 1, 2, 3, 4, 5][X, X, X, X, X]
Buckets + begin: [2, 3, 4, 5, 6, 7][2, 3, 4, 5, 6, 7][X, X, X, X, X]
1st call to rand() => 13
→ 13 is not in the bucket-range anymore (>= limit), while-condition is true
→ retry...
2nd call to rand() => 7
→ 7 is in the bucket-range (< limit), while-condition is false
→ Get the corresponding bucket-value 1 (randVal % range) and add begin
=> 3
RAND_MAX + 1
簡単にオーバーフローできint
ます。その場合、(RAND_MAX + 1) % range
疑わしい結果が生成されます。考えてみましょう(RAND_MAX + (uint32_t)1)
ライアンは正しいですが、解はランダム性の原因について知られていることに基づいてはるかに単純になる可能性があります。問題を再度述べるには:
[0, MAX)
を均一な分布で出力します。[rmin, rmax]
で一様に分布するランダムな整数を生成すること0 <= rmin < rmax < MAX
です。ビン(または「ボックス」)の数は、元の番号の範囲、より著しく小さい場合私の経験では、と元のソース暗号的に強いです-すべてがrigamaroleを通過する必要がない、とシンプルなモジュロ除算だろう(output = rnd.next() % (rmax+1)
ifのようにrmin == 0
)十分であり、速度を失うことなく、「十分」に均一に分布する乱数を生成します。重要な要素はランダム性のソースです(つまり、子供たち、これを自宅で試してはいけませんrand()
)。
これが実際にどのように機能するかの例/証明です。1から22までの乱数を生成したかったのですが、(Intel RDRANDに基づいて)ランダムなバイトを生成する暗号的に強力なソースがあります。結果は次のとおりです。
Rnd distribution test (22 boxes, numbers of entries in each box): 1: 409443 4.55% 2: 408736 4.54% 3: 408557 4.54% 4: 409125 4.55% 5: 408812 4.54% 6: 409418 4.55% 7: 408365 4.54% 8: 407992 4.53% 9: 409262 4.55% 10: 408112 4.53% 11: 409995 4.56% 12: 409810 4.55% 13: 409638 4.55% 14: 408905 4.54% 15: 408484 4.54% 16: 408211 4.54% 17: 409773 4.55% 18: 409597 4.55% 19: 409727 4.55% 20: 409062 4.55% 21: 409634 4.55% 22: 409342 4.55% total: 100.00%
これは、私が必要とする限り、ユニフォームに近いです(公正なサイコロの投げ、http://users.telenet.be/d.rijmenants/en/kl-7sim.htmなどの第二次世界大戦の暗号マシン用の暗号的に強力なコードブックの生成など) )。出力には、かなりの偏りはありません。
暗号学的に強力な(真の)乱数ジェネレーターのソースは次のとおりです: インテルデジタル乱数ジェネレーター と64ビット(符号なし)乱数を生成するサンプルコード。
int rdrand64_step(unsigned long long int *therand)
{
unsigned long long int foo;
int cf_error_status;
asm("rdrand %%rax; \
mov $1,%%edx; \
cmovae %%rax,%%rdx; \
mov %%edx,%1; \
mov %%rax, %0;":"=r"(foo),"=r"(cf_error_status)::"%rax","%rdx");
*therand = foo;
return cf_error_status;
}
Mac OS Xでclang-6.0.1(ストレート)を使用してコンパイルし、gcc-4.8.3で "-Wa、q"フラグを使用してコンパイルしました(GASはこれらの新しい命令をサポートしていないため)。
gcc randu.c -o randu -Wa,q
(Ubuntuの16上のGCC 5.3.1)またはclang randu.c -o randu
(クラン3.8.0)動作しますが、使用して実行時にコアをダンプしますIllegal instruction (core dumped)
。何か案は?
前に述べたように、モジュロは分布を歪めるので十分ではありません。ビットをマスクして、分布が歪んでいないことを確認するためにそれらを使用する私のコードを示します。
static uint32_t randomInRange(uint32_t a,uint32_t b) {
uint32_t v;
uint32_t range;
uint32_t upper;
uint32_t lower;
uint32_t mask;
if(a == b) {
return a;
}
if(a > b) {
upper = a;
lower = b;
} else {
upper = b;
lower = a;
}
range = upper - lower;
mask = 0;
//XXX calculate range with log and mask? nah, too lazy :).
while(1) {
if(mask >= range) {
break;
}
mask = (mask << 1) | 1;
}
while(1) {
v = rand() & mask;
if(v <= range) {
return lower + v;
}
}
}
次の簡単なコードで、分布を確認できます。
int main() {
unsigned long long int i;
unsigned int n = 10;
unsigned int numbers[n];
for (i = 0; i < n; i++) {
numbers[i] = 0;
}
for (i = 0 ; i < 10000000 ; i++){
uint32_t rand = random_in_range(0,n - 1);
if(rand >= n){
printf("bug: rand out of range %u\n",(unsigned int)rand);
return 1;
}
numbers[rand] += 1;
}
for(i = 0; i < n; i++) {
printf("%u: %u\n",i,numbers[i]);
}
}
v = rand(); if (v > RAND_MAX - (RAND_MAX % range) -> reject and try again; else return v % range;
モジュロはマスキングよりもはるかに遅い操作であることを理解していますが、それでもテストする必要があると思います。
rand()
int
範囲内のを返します[0..RAND_MAX]
。その範囲は簡単にその範囲の一部になり、範囲内の値を生成することはuint32_t
ありrandomInRange(0, ,b)
ません(INT_MAX...b]
。