使用 Math.random() 获得通常排除的上限的几率

Odds to get the usually excluded upper-bound with Math.random()

提问人:Fabrício Matté 提问时间:1/12/2013 最后编辑:CommunityFabrício Matté 更新时间:10/25/2013 访问量:522

问:

这可能看起来更像是一个数学问题,但由于它专门链接到 Javascript 的伪随机数生成器,我想它非常适合 SO。如果没有,请随时将其移至其他地方。

首先,我知道 ES 没有指定在伪随机数生成器中使用的算法 - -,但它确实指定了范围应该具有近似的均匀分布:Math.random()

15.8.2.14 随机 ( )

使用与实现相关的算法或策略,返回一个带有正号的 Number 值,该值大于或等于 0 但小于 1,该值是随机或伪随机选择的,在该范围内分布大致均匀。此函数不带任何参数。

目前为止,一切都好。现在,我最近偶然发现了来自 MDN 的这段数据:

请注意,由于 JavaScript 中的数字是 IEEE 754 浮点数,具有从舍入到最接近偶数的行为,因此这些范围(不包括本身的范围)并不精确,并且根据边界,在极少数情况下(大约为 1/2^62)可以计算通常排除的上限。Math.random()

好。这让我进行了一些测试,结果(显然)在 Chrome 控制台和 Firefox 的 Firebug 上是相同的:

>> 0.99999999999999995
1
>> 0.999999999999999945
1
>> 0.999999999999999944
0.9999999999999999

让我们用一个简单的实际例子来说明我的问题:

Math.floor(Math.random() * 1)

考虑到上面的代码,IEEE 754 浮点数具有四舍五入到最接近偶数的行为,在范围均匀分布的评估下,我得出结论,它返回通常排除的上限(在我上面的代码中)的几率是 ,即大约 。Math.random()10.000000000000000055555...1/18,000,000,000,000,000

现在查看 MDN 数字,计算结果为 ,即比我的计算结果小 200 多倍。1/2^621/4,611,686,018,427,387,904

我做错了数学吗?Firefox 的伪随机数生成器是否分布不够均匀,无法产生这 200 倍的差异?

我知道如何解决这个问题,我知道这么小的几率甚至不应该被考虑在每天的使用中,但我很想知道这里发生了什么,以及我的数学是否被打破了或Mozilla的(我希望是前者)。 任何意见都是值得赞赏的。=]

JavaScript的

评论

1赞 Esailija 1/12/2013
据我所知,所有实现的分布根本不是均匀的,可以用直接从维基百科实现的函数来击败。Math.random
0赞 Fabrício Matté 1/12/2013
谢谢,您总是为我的问题提供非常有用的 ES/JS 相关数据@Esailija=]
0赞 Fabrício Matté 1/12/2013
这可能有点偏离,但返回 63 位,但 JS 会四舍五入到第 54 位的上限。也不确定这个计算是否正确。我想我需要更深入地挖掘。Math.pow(2, 62).toString(2)
0赞 Esailija 1/12/2013
.toString(2)与数字比特无关,它是二进制数字系统转换。所以它甚至可以与等等Math.pow(2,1000).toString(2)
1赞 Esailija 1/13/2013
是的,与 JS 浮点数后面的实际二进制文件完全无关。 是。。。但是 double 下的实际位数始终为 64。toString(2)Math.pow(2,1000).toString(2).length1001

答:

2赞 jJ' 1/12/2013 #1

您不必担心将 Math.random() 中的数字四舍五入到 1。

当我在当前版本的 IE、Chrome 和 FF 中查看实现(从我得到的结果推断)时,有几个观察结果几乎可以肯定意味着您应该始终在 0 到 0.11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111110.999999999999999944.toString(2)

铬:这里很简单。它通过生成 32 位数字并将其除以 1 << 32 来生成数字。(您可以看到始终返回整数)。(1 << 30) * 4 * Math.random()

FF:在这里,似乎总是生成的数字最多是 0.11......(53x 1),它实际上只使用小数点后 53 位。(您可以看到返回的不超过 53)。Math.random().toString(2).length - 2

IE浏览器:这里它与 FF 非常相似,只是如果小数点后的第一个数字是 0,并且这些数字肯定不会四舍五入为 1,则位数可能会更多。(您可以看到返回的不超过 53)。Math.random().toString(2).match(/1[01]*$/)[0].length

我认为(尽管我现在无法提供任何证据)任何实现都应该属于所描述的组之一,并且四舍五入到 1 没有问题。

评论

0赞 Fabrício Matté 1/13/2013
+1非常有用的数据,谢谢。对于这个问题,我主要将其范围限定为Firefox实现,因为数据来自MDN,而其他JS实现(包括闭源实现)会有所不同。虽然拥有其他实现的数据很棒。 不过,有一件事,在你调用结果之前,已经遭受了四舍五入以适应 JS 的 53 位尾数,不是吗?=].toString(2)Math.random()
0赞 Fabrício Matté 1/13/2013
另请查看此链接:“对于 ,MS IE 8 显示 bit53 的 '1' 位,以及 bit54,但设置了 BIT 1 时除外(随机值≥ 0.5)。Firefox 和 Opera 都不错,bit53 的“1”位。Safari 和 Chrome 将“1”位显示为 bit32。(2010-12-09,当前浏览器版本。到目前为止,一切都符合您的答案。Math.random()
0赞 jJ' 1/13/2013
正是由于尾数只是一个已知位数的序列,如果随机数生成器不这样对待它,它只会变得更糟(无论是在效率方面还是在四舍五入问题方面)。顺便说一句,您的示例中的大多数舍入是另一种类型 - 在解析十进制表示时进行舍入。您可以看到 0.9999999999999999944 === 0.9999999999999999934。
0赞 Fabrício Matté 1/13/2013
是的,评估结果与此完全相同。在我将您的答案标记为已接受之前,只剩下一个问题,如果您查看问题的评论,您使用的方法似乎与数字位/尾数无关。不过,它如何匹配您的结果和上面链接页面的结果很奇怪。二进制表示和我缺少的尾数之间有什么关系吗?0.9999999999999999440.99999999999999990.999999999999999834.toString(2)
0赞 jJ' 1/13/2013
十进制表示的问题在于它不能很好地映射到内部二进制形式。所以 0.9999999999999999944 的计算结果并不是 0.999999999999999999,而是满是 1 的尾数,这是不一样的数字。一个很好的例子是 .2.toString(2),它表明 0.2 只是在二进制表示中是近似的(它在那里是一个周期数)。当你使用 .toString(2) 时,情况就不同了,因为显示的数字代表尾数,因为在打印时不需要“丢失”任何信息,而是根据指数“填充”0(并显示前导“隐藏”1)。