私はこの質問が頻繁に尋ねられるのを見ましたが、それに対する真の具体的な答えを見たことはありません。そこでrand()
、C ++のように乱数ジェネレータを使用するときに「モジュロバイアス」が正確に存在する理由を人々が理解できるように役立つものをここに投稿します。
私はこの質問が頻繁に尋ねられるのを見ましたが、それに対する真の具体的な答えを見たことはありません。そこでrand()
、C ++のように乱数ジェネレータを使用するときに「モジュロバイアス」が正確に存在する理由を人々が理解できるように役立つものをここに投稿します。
回答:
で定義されている定数であるrand()
0からRAND_MAX
までの自然数を選択する疑似乱数ジェネレーターもありcstdlib
ます(の一般的な概要については、この記事を参照してくださいrand()
)。
次に、たとえば0と2の間の乱数を生成したい場合はどうなりますか?説明のために、RAND_MAX
10としましょう。私はを呼び出して、0と2の間の乱数を生成することにしrand()%3
ます。ただし、rand()%3
0と2の間の数を等しい確率で生成しません!
がrand()
0、3、6、または9を返す場合 rand()%3 == 0
。したがって、P(0)= 4/11
がrand()
1、4、7、または10を返す場合 rand()%3 == 1
。したがって、P(1)= 4/11
がrand()
2、5、または8を返す場合 rand()%3 == 2
。したがって、P(2)= 3/11
これは、等しい確率で0と2の間の数を生成しません。もちろん、範囲が小さい場合、これは最大の問題ではない可能性がありますが、範囲が大きい場合、分布が歪む可能性があり、小さい数に偏ります。
では、rand()%n
等確率で0からn-1までの範囲の数値を返すのはいつでしょうか。ときRAND_MAX%n == n - 1
。この場合、以前の仮定rand()
は0からRAND_MAX
等しい確率で数値を返すため、nのモジュロクラスも均等に分散されます。
では、この問題をどのように解決すればよいでしょうか?おおまかな方法は、希望する範囲の数値が得られるまで乱数を生成し続けることです。
int x;
do {
x = rand();
} while (x >= n);
それは低い値のために非効率的だn
あなたが唯一持っているので、n/RAND_MAX
あなたの範囲の値を得ることのチャンスを、あなたが実行する必要がありますので、RAND_MAX/n
への呼び出しをrand()
平均で。
より効率的な式のアプローチはn
、のようにRAND_MAX - RAND_MAX % n
で割り切れる長さのいくつかの大きな範囲を取り、範囲内にあるものを取得するまで乱数を生成し続け、次に係数を取得することです。
int x;
do {
x = rand();
} while (x >= (RAND_MAX - RAND_MAX % n));
x %= n;
の値が小さい場合n
、これはめったにへの複数の呼び出しを必要としませんrand()
。
引用された作品と参考文献:
ランダムを選択し続けることは、バイアスを取り除くための良い方法です。
更新
で割り切れる範囲のxを検索すると、コードを高速にすることができn
ます。
// Assumptions
// rand() in [0, RAND_MAX]
// n in (0, RAND_MAX]
int x;
// Keep searching for an x in a range divisible by n
do {
x = rand();
} while (x >= RAND_MAX - (RAND_MAX % n))
x %= n;
上記のループは非常に高速でなければなりません。たとえば、平均で1回の繰り返しです。
rand()
返すことができる値の数がの倍数でない場合、n
何をしても、それらの値の一部を破棄しない限り、必然的に「モジュロバイアス」が発生します。user1413793はそれをうまく説明しています(ただし、その回答で提案されているソリューションは本当に不幸です)。
RAND_MAX+1 - (RAND_MAX+1) % n
正しく機能しますがRAND_MAX+1 - ((RAND_MAX+1) % n)
、明確にするために記述する必要があります。
RAND_MAX == INT_MAX
(ほとんどのシステムでそうであるように)動作しません。上記の@ user1413793への2番目のコメントを参照してください。
@ user1413793は問題について正しいです。1つのポイントを除いて、それについてさらに説明するつもりはありません。そうです。の小さな値n
と大きな値のRAND_MAX
場合、モジュロバイアスは非常に小さくなります。ただし、バイアスを誘発するパターンを使用するということは、乱数を計算するたびにバイアスを考慮し、ケースごとに異なるパターンを選択する必要があることを意味します。そして、あなたが間違った選択をした場合、それがもたらすバグは微妙であり、ユニットテストすることはほとんど不可能です。適切なツール(などarc4random_uniform
)のみを使用する場合と比較すると、これは追加の作業であり、作業量が少なくなるわけではありません。より多くの作業を行い、より悪い解決策を得ることはひどいエンジニアリングです。特に、ほとんどのプラットフォームで毎回正しく行うことが簡単な場合はなおさらです。
残念ながら、ソリューションの実装はすべて正しくないか、本来よりも効率が低くなっています。(各ソリューションには問題を説明するさまざまなコメントがありますが、それらを解決するために修正されたソリューションはありません。)これはカジュアルな回答を求める人を混乱させる可能性があるため、ここでは既知の良好な実装を提供します。
繰り返しになりますが、最善のソリューションはarc4random_uniform
、それを提供するプラットフォーム、またはプラットフォーム(Random.nextInt
Java など)に類似した範囲ソリューションで使用することです。それはあなたにコードコストなしで正しいことをします。ほとんどの場合、これは正しい呼び出しです。
がない場合はarc4random_uniform
、オープンソースの機能を使用して、より広範囲のRNG上でどのように実装されているかを正確に確認できます(ar4random
この場合、同様のアプローチが他のRNG上でも機能する可能性があります)。
ここでOpenBSDの実装は:
/*
* Calculate a uniformly distributed random number less than upper_bound
* avoiding "modulo bias".
*
* Uniformity is achieved by generating new random numbers until the one
* returned is outside the range [0, 2**32 % upper_bound). This
* guarantees the selected random number will be inside
* [2**32 % upper_bound, 2**32) which maps back to [0, upper_bound)
* after reduction modulo upper_bound.
*/
u_int32_t
arc4random_uniform(u_int32_t upper_bound)
{
u_int32_t r, min;
if (upper_bound < 2)
return 0;
/* 2**32 % x == (2**32 - x) % x */
min = -upper_bound % upper_bound;
/*
* This could theoretically loop forever but each retry has
* p > 0.5 (worst case, usually far better) of selecting a
* number inside the range we need, so it should rarely need
* to re-roll.
*/
for (;;) {
r = arc4random();
if (r >= min)
break;
}
return r % upper_bound;
}
同様のことを実装する必要がある人のために、このコードに関する最新のコミットコメントに注目する価値があります。
arc4random_uniform()を
2**32 % upper_bound
として計算するように変更し-upper_bound % upper_bound
ます。コードを簡略化し、ILP32アーキテクチャとLP64アーキテクチャの両方で同じにします。また、64ビット剰余の代わりに32ビット剰余を使用することにより、LP64アーキテクチャではわずかに高速化します。Jorden Verwerがtech @ ok deraadtで指摘しました。DJMやオットーからの異議なし
Java実装も簡単に見つけることができます(前のリンクを参照)。
public int nextInt(int n) {
if (n <= 0)
throw new IllegalArgumentException("n must be positive");
if ((n & -n) == n) // i.e., n is a power of 2
return (int)((n * (long)next(31)) >> 31);
int bits, val;
do {
bits = next(31);
val = bits % n;
} while (bits - val + (n-1) < 0);
return val;
}
arcfour_random()
その実装で実際に実際のRC4アルゴリズムを実際に使用する場合、出力には間違いなくバイアスがあることに注意してください。うまくいけば、ライブラリの作成者が、同じインターフェイスの背後でより優れたCSPRNGを使用するように切り替えたことを確認します。BSDの1つが実際にChaCha20アルゴリズムを使用して実装していることを思い出しますarcfour_random()
。より多くのセキュリティや、ビデオポーカーなどの他の重要なアプリケーションのために、それは無用RC4出力バイアス上:blog.cryptographyengineering.com/2013/03/...
/dev/random
ますが、過去に一部のプラットフォームでRC4も使用しました(LinuxはカウンターモードでSHA-1を使用します)。残念ながら、検索で見つかったmanページは、RC4が提供するさまざまなプラットフォームでまだ使用されていることを示していますarc4random
(実際のコードは異なる場合があります)。
-upper_bound % upper_bound == 0
か?
-upper_bound % upper_bound
はint
、32ビットより広い場合、実際には0になります。(それはのBSD-ismであると(u_int32_t)-upper_bound % upper_bound)
想定しu_int32_t
ていますuint32_t
)。
モジュロバイアスは、モジュロ演算を使用して出力セットを入力セットのサブセットに削減する際の固有のバイアスです。一般に、出力セットのサイズが入力セットのサイズの約数ではないときにモジュロ演算を使用する場合のように、入力セットと出力セットの間のマッピングが均等に分散されない場合は常にバイアスが存在します。
このバイアスは、数値をビットの文字列(0と1)として表す計算では特に避けがたいものです。真にランダムなランダム性のソースを見つけることも非常に困難ですが、この議論の範囲を超えています。この回答の残りの部分では、真にランダムなビットの無制限のソースが存在すると仮定します。
これらのランダムビットを使用して、サイコロ(0〜5)のシミュレーションを検討してみましょう。6つの可能性があるので、6を表すのに十分なビット、つまり3ビットが必要です。残念ながら、3つのランダムなビットは8つの可能な結果をもたらします:
000 = 0, 001 = 1, 010 = 2, 011 = 3
100 = 4, 101 = 5, 110 = 6, 111 = 7
私たちは、しかし、このプレゼント、値のモジュロ6を取ることによって、正確に6に結果セットのサイズを小さくすることができモジュロバイアスの問題を:110
0が得られ、かつ111
1を生み出す。このダイがロードされています。
理論上、ランダムなビットに頼るのではなく、小さな軍隊を雇って1日中サイコロを振ってデータベースに結果を記録し、各結果を1回だけ使用することができます。これは、それが思うほど実用的であり、とにかく真にランダムな結果をもたらすことはおそらくありません(しゃれた意図)。
モジュラスを使用する代わりに、素朴だが数学的に正しい解決策は、生成された結果を破棄110
して111
、単純に3つの新しいビットで再試行することです。残念ながら、これは、再ロールのそれぞれを含めて、再ロールが必要になる可能性が各ロールに25%あることを意味します。これは、最も些細な使用を除いて、すべて非現実的です。
より多くのビットを使用します。3ビットの代わりに4を使用します。これにより、16の結果が得られます。もちろん、結果が5より大きいときはいつでも再ロールすると事態が悪化し(10/16 = 62.5%)、それだけでは効果がありません。
2 * 6 = 12 <16であることに注意してください。したがって、12未満の任意の結果を安全に取得し、その結果を均等に分配するためにそのモジュロ6を減らすことができます。他の4つの結果は破棄する必要があり、前のアプローチと同様に再ロールします。
最初はいいように聞こえますが、数学をチェックしましょう:
4 discarded results / 16 possibilities = 25%
この場合、1ビット追加してもまったく効果がありませんでした。
その結果は残念ですが、5ビットでもう一度試してみましょう。
32 % 6 = 2 discarded results; and
2 discarded results / 32 possibilities = 6.25%
明確な改善ですが、多くの実際的なケースでは十分ではありません。朗報ですが、ビットを追加しても、破棄して再ロールする必要が生じる可能性は高くなりません。これはサイコロだけでなく、すべての場合に当てはまります。
ただし、示されているように、1ビットを追加しても何も変更されない場合があります。実際、ロールを6ビットに増やしても、確率は6.25%のままです。
これは2つの追加の質問を頼みます:
ありがたいことに、最初の質問への答えはイエスです。6の問題は、2 ^ x mod 6が2と4の間で反転し、偶然にも互いに2の倍数であるため、偶数x> 1の場合
[2^x mod 6] / 2^x == [2^(x+1) mod 6] / 2^(x+1)
したがって、6は規則ではなく例外です。同じ方法で連続する2の累乗を生成するより大きい係数を見つけることは可能ですが、最終的にこれはラップアラウンドする必要があり、破棄の確率は減少します。
さらなる証拠を提供することなく、一般に必要なビット数を2倍にすると、廃棄の可能性は小さくなり、通常は重要ではなくなります。
OpenSSLのlibcrypoを使用してランダムなバイトを提供するプログラムの例を次に示します。コンパイルするときは、-lcrypto
ほとんどの人が利用できるはずのライブラリにリンクしてください。
#include <iostream>
#include <assert.h>
#include <limits>
#include <openssl/rand.h>
volatile uint32_t dummy;
uint64_t discardCount;
uint32_t uniformRandomUint32(uint32_t upperBound)
{
assert(RAND_status() == 1);
uint64_t discard = (std::numeric_limits<uint64_t>::max() - upperBound) % upperBound;
uint64_t randomPool = RAND_bytes((uint8_t*)(&randomPool), sizeof(randomPool));
while(randomPool > (std::numeric_limits<uint64_t>::max() - discard)) {
RAND_bytes((uint8_t*)(&randomPool), sizeof(randomPool));
++discardCount;
}
return randomPool % upperBound;
}
int main() {
discardCount = 0;
const uint32_t MODULUS = (1ul << 31)-1;
const uint32_t ROLLS = 10000000;
for(uint32_t i = 0; i < ROLLS; ++i) {
dummy = uniformRandomUint32(MODULUS);
}
std::cout << "Discard count = " << discardCount << std::endl;
}
ほとんどの条件下で実際に発生する再ロールの数を確認するために、MODULUS
とROLLS
値をいじることをお勧めします。懐疑的な人は、計算された値をファイルに保存して、分布が正常に見えることを確認することもできます。
randomPool = RAND_bytes(...)
ラインは常にになりますrandomPool == 1
アサーションによるもの。これにより、常に破棄と再ロールが行われます。別の行で宣言したいと思います。その結果、これにより、RNGは1
反復ごとに戻ります。
モジュロの使用には、通常2つの不満があります。
1つはすべてのジェネレーターに有効です。限界の場合に見やすくなります。ジェネレーターのRAND_MAXが2(C標準に準拠していない)であり、値として0または1のみが必要な場合、moduloを使用すると、ジェネレーターが0と2を生成するときの2倍の頻度で0が生成されます1を生成します(ジェネレータが1を生成する場合)。これは、値をドロップしないとすぐに当てはまることに注意してください。ジェネレーターの値から必要な値へのマッピングに関係なく、一方は他方の2倍の頻度で発生します。
ある種のジェネレーターは、少なくとも一部のパラメーターについて、他よりも重要でないビットのランダム性が低くなっていますが、残念なことに、これらのパラメーターには他の興味深い特性があります(RAND_MAXを2のべき乗よりも小さくできるなど)。この問題はよく知られており、長い間ライブラリの実装ではおそらく問題を回避できます(たとえば、C標準のサンプルrand()実装はこの種類のジェネレータを使用しますが、下位16ビットを削除します)。それとあなたは運が悪いかもしれません
のようなものを使用して
int alea(int n){
assert (0 < n && n <= RAND_MAX);
int partSize =
n == RAND_MAX ? 1 : 1 + (RAND_MAX-n)/(n+1);
int maxUsefull = partSize * n + (partSize-1);
int draw;
do {
draw = rand();
} while (draw > maxUsefull);
return draw/partSize;
}
0とnの間の乱数を生成すると、両方の問題が回避されます(RAND_MAX == INT_MAXでオーバーフローが回避されます)
ところで、C ++ 11は、還元とrand()以外のジェネレーターに標準的な方法を導入しました。
マークのソリューション(承認されたソリューション)はほぼ完璧です。
int x; do { x = rand(); } while (x >= (RAND_MAX - RAND_MAX % n)); x %= n;
作成25 3月.162016-03-25 23:16
マークアメリー39k21170211
ただし、RAND_MAX
(RM
)がN
(Where N
=有効な有効な結果の数)の倍数より1少ない任意のシナリオで、1つの有効な結果のセットを破棄するという警告があります。
つまり、「破棄された値の数」(D
)がに等しい場合N
、それらは実際には有効なセットです(V)
無効なセット(I
)ではありません)。
これを引き起こすのは、ある時点でマークがとの違いを見失っN
てRand_Max
しまうことです。
N
有効な応答の数が含まれているため、有効なメンバーが正の整数のみで構成されるセットです。(例:セットN
= {1, 2, 3, ... n }
)
Rand_max
ただし、これは(目的のために定義された)任意の数の非負の整数を含むセットです。
最も一般的な形式でRand Max
は、ここで定義されているのはすべての有効な結果のセットであり、理論的には負の数や非数値を含めることができます。
したがってRand_Max
、「可能な応答」のセットとしてより適切に定義されます。
ただしN
、有効な応答のセット内の値のカウントに対して動作するため、特定のケースで定義されている場合でも、Rand_Max
含まれている合計数より1少ない値になります。
マークのソリューションを使用すると、次の場合に値が破棄されます:X => RM-RM%N
EG:
Ran Max Value (RM) = 255
Valid Outcome (N) = 4
When X => 252, Discarded values for X are: 252, 253, 254, 255
So, if Random Value Selected (X) = {252, 253, 254, 255}
Number of discarded Values (I) = RM % N + 1 == N
IE:
I = RM % N + 1
I = 255 % 4 + 1
I = 3 + 1
I = 4
X => ( RM - RM % N )
255 => (255 - 255 % 4)
255 => (255 - 3)
255 => (252)
Discard Returns $True
上記の例でわかるように、Xの値(初期関数から取得した乱数)が252、253、254、または255の場合、これらの4つの値が有効な戻り値のセットを構成していても、それを破棄します。
IE:破棄された値のカウント(I)= N(有効な結果の数)の場合、戻り値の有効なセットは元の関数によって破棄されます。
値NとRMの差をDとして説明すると、次のようになります。
D = (RM - N)
次に、Dの値が小さくなると、この方法に起因する不要な再ロールのパーセンテージは、各自然乗算で増加します。(RAND_MAXが素数と等しくない場合、これは有効な問題です)
例えば:
RM=255 , N=2 Then: D = 253, Lost percentage = 0.78125%
RM=255 , N=4 Then: D = 251, Lost percentage = 1.5625%
RM=255 , N=8 Then: D = 247, Lost percentage = 3.125%
RM=255 , N=16 Then: D = 239, Lost percentage = 6.25%
RM=255 , N=32 Then: D = 223, Lost percentage = 12.5%
RM=255 , N=64 Then: D = 191, Lost percentage = 25%
RM=255 , N= 128 Then D = 127, Lost percentage = 50%
必要な再ロールのパーセンテージはNがRMに近づくほど増加するため、コードを実行しているシステムの制約と検索される値に応じて、これは多くの異なる値で有効な問題になる可能性があります。
これを無効にするために、簡単な修正を加えることができます。
int x;
do {
x = rand();
} while (x > (RAND_MAX - ( ( ( RAND_MAX % n ) + 1 ) % n) );
x %= n;
これは、モジュラスを使用して最大値を定義することの追加の特殊性を説明する、より一般的なバージョンの式を提供します。
Nの乗法であるRAND_MAXに小さい値を使用する例。
マークオリジナルバージョン:
RAND_MAX = 3, n = 2, Values in RAND_MAX = 0,1,2,3, Valid Sets = 0,1 and 2,3.
When X >= (RAND_MAX - ( RAND_MAX % n ) )
When X >= 2 the value will be discarded, even though the set is valid.
一般化バージョン1:
RAND_MAX = 3, n = 2, Values in RAND_MAX = 0,1,2,3, Valid Sets = 0,1 and 2,3.
When X > (RAND_MAX - ( ( RAND_MAX % n ) + 1 ) % n )
When X > 3 the value would be discarded, but this is not a vlue in the set RAND_MAX so there will be no discard.
さらに、NがRAND_MAXの値の数である必要がある場合。この場合、RAND_MAX = INT_MAXでない限り、N = RAND_MAX +1を設定できます。
ループに関しては、N = 1を使用するだけで、Xの任意の値が受け入れられ、最終的な乗数としてIFステートメントが挿入されます。しかし、おそらく、n = 1で関数が呼び出されたときに1を返す正当な理由があるコードがあるかもしれません...
したがって、n = RAND_MAX + 1にしたい場合は、通常はDiv 0エラーを提供する0を使用する方がよい場合があります。
一般化バージョン2:
int x;
if n != 0 {
do {
x = rand();
} while (x > (RAND_MAX - ( ( ( RAND_MAX % n ) + 1 ) % n) );
x %= n;
} else {
x = rand();
}
これらのソリューションはどちらも、RM + 1がnの積である場合に発生する、不必要に破棄された有効な結果で問題を解決します。
2番目のバージョンは、nをRAND_MAXに含まれる可能な値の合計セットと等しくする必要がある場合のエッジケースシナリオもカバーしています。
両方の変更されたアプローチは同じであり、有効な乱数を提供し、破棄される値を最小限に抑える必要性に対するより一般的な解決策を可能にします。
繰り返します:
マークの例を拡張する基本的な一般的なソリューション:
// Assumes:
// RAND_MAX is a globally defined constant, returned from the environment.
// int n; // User input, or externally defined, number of valid choices.
int x;
do {
x = rand();
} while (x > (RAND_MAX - ( ( ( RAND_MAX % n ) + 1 ) % n) ) );
x %= n;
RAND_MAX + 1 = nの1つの追加シナリオを許可する拡張一般ソリューション:
// Assumes:
// RAND_MAX is a globally defined constant, returned from the environment.
// int n; // User input, or externally defined, number of valid choices.
int x;
if n != 0 {
do {
x = rand();
} while (x > (RAND_MAX - ( ( ( RAND_MAX % n ) + 1 ) % n) ) );
x %= n;
} else {
x = rand();
}
一部の言語(特にインタプリタ言語)では、while条件の外で比較操作の計算を行うと、何度も再試行が必要な場合でも1回の計算なので、結果が速くなる場合があります。YMMV!
// Assumes:
// RAND_MAX is a globally defined constant, returned from the environment.
// int n; // User input, or externally defined, number of valid choices.
int x; // Resulting random number
int y; // One-time calculation of the compare value for x
if n != 0 {
y = RAND_MAX - ( ( ( RAND_MAX % n ) + 1 ) % n)
do {
x = rand();
} while (x > y);
x %= n;
} else {
x = rand();
}
RAND_MAX%n = n - 1
RAND_MAX
値3
は偏りがあることをこれらの計算から、理にかなっている(実際にはそれよりもはるかに高くなるはずですが、バイアスは依然として存在することになります)。
1 % 2 = 1
2 % 2 = 0
3 % 2 = 1
random_between(1, 3) % 2 = more likely a 1
この場合、との% 2
間の乱数が必要な場合は、を実行しないでください。あなたは間の乱数を得ることができますし、実行しているため、この場合には、しかし:の倍数です。0
1
0
2
% 3
RAND_MAX
3
別の方法
そこはるかに簡単ですが、他の回答に追加するには、ここの間の乱数を得るために私の解決策である0
とn - 1
、そのn
偏りなく、さまざまな可能性。
>= n
、再起動します(モジュロなし)。本当にランダムなデータを取得するのは簡単ではないので、なぜ必要以上のビットを使用するのか。
以下は、Smalltalkの例で、疑似乱数ジェネレータからのビットのキャッシュを使用しています。私はセキュリティの専門家ではないので、自己責任で使用してください。
next: n
| bitSize r from to |
n < 0 ifTrue: [^0 - (self next: 0 - n)].
n = 0 ifTrue: [^nil].
n = 1 ifTrue: [^0].
cache isNil ifTrue: [cache := OrderedCollection new].
cache size < (self randmax highBit) ifTrue: [
Security.DSSRandom default next asByteArray do: [ :byte |
(1 to: 8) do: [ :i | cache add: (byte bitAt: i)]
]
].
r := 0.
bitSize := n highBit.
to := cache size.
from := to - bitSize + 1.
(from to: to) do: [ :i |
r := r bitAt: i - from + 1 put: (cache at: i)
].
cache removeFrom: from to: to.
r >= n ifTrue: [^self next: n].
^r
受け入れられた答えは示し、「モジュロバイアス」の低い値にそのルーツを持っていますRAND_MAX
。彼は非常に小さい値RAND_MAX
(10)を使用して、RAND_MAXが10の場合、%を使用して0と2の間の数値を生成しようとすると、次の結果が得られることを示しています。
rand() % 3 // if RAND_MAX were only 10, gives
output of rand() | rand()%3
0 | 0
1 | 1
2 | 2
3 | 0
4 | 1
5 | 2
6 | 0
7 | 1
8 | 2
9 | 0
したがって、0の4つの出力(4/10の確率)と、1と2の3つの出力(それぞれ3/10の確率)しかありません。
そのため、偏っています。数値が小さいほど、出てくる可能性が高くなります。
しかし、それRAND_MAX
はが小さい場合にのみ明らかに表示されます。より具体的には、モッドする数がと比較して大きい場合RAND_MAX
。
ループよりもはるかに優れたソリューション(めちゃくちゃ非効率的であり、推奨もされません)は、出力範囲がはるかに大きいPRNGを使用することです。メルセンヌツイスターアルゴリズムは4,294,967,295の最大出力を持っています。そのようなものがやったようMersenneTwister::genrand_int32() % 10
すべての意図や目的のために、均等に配分され、モジュロバイアス効果は、すべてが、消えてしまいます。
MT::genrand_int32()%2
0(50 + 2.3e-8)%の時間と1(50-2.3e-8)%の時間を選択します。カジノのRGN(おそらくより広い範囲のRGNを使用する可能性があります)を構築しているのでない限り、どのユーザーも2.3e-8%の時間に余分に気付くことはありません。あなたはここで問題を起こすには小さすぎる数について話している。
RAND_MAX
値を使用すると、モジュロバイアスは減少しますが、除去されません。ルーピングします。
RAND_MAX
が修正する数値よりも十分に大きい場合、乱数を再生成する必要がある回数は非常に少なく、効率には影響しません。受け入れられた回答で提案されたn
ものだけでなく、の最大の倍数に対してテストしている限り、ループを続けると言いn
ます。
私は、フォンノイマンのバイアスなしコインフリップ法のコードを書いたところです。これにより、乱数生成プロセスのバイアスが理論的に排除されます。詳細については、(http://en.wikipedia.org/wiki/Fair_coin)を参照してください。
int unbiased_random_bit() {
int x1, x2, prev;
prev = 2;
x1 = rand() % 2;
x2 = rand() % 2;
for (;; x1 = rand() % 2, x2 = rand() % 2)
{
if (x1 ^ x2) // 01 -> 1, or 10 -> 0.
{
return x2;
}
else if (x1 & x2)
{
if (!prev) // 0011
return 1;
else
prev = 1; // 1111 -> continue, bias unresolved
}
else
{
if (prev == 1)// 1100
return 0;
else // 0000 -> continue, bias unresolved
prev = 0;
}
}
}
rand() % 100
100回呼び出す。B)すべての結果が異なる場合は、最初の結果を取ります。C)それ以外の場合、GOTO A.これは機能しますが、予測される反復回数は約10 ^ 42なので、非常に辛抱する必要があります。そして不滅。
else if(prev==2) prev= x1; else { if(prev!=x1) return prev; prev=2;}
RAND_MAX%n == n - 1
_ _です(RAND_MAX + 1) % n == 0
。コードを読むとき% something == 0
、他の計算方法よりも「均等に割り切れる」と理解する傾向があります。 もちろん、C ++ stdlibがRAND_MAX
と同じ値の場合INT_MAX
、(RAND_MAX + 1)
きっと動作しません。したがって、マークの計算は依然として最も安全な実装です。