このランダムな値に50/50ではなく25/75の分布がある理由


139

編集:基本的に私が書こうとしているのはの1ビットのハッシュですdouble

私はマッピングするdoubletrueまたはfalse50/50チャンスと。そのために、いくつかの乱数を選択するコードを書きました(例として、規則性のあるデータでこれを使用し、それでも50/50の結果を取得したい)、最後のビットをチェックし、y1であるかn、それが0。

ただし、このコードでは常に25%yと75%になりnます。なぜ50/50ではないのですか?そして、なぜこのように奇妙だが単純な(1/3)分布なのか?

public class DoubleToBoolean {
    @Test
    public void test() {

        int y = 0;
        int n = 0;
        Random r = new Random();
        for (int i = 0; i < 1000000; i++) {
            double randomValue = r.nextDouble();
            long lastBit = Double.doubleToLongBits(randomValue) & 1;
            if (lastBit == 1) {
                y++;
            } else {
                n++;
            }
        }
        System.out.println(y + " " + n);
    }
}

出力例:

250167 749833

43
答えが「LCGは低ビットのエントロピーが低い」というよりは、浮動小数点変量のランダム生成について魅力的なものであることを本当に望んでいます。
Sneftel 2014

4
私は非常に好奇心旺盛ですが、「doubleの1ビットハッシュ」の目的は何ですか 私はそのような要件の正当な適用を真剣に考えることはできません。
corsiKa 2014

3
@corsiKaジオメトリの計算では、2つの可能な答えから選択しようとするケースが2つあることが多く(たとえば、ポイントがラインの左側または右側にある場合など)、場合によっては、3番目の縮退したケース(ポイントが行の右側にあります)が、使用可能な回答は2つしかないため、その場合は使用可能な回答の1つを疑似ランダムに選択する必要があります。私が考えることができる最良の方法は、与えられたdouble値の1つの1ビットハッシュを取ることです(これらはジオメトリ計算なので、あちこちにdoubleがあることを思い出してください)。
gvlasov 14

2
@corsiKa(コメントが長すぎるため、2つに分割されます)のような単純なものから始めるdoubleValue % 1 > 0.5こともできますが、場合によっては目に見える規則性をもたらす可能性があるため、粒度が粗すぎます(すべての値が長さ1の範囲内にあります)。それが粗すぎる場合は、おそらく次のように範囲を狭めてみるべきでしょうdoubleValue % 1e-10 > 0.5e-10か?はい、そうです。そして、最後のビットだけをaのハッシュとしてとるdoubleことは、このアプローチを最後までたどると、可能な限り最小のモジュロで行われることになります。
gvlasov 14

1
@kmoteを使用すると、バイアスが最も低い最下位ビットが残り、他のビットはそれを補正しません。実際、まったく同じ理由で、ビットはゼロに向かってバイアスされます(ただし、そうではありません)。したがって、分布は約50、12.5、25、12.5になります。(lastbit & 3) == 0しかし、奇妙なことに、うまくいきます。
ハロルド2014

回答:


165

