为什么mt_rand(1, PHP_INT_MAX)总是返回一个奇数?

10
我刚在Hacker News上看到了ComputerGuru的有趣问题,但是没有任何评论给出令人信服的答案。
为什么mt_rand(1, PHP_INT_MAX)总是返回奇数?
我不是原问题的作者。

http://3v4l.org/dMbat

for ($i=0;$i<10000;$i++)
{
    echo mt_rand(1, PHP_INT_MAX)."\n";
}

输出:

8571620074060775425
7401021871338029057
4351677773593444353
1801559362708176897
7848614552286527489
...

5
可能与此相关,以下内容摘自PHP手册中mt_rand函数的页面:注意:当max超过2^32时,在64位版本的PHP中,mt_rand()返回值的分布偏向于偶数。这是因为如果max大于mt_getrandmax()返回的值,则需要将随机数生成器的输出进行缩放。 - Simba
4
由于 PHP_INT_MAX > mt_getrandmax(),因此在使用 mt_rand(1, PHP_INT_MAX) 时,只会生成奇数。更多细节请参考链接:https://www.reddit.com/r/lolphp/comments/3eaw98/mt_rand1_php_int_max_only_generates_odd_numbers/ctdhxha。 - Sony
1个回答

6
这里的PHP_INT_MAX是2的63次方减1(64位有符号整数最大值)。
然而,mt_rand()无法处理这么大的值。Mersenne旋转器内部生成32位字,并且PHP的mt_getrandmax()仅为2的31次方减1(它丢弃了最高位)。
要在您请求的minmax范围内生成值,mt_rand首先获取0到2的31次方减1的随机数,然后使用以下公式进行缩放:
x = ((x / (mt_getrandmax() + 1)) * (max - min + 1)) + min;

(查看rand.cphp_rand.h的源代码。)

基本上,它盲目地缩放内部生成的数字以适应过大的范围,甚至没有引发警告。将其乘以适应过大的范围会在低位生成许多零,然后添加min(即1)会使结果变为奇数。

在十六进制中问题更加严重,您可以看到每个数字的低32位是完全非随机的:

for ($i = 0; $i < 10000; $i++) {
    printf("%016x\n", mt_rand(1, PHP_INT_MAX));
}

输出:

41e0449b00000001
53d33d7c00000001
6ec8855700000001
234140e000000001
13a4581900000001
77547beb00000001
35a0660a00000001
0d0cd44200000001
...

手册中有一条注意事项, 试图警告这个问题,尽管它低估了这个问题:

mt_rand()返回值的分布在64位PHP构建时偏向于偶数,当max超过232。这是因为如果max大于mt_getrandmax()返回的值,则必须扩大随机数生成器的输出。

(它说偏向于偶数,但只有当min是偶数时才成立。)


1
非常好的解释,谢谢。如果您传递一个值而不是 mt_getrandmax(),PHP 不抛出错误/异常,这有点奇怪。 - ceejayoz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接