不,对于大多数值的 n,得到的分布不会完美地均匀。对于小的 n 值,它将非常接近于均匀分布,以至于你很难从均匀分布中检测出任何差异,但是随着 n 的增大,偏差可能变得明显。
为了说明这一点,这里有一些 Python 代码(不是 JavaScript,抱歉,但原理相同):
from collections import Counter
from random import random
def badrand(n):
return int(random() * n)
print(Counter(badrand(6755399441055744) % 3 for _ in range(10000000)))
这将在区间
[0, 6755399441055744)
内生成1000万个随机整数,对每个整数取模3,统计余数为0、1或2的次数。如果我们均匀地生成这些整数,我们期望模3的余数大致均匀分布,因此我们期望计数相似。
以下是在我的机器上运行此操作的示例结果:
Counter({1: 3751915, 0: 3334643, 2: 2913442})
也就是说,余数为
1
的发生概率比
0
显著地高,而
0
的发生概率又比余数为
2
显著地高。这里的差异远远超出了随机变异的范围。
那么出了什么问题呢?Python的
random()
函数相对较高质量,基于
Mersenne Twister,因此我们不太可能看到基础随机数生成器导致的统计问题。问题在于
random()
生成2^53(大约)个等可能结果之一 - 每个结果都是某个整数
x
(范围为
[0,2^53)
)的形式
x/2^53
的数字。现在,在
badrand
调用中,我们有效地将这些结果映射到
6755399441055744
个可能的输出之一。现在,该值不是随机选择的(哈!);它恰好是2^53的3/4。这意味着在最均匀的分布下,2/3的可能的
badrand
输出值被恰好一个2^53个可能的
random()
输出值命中,而另外1/3则被
两个2^53个可能的
random()
输出值命中。也就是说,一些潜在的输出是发生的可能性是其他输出的
两倍。所以我们离均匀还有很长的路要走。
你会在JavaScript中看到相同的效果。在Chrome的情况下,似乎
Math.random()只有2 ^ 32个不同的结果,因此您应该能够找到像上面那样的效果,其中
n
小于(但接近)2 ^ 32。
当然,对于小的
n
也是如此:如果
n = 5
,那么因为
5
不是
2 ^ 32
的除数,我们无法完美地均匀分配所有
2 ^ 32
可能的
Math.random()
结果之间的5个期望结果:我们所能希望的最好的结果是4个5个结果中的每一个出现在858993459个可能的
random()
结果中,而第五个出现在858993460个
random()
结果中。但是,该分布将非常接近均匀分布,以至于很难找到任何统计测试来告诉您不同。因此,在实际目的中,使用小的
n
应该是安全的。
这里有一个相关的 Python bug,可能会对您感兴趣,链接为http://bugs.python.org/issue9025。该 bug 在 Python 3 中已通过摒弃使用 int(random() * n)
方法计算这些数字而得到解决。然而,该 bug 在 Python 2 中仍然存在。
Math.random
以相等概率生成 2^53 个不同结果,我们只能得到对于n
的精确除数为2^53
的值的均匀结果。例如,当n=5
时,如果我们将2^53
个不同元素分成 5 个箱子,则无论如何放置这些元素,这些箱子都不能拥有相同数量的元素。 - Mark Dickinsonn
接近2^53
时存在严重偏差。 (该问题已在Python 3中修复。) - Mark Dickinson