【问题标题】:Why does this random value have a 25/75 distribution instead of 50/50?为什么这个随机值的分布是 25/75 而不是 50/50?
【发布时间】:2015-02-21 21:43:44
【问题描述】:

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

我想以 50/50 的几率将 double 映射到 truefalse。为此,我编写了选择一些随机数的代码(仅作为示例,我想在有规律的数据上使用它并且仍然得到 50/50 的结果),检查它们的最后一位并递增 @987654325 @如果是1,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

【问题讨论】:

  • 我真的希望答案是关于浮点变量的随机生成,而不是“LCG 在低位具有低熵”。
  • 我很好奇,“双倍的 1 位哈希”的目的是什么?我真的想不出这种要求的任何合法应用。
  • @corsiKa 在几何计算中,我们经常要从两个可能的答案中选择两种情况(例如指向线的左侧还是右侧?),有时它会引入第三种,退化的情况(点就在线上),但你只有两个可用的答案,所以在这种情况下你必须伪随机地选择一个可用的答案。我能想到的最好方法是对给定的双精度值之一进行 1 位哈希(请记住,这些是几何计算,所以到处都有双精度值)。
  • @corsiKa (因为太长,评论分成两部分)我们可以从doubleValue % 1 &gt; 0.5 这样更简单的东西开始,但这会过于粗略,因为它在某些情况下会引入可见的规律性(所有值都在长度 1) 的范围内。如果这太粗粒度,那么我们是否应该尝试更小的范围,比如doubleValue % 1e-10 &gt; 0.5e-10?嗯,是。并且仅将最后一位作为double 的哈希值是当您遵循这种方法直到最后时会发生的情况,并且模数尽可能少。
  • @kmote 那么你仍然有严重偏向的最低有效位,而另一位不能补偿它 - 事实上它也偏向零(但更少),确切地说同样的原因。所以分布将是大约 50、12.5、25、12.5。 (lastbit &amp; 3) == 0 会起作用,虽然很奇怪。

标签: java random double bit-manipulation probability


【解决方案1】:

因为 nextDouble 是这样工作的:(source)

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

next(x) 生成 x 随机位。

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


由于受到了广泛关注,这里有一些额外的解释,说明 Java(和许多其他语言)中的 double 的真实外观以及它在这个问题中的重要性。

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

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

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

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

1:并非总是如此,显然它不能对没有最高 1 的零进行。这些数字称为非正规数或次正规数,请参阅wikipedia:denormal number

【讨论】:

  • 万岁!正是我所希望的。
  • @Matt 大概是速度优化吧。另一种方法是生成具有几何分布的指数,然后分别生成尾数。
  • @Matt:定义“最佳”。 random.nextDouble() 通常是其预期用途的“最佳”方式,但大多数人并不想从他们的随机双精度数中产生 1 位哈希。您是在寻找均匀分布、对密码分析的抵抗力还是什么?
  • 这个答案表明,如果 OP 将随机数乘以 2^53 并检查得到的整数是否为奇数,那么就会有 50/50 的分布。
  • @The111 它说here next 必须返回一个int,所以无论如何它最多只能有32位
【解决方案2】:

来自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);

这似乎是等效的,如果不是更好的话,但实际上由于浮点数舍入的偏差,它引入了很大的不均匀性:低位的可能性是低位的三倍的有效数字将是 0,而不是 1!这种不一致性在实践中可能无关紧要,但我们力求完美。]

至少从 Java 5 开始就有这个注释(Java

【讨论】:

  • 奇怪。我刚刚在 Java 8 上复制了这个。
  • 现在这很有趣,因为我只是认为这种偏见仍然适用于新方法。我错了吗?
  • @harold:不,我认为你是对的,试图纠正这种偏见的人可能犯了一个错误。
  • @harold 是时候给 Java 人发一封电子邮件了。
  • "也许固定版本从未测试过?"实际上,在重读这篇文章时,我认为该文档是关于一个不同的问题。请注意,它提到了 rounding,这表明他们没有直接认为“三倍的可能性”是问题,而是当值是圆角。请注意,在我的回答中,我列出的值是均匀分布的,但以 IEEE 格式表示的低位并不均匀。我认为他们解决的问题与整体均匀性有关,而不是低位的均匀性。
【解决方案3】:

考虑到浮点数的表示方式,这个结果并不让我感到惊讶。假设我们有一个非常短的浮点类型,只有 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 且 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 出现在低位。当尾数有 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

但我不确定这是大多数程序员所期望的那种分发,所以它可能不值得。另外,当这些值用于生成整数时,它不会给您带来太多好处,因为通常是随机浮点值。)

【讨论】:

猜你喜欢
  • 2020-01-12
  • 1970-01-01
  • 2020-04-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-10
  • 1970-01-01
相关资源
最近更新 更多