nextDoubleは次のように機能するため:(source

public double nextDouble()
{
    return (((long) next(26) << 27) + next(27)) / (double) (1L << 53);
}

next(x)xランダムなビットを作ります。

なぜこれが問題なのでしょうか?最初の部分(除算前)によって生成された数値の約半分は未満で1L << 52あり、したがって、それらの有効桁は埋めることができる53ビットを完全には埋めません。つまり、有効桁の最下位ビットは常にゼロです。


これは注目を集めているためdouble、Java(および他の多くの言語)のa が実際にどのように見えるか、およびこの質問でそれがなぜ重要であったかについての追加の説明があります。

基本的には、double次のようになります:(source

ダブルレイアウト

この写真では見えない非常に重要な詳細は数字が「正規化」されることで1ように1が省略されていること、(それがそうであるように指数を選択することによって)1と53ビットの小数を開始します。そのため、画像では小数部(仮数)に52ビットが表示されていますが、実際には53ビットです。

正規化はnextDouble、53番目のビットのコードが設定されている場合、そのビットは暗黙の先行1であり、それがなくなることを意味し、他の52ビットは結果のの仮数に文字通りコピーされdoubleます。ただし、そのビットが設定されていない場合、残りのビットは設定されるまで左にシフトする必要があります。

平均して、生成された数値の半分は、仮数がまったく左にシフトされなかった場合(および約半分は最下位ビットが0である場合)になり、残りの半分は少なくとも1だけシフトされます(または完全に完全にゼロ)その最下位ビットは常に0です。

1:常にそうとは限らない。明らかに1がゼロではないため、実行できない。これらの数値は非正規数または非正規数と呼ばれる。wikipedia:denormal numberを参照。


16
やったー!まさに私が望んでいたこと。
Sneftel 2014

3
@Mattおそらく速度の最適化です。代替案は、幾何分布のある指数を生成し、次に仮数を個別に生成することです。
Sneftel 2014

7
@マット:「最高」を定義します。random.nextDouble()通常は、それが意図されている「最善の」方法ですが、ほとんどの人はランダムなdoubleから1ビットのハッシュを生成しようとはしていません。均一な分布、暗号解読への抵抗、または何を探していますか?
StriplingWarrior 2014

1
この答えは、OPが乱数に2 ^ 53を掛けて、結果の整数が奇数であるかどうかをチェックした場合、50/50の分布があったことを示唆しています。
rici 2014

4
@ The111は、ここnextはを返す必要があると言っいるintので、とにかく最大32ビットしか持てません
Harold

48

ドキュメントから:

nextDoubleメソッドは、次のようにクラスRandomによって実装されます。

public double nextDouble() {
  return (((long)next(26) << 27) + next(27))
      / (double)(1L << 53);
}

しかし、それはまた次のようにも述べています(私の強調):

[Javaの初期バージョンでは、結果は次のように誤って計算されていました。

 return (((long)next(27) << 27) + next(27))
     / (double)(1L << 54);

これは同等ではないかもしれませんが、浮動小数点数の丸めにバイアスがあるため、実際には大きな不均一性が生じました。仮数の下位ビットが0になる可能性は3倍でしたそれよりも1になります!この不均一性は実際にはそれほど問題ではないかもしれませんが、完璧を目指して努力します。]

このメモは少なくともJava 5以降に存在します(Java <= 1.4のドキュメントはログインウォールの背後にあり、チェックが面倒です)。これは興味深い問題です。Java8でも問題が明らかに残っているためです。「修正済み」バージョンはテストされていないのではないでしょうか。


4
奇妙な。私はただのJava 8でこれを再現
aioobe

1
バイアスがまだ新しい方法にも適用されると私が主張しただけなので、今それは興味深いです。私が間違っている?
ハロルド2014

3
@harold:いいえ、私はあなたが正しいと思います。このバイアスを修正しようとした人は誰でも間違いを犯したかもしれません。
トーマス

6
@harold Javaの人にメールを送信する時間。
ダニエル

8
「おそらく、修正されたバージョンはテストされなかったのでしょうか?」実際、これを再読すると、ドキュメントは別の問題に関するものだったと思います。丸めについて言及していることに注意してください。これは、「可能性が3倍」が問題であると直接考えていなかったことを示唆していますが、値が丸められたときに分布が不均一になることを示唆しています。私の回答では、リストする値は均一に分布していますが、IEEE形式で表される下位ビットは均一ではないことに注意してください。彼らが修正した問題は、ロービットの均一性ではなく、全体的な均一性に関係していると思います。
ajb

33

浮動小数点数がどのように表現されるかを考えると、この結果は私を驚かせません。4ビットの精度しかない非常に短い浮動小数点型があるとしましょう。一様に分布する0と1の間の乱数を生成する場合、16の可能な値があります。

0.0000
0.0001
0.0010
0.0011
0.0100
...
0.1110
0.1111

それがマシンでどのように見えるかであれば、下位ビットをテストして50/50分布を得ることができます。ただし、IEEE浮動小数点数は仮数の2の累乗として表されます。フロートの1つのフィールドは、2の累乗です(固定オフセットを加えたもの)。「仮数」部分が常に1.0以上2.0未満の数値になるように、2の累乗が選択されます。つまり、実際には、以外の数値0.0000は次のように表されます。

0.0001 = 2^(-4) x 1.000
0.0010 = 2^(-3) x 1.000
0.0011 = 2^(-3) x 1.100
0.0100 = 2^(-2) x 1.000
... 
0.0111 = 2^(-2) x 1.110
0.1000 = 2^(-1) x 1.000
0.1001 = 2^(-1) x 1.001
...
0.1110 = 2^(-1) x 1.110
0.1111 = 2^(-1) x 1.111

12進小数点の前は暗黙の値です。32ビットと64ビットの浮動小数点の場合、これを保持するために実際に割り当てられるビットはありません1。)

しかし、上記を見ると理由がわかるはずです。表現をビットに変換して下位ビットを見ると、75%の時間はゼロになります。これは0.1000、可能性のある値の半分である0.5(バイナリ)未満のすべての値が原因であり、仮数部がシフトオーバーされているため、下位ビットに0が表示されます。仮数が52ビット(暗黙の1を含まない)を持っている場合、状況は基本的に同じdoubleです。

(実際には、@ sneftelがコメントで提案したように、次のコードを生成することで、分布に16を超える可能な値を含めることができます。

0.0001000 with probability 1/128
0.0001001 with probability 1/128
...
0.0001111 with probability 1/128
0.001000  with probability 1/64
0.001001  with probability 1/64
...
0.01111   with probability 1/32 
0.1000    with probability 1/16
0.1001    with probability 1/16
...
0.1110    with probability 1/16
0.1111    with probability 1/16

しかし、それがほとんどのプログラマーが期待する種類のディストリビューションであるかどうかはわからないので、おそらく価値はありません。さらに、ランダムな浮動小数点値がよくあるように、整数を生成するために値が使用される場合、それはあなたに多くの利益をもたらしません。)


5
浮動小数点を使用してランダムなビット/バイト/何かを取得すると、とにかく身震いします。でも、0とnの間のランダムな分布のために、我々はより良い代替手段(arc4random_uniformを見て) ...ランダム*よりのN
mirabilos
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.