为什么这个随机值的分布是25/75,而不是50/50?


139

编辑:所以基本上我想写的是1位哈希double

我想将映射doubletruefalse有50/50的机会。为此,我编写了选择一些随机数的代码(仅作为示例,我想在具有规则性的数据上使用它并仍然获得50/50的结果),检查它们的最后一位,并递增y其是否为1,或者n是否为1。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
我很好奇,“ 1位哈希加倍”的目的是什么?我真的想不出有任何此类要求的合法应用。
corsiKa 2014年

3
@corsiKa在几何计算中,我们通常希望从两种可能的答案中选择两种情况(例如,指向该行的左侧还是右侧?),有时会引入第三种退化的情况(点为),但是您只有两个可用的答案,因此在这种情况下,您必须伪随机地选择一个可用的答案。我能想到的最好的方法是对给定的double值之一进行1位哈希处理(请记住,这些是几何计算,因此到处都是double值)。
gvlasov 2014年

2
@corsiKa(由于太长而将注释分为两部分),我们可以从简单的东西开始doubleValue % 1 > 0.5,但由于它在某些情况下会引入可见的规律性(所有值都在长度1的范围内),因此可能太粗糙了。如果那太粗糙了,那么我们应该尝试较小的范围doubleValue % 1e-10 > 0.5e-10吗?嗯,是。double当您采用这种方法直到最后,以最小的模数时,会发生最后的事情作为a的散列的情况。
gvlasov 2014年

1
@kmote则您仍然会有严重偏向的最低有效位,而另一位却没有补偿-实际上,出于完全相同的原因,它也偏向零(但偏少)。因此分布约为50、12.5、25、12.5。(lastbit & 3) == 0虽然可以,但是很奇怪。
哈罗德2014年

Answers:


165

因为nextDouble是这样的:(source

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

next(x)使x随机位。

现在为什么这么重要?由于第一部分(除法之前)生成的数字的大约一半小于1L << 52,因此它们的有效位数未完全填充它可以填充的53位,这意味着有效位数的最低有效位对于这些位始终为零。


由于这引起了人们的广泛关注,因此,这里对doubleJava(和许多其他语言)中的a真正看起来是什么以及在这个问题中为什么如此重要进行了一些额外的解释。

基本上,double看起来像这样:(source

双重布局

在此图中看不到的一个非常重要的细节是数字被“规格化”为1,以使53位分数以1开头(通过这样选择指数),然后省略1。这就是为什么图片显示小数(有效位数)为52位,但其中有效为53位的原因。

规范化意味着,如果在代码中为nextDouble第53位设置了该位,则该位为隐含的前导1,然后消失,其余52位从字面上复制为结果的有效位double。但是,如果未设置该位,则必须将其余位左移,直到将其置位。

平均而言,生成的数字一半属于有效数根本没有左移的情况(大约一半的数字的最低有效位为0),而另一半则至少移位了​​1(或只是完全移位了)零),因此它们的最低有效位始终为0。

1:并非总是如此,显然不能做到没有最高1的零。这些数字称为非正规数或次正规数,请参阅Wikipedia:非正规数


16
万岁!正是我所希望的。
Sneftel 2014年

3
@Matt大概是速度优化。另一种方法是生成具有几何分布的指数,然后分别生成尾数。
Sneftel 2014年

7
@Matt:定义“最佳”。random.nextDouble()通常是实现其目的的“最佳”方法,但是大多数人并没有尝试从其随机双精度数产生1位哈希。您是否正在寻找统一的分布,对密码分析的抵抗力或什么?
StriplingWarrior 2014年

1
这个答案表明,如果OP将随机数乘以2 ^ 53并检查所得整数是否为奇数,则将有50/50的分布。
rici 2014年

4
@ The111它在这里next必须返回一个int,因此它最多只能有32位
harold 2014年

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的可能性是三倍。比那会是1!在实践中,这种不均匀性可能并不重要,但是我们努力追求完美。

至少从Java 5开始就存在此注释(Java <= 1.4的文档位于登录墙后,懒得检查)。这很有趣,因为即使在Java 8中,问题显然仍然存在。也许“固定”版本从未经过测试?


4
奇怪。我只是转载此Java的8
aioobe

1
现在这很有趣,因为我只是说偏见仍然适用于新方法。我错了吗?
哈罗德2014年

3
@harold:不,我认为您是对的,任何试图纠正这一偏见的人都可能会犯错。
托马斯

6
@harold是时候给Java专家发送电子邮件了。
丹尼尔(Daniel)

8
“也许固定版本从未经过测试?” 实际上,在重读此内容时,我认为该文档涉及另一个问题。请注意,它提到了舍入,这表明他们没有直接考虑“三倍的可能性”是问题,而是在对值进行舍入时导致分布不均匀。请注意,在我的答案中,我列出的值是均匀分布的,但是以IEEE格式表示的低阶位不是均匀的。我认为他们解决的问题与整体一致性有关,而不是低位的一致性。
2014年

33

考虑到浮点数的表示方式,这个结果并不令我感到惊讶。假设我们有一个只有4位精度的非常短的浮点类型。如果我们要生成一个介于0和1之间的随机数,并且分布均匀,那么将有16个可能的值:

0.0000
0.0001
0.0010
0.0011
0.0100
...
0.1110
0.1111

如果那是他们在计算机中的外观,则可以测试低阶位以获得50/50的分布。但是,IEEE浮点数表示为尾数的2倍;浮点数中的一个字段是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

1二进制点之前的表示一个隐含值;对于32位和64位浮点数,实际上没有分配任何位来保存该值1。)

但是,看上面的内容应该可以证明为什么,如果将表示形式转换为位并查看低位,那么您将有75%的时间得到零。这是由于所有小于0.5的值(二进制0.1000),这是可能值的一半,其尾数被移位,导致0出现在低位。当尾数具有a的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之间的随机分布,我们也有比随机* n 更好的选择(请看arc4random_uniform)
mirabilos 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.