我只是在PHP
闲逛,我决定使用PHP_INT_MIN
( - 9223372036854775808)和PHP_INT_MAX
(9223372036854775807)生成一些随机数字。我只是echo
编辑了以下内容:
echo rand(-9223372036854775808, 9223372036854775807);
我不断刷新以查看生成的数字并查看数字的随机性,结果我开始注意到一种模式正在出现。每出2-4次刷新0就会出现,这种情况一定会发生,在一个阶段,我甚至连0都连续出现4次。
我想进一步尝试,所以我创建了以下代码段:
<?php
$countedZero = 0;
$totalGen = 250;
for ($i = 1; $i <= $totalGen; $i++) {
$rand = rand(-9223372036854775808, 9223372036854775807);
if ($rand == 0) {
echo $i . ": <font color='red'>" . $rand . "</font><br/>";
$countedZero++;
} else {
echo $i . ": " . $rand . "<br/>";
}
}
echo "0 was generated " . $countedZero . "/" . $totalGen . " times which is " . (($countedZero / $totalGen) * 100) . "%."
?>
这会让我清楚地了解发电率是多少。我跑了8次测试:
前3个测试使用的是$totalGen
250个。(总共3次测试)。
第二次3次测试使用的是$totalGen
1000次。(共计6次测试)。
第三个测试只是为了看看结果会对更大的数字产生什么影响,我选择了10,000。 (总共7次测试)。
第四次测试是最后的测试,我对此感兴趣,因为最后一次(大量)测试得到了如此高的结果,所以我提高了赌注并将$totalGen
设置为500,000。 (第8次测试总计)。
结果
我截取了结果截图。我拿了第一个输出,我没有继续测试它试图让它适合某种模式:
测试1(250)
(1)。
(2)。
(3)。
测试2(1000)
(1)。
(2)。
(3)。
测试3(10,000)
(1)。
测试4(500,000)
(1)。
根据上述结果,可以安全地假设即使可能数的范围达到最大值,0也很有可能出现。所以我的问题是:
为什么会发生这种情况有合理的理由吗?
考虑可以选择多少个数字为什么0是一个重复的数字?
注意测试8最初将是1,000,000,但是它已经非常糟糕,所以如果有人可以测试1,000,000并通过编辑OP来显示结果,我会将其减少到500,000,这将非常感激。
修改1
根据@ maiorano84的要求,我使用了mt_rand
代替rand
,结果就是这些。
测试1(250)
(1)。
(2)。
(3)。
测试2(1000)
(1)。
(2)。
(3)。
测试3(10,000)
(1)。
测试4(500,000)
(1)。
您可以看到的结果显示0仍然很有可能出现。同样使用函数rand
提供了最低的结果。
似乎在PHP7
使用新功能random_int
时,它解决了问题。
答案 0 :(得分:4)
这基本上是某人如何编写错误的rand()
函数的示例。当您在min
中指定max
/ rand()
范围时,您会遇到PHP的一部分内容,这会导致PRNG中的分发不完善。
在php-src中具体为lines 44-45 of php_rand.h
,这是以下宏:
#define RAND_RANGE(__n, __min, __max, __tmax) \
(__n) = (__min) + (zend_long) ((double) ( (double) (__max) - (__min) + 1.0) * ((__n) / ((__tmax) + 1.0)))
从较高的调用堆栈(lines 300-302 in rand.c
of php-src):
if (argc == 2) {
RAND_RANGE(number, min, max, PHP_RAND_MAX);
}
RAND_RANGE
是上面定义的宏。只需拨打rand()
而不是rand(-9223372036854775808, 9223372036854775807)
即可删除范围参数,您将再次获得均匀分布。
这是一个演示效果的脚本......
function unevenRandDist() {
$r = [];
for ($i = 0; $i < 10000; $i++) {
$n = rand(-9223372036854775808,9223372036854775807);
if (isset($r[$n])) {
$r[$n]++;
} else {
$r[$n] = 1;
}
}
arsort($r);
// you should see 0 well above average in the top 10 here
var_dump(array_slice($r, 0, 10));
}
function evenRandDist() {
$r = [];
for ($i = 0; $i < 10000; $i++) {
$n = rand();
if (isset($r[$n])) {
$r[$n]++;
} else {
$r[$n] = 1;
}
}
arsort($r);
// you should see the top 10 are about identical
var_dump(array_slice($r, 0, 10)); //
}
unevenRandDist();
evenRandDist();
array(10) {
[0]=>
int(5005)
[1]=>
int(1)
[2]=>
int(1)
[3]=>
int(1)
[4]=>
int(1)
[5]=>
int(1)
[6]=>
int(1)
[7]=>
int(1)
[8]=>
int(1)
[9]=>
int(1)
}
array(10) {
[0]=>
int(1)
[1]=>
int(1)
[2]=>
int(1)
[3]=>
int(1)
[4]=>
int(1)
[5]=>
int(1)
[6]=>
int(1)
[7]=>
int(1)
[8]=>
int(1)
[9]=>
int(1)
}
请注意,在第一个数组和第二个数组中出现0次的次数存在过分差异。即使从技术上讲,它们都会在PHP_INT_MIN
到PHP_INT_MAX
的同一确切范围内生成随机数。
我想你可以为此归咎于PHP,但重要的是要注意glibc rand
并不知道生成好的随机数(而不管加密)。 This problem is known in glibc's implementation of rand
as pointed out by this SO answer
答案 1 :(得分:3)
我快速查看了您的脚本并通过命令行运行它。我注意到的第一件事是因为我运行的是32位版本的PHP,我的整数最小值和最大值与你的不同。
因为我使用的是原始值,所以我实际上是100%的时间。我通过修改脚本解决了这个问题:
$countedZero = 0;
$totalGen = 1000000;
for ($i = 1; $i <= $totalGen; $i++) {
$rand = rand(~PHP_INT_MAX, PHP_INT_MAX);
if ($rand === 0) {
//echo $i . ": <font color='red'>" . $rand . "</font><br/>";
$countedZero++;
} else {
//echo $i . ": " . $rand . "<br/>";
}
}
echo "0 was generated " . $countedZero . "/" . $totalGen . " times which is " . (($countedZero / $totalGen) * 100) . "%.";
我能够确认每次测试只会产生50%的命中率。
但这是有趣的部分:
$rand = rand(~PHP_INT_MAX+1, PHP_INT_MAX-1);
将范围改变为这些值会导致零的可能性下降到平均0.003%(8次测试后)。奇怪的是,在检查了不零的$ rand的值之后,我看到很多值为1,并且有很多随机的负数。没有出现大于1的正数。
将范围更改为以下后,我能够看到一致的行为和更多随机化:
$rand = rand(~PHP_INT_MAX/2, PHP_INT_MAX/2);
以下是我非常确定的事情:
因为你在这里处理一个范围,你必须考虑最小值和最大值之间的差异,以及PHP是否可以支持该值。
在我的情况下,PHP能够支持的最小值是-2147483648,最大值2147483647,但它们之间的差异实际上最终为4294967295 - 比PHP可以存储的数字大得多,因此它按顺序截断最大值试图管理这个价值。
最终,如果您的最小值和最大值之差超过了PHP_INT_MAX常量,那么您将看到意外行为。