数学指向の Web サイトの記事でランダム ビットを効率的に使用する方法について読んだことを思い出しますが、Google で適切なキーワードを見つけて見つけることができなくなったようで、ブラウザの履歴にもありません。
domainStart
問われていた問題の要点は、領域 [ , )内の乱数のシーケンスを取得し、乱数シーケンスdomainEnd
のビットを効率的に使用して範囲 [ rangeStart
, rangeEnd
) に一様に射影することでした。ドメインと範囲はどちらも整数です (より正確には、long
Z ではなく s)。これを行うアルゴリズムは何ですか?
実装に関しては、次のシグネチャを持つ関数があります。
long doRead(InputStream in, long rangeStart, long rangeEnd);
in
私が使用する必要がある CSPRNG (SecureRandom を介して調整されたハードウェア RNG によって供給される) に基づいています。戻り値は と の間rangeStart
でなければなりませんがrangeEnd
、これを明らかに実装するのは無駄です:
long doRead(InputStream in, long rangeStart, long rangeEnd) {
long retVal = 0;
long range = rangeEnd - rangeStart;
// Fill until we get to range
for (int i = 0; (1 << (8 * i)) < range; i++) {
int in = 0;
do {
in = in.read();
// but be sure we don't exceed range
} while(retVal + (in << (8 * i)) >= range);
retVal += in << (8 * i);
}
return retVal + rangeStart;
}
これは事実上 と同じ考えだと思い Henry は、このコードが 0 と 257 に対して偏っていることを指摘しています。Banthar は例でそれを示します。(rand() * (max - min)) + min
ますが、私たちを押しのけるビットを破棄しているだけですmax
。結果を誤って低い値にバイアスするモジュロ演算子を使用するのではなく、それらのビットを破棄して再試行します。CSPRNG をヒットすると再シードがトリガーされる可能性があるため (InputStream をブロックする可能性があります)、ランダムなビットを無駄にすることは避けたいと思います。
最初の編集: Henry は、総和が中心極限定理を呼び出すことを思い出させてくれました。その問題を回避するために、上記のコードを修正しました。
2 番目の編集: Mechanical snail は、Random.nextInt() のソースを確認することを提案しました。しばらく読んだ後、この問題は基数変換の問題に似ていることに気付きました。以下の回答を参照してください。