編集:つまり、基本的に私が書き込もうとしているのは、double
の1ビットのハッシュです。
double
をtrue
またはfalse
に50/50のチャンスでマップしたい。そのために、いくつかの乱数を選択するコードを作成しました(例として、規則性のあるデータでこれを使用しても、50/50の結果が得られます)、最後のビットをチェックし、1の場合はy
をインクリメントし、0の場合はn
をインクリメントします。
ただし、このコードは常に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
NextDoubleは次のように機能するため:( source )
_public double nextDouble()
{
return (((long) next(26) << 27) + next(27)) / (double) (1L << 53);
}
_
next(x)
は、x
ランダムビットを作成します。
なぜこれが重要なのでしょうか? (除算前の)最初の部分で生成された数値の約半分は_1L << 52
_より小さいため、その有効桁は埋めることができる53ビットを完全には埋めません。つまり、有効桁の最下位ビットはそれらの場合は常にゼロです。
注目を集めているため、Java(および他の多くの言語))のdouble
が実際にどのように見えるのか、この質問でなぜそれが重要なのかについての追加説明があります。 。
基本的に、double
は次のようになります:( source )
この図では見えない非常に重要な詳細は、数値が「正規化」されていることです1 53ビットの小数部が1で始まるように(そのように指数を選択することにより)、その1は省略されます。そのため、図には小数部(有効数字)に対して52ビットが表示されますが、実際には53ビットが含まれています。
正規化とは、nextDouble
のコードで53番目のビットが設定されている場合、そのビットが暗黙の先行1であり、それが消え、残りの52ビットが結果のdouble
。ただし、そのビットが設定されていない場合、残りのビットは設定されるまで左にシフトする必要があります。
平均して、生成された数の半分は、仮数がnotまったく左にシフトされた場合に該当します(そして約半数は最下位として0を持っています)ビット)、および他の半分は少なくとも1だけシフトされる(または完全にゼロになる)ため、最下位ビットは常に0です。
1:常にではありませんが、明らかに最高1を持たないゼロではできません。これらの数値は非正規数または非正規数と呼ばれます。 wikipedia:denormal number を参照してください。
docs から:
メソッド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);
これは同等のように思えるかもしれませんが、実際には浮動小数点数の丸めの偏りのために大きな不均一性を導入しました:仮数の次数ビットは1であるよりも0になります!この不均一性は実際にはおそらく重要ではありませんが、完璧を目指して努力しています。]
このメモは、少なくともJava 5以降に存在します(Java <= 1.4のドキュメントはログインウォールの背後にあるため、チェックするのが面倒です)。なぜなら、問題は明らかにJava 8.でも存在するからです。
浮動小数点数がどのように表現されるかを考えると、この結果は私を驚かせません。精度が4ビットしかない非常に短い浮動小数点型があるとします。 0から1の間の乱数を均一に生成する場合、16の可能な値があります。
0.0000
0.0001
0.0010
0.0011
0.0100
...
0.1110
0.1111
それがマシンでどのように見えるかであれば、下位ビットをテストして50/50分布を取得できます。ただし、IEEE floatは仮数の2のべき乗として表されます。フロートの1つのフィールドは2の累乗(および固定オフセット)です。 2のべき乗は、「仮数」部分が常に1.0以上2.0以下の数になるように選択されます。つまり、実際には、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
(2進小数点の前の1
は暗黙の値です。32ビットおよび64ビットの浮動小数点の場合、この1
を保持するために実際にビットは割り当てられません。)
しかし、上記を見ると、表現をビットに変換して低ビットを見ると、75%の時間がゼロになる理由がわかるはずです。これは、0.5(バイナリ0.1000
)未満のすべての値が原因です。これは、可能な値の半分であり、仮数がシフトされて、下位ビットに0が表示されます。 double
がそうであるように、仮数に52ビット(暗黙の1を含まない)があるとき、状況は本質的に同じです。
(実際、@ sneftelがコメントで示唆したように、couldを生成することで、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
しかし、ほとんどのプログラマーが期待するようなディストリビューションであるかどうかはわかりません。そのため、おそらく価値がないでしょう。さらに、値が整数を生成するために使用される場合、ランダムな浮動小数点値がしばしば使用されるので、あまり得られません。)