大多数答案都使用mt_rand。但是,mt_getrandmax() 通常只返回2147483647。这意味着你只有 31 位的信息,而 double 有一个 52 位的尾数,这意味着 0 到 1 之间的数字的密度至少为 2^53。
这种更复杂的方法会让你得到更精细的分布:
function rand_754_01() {
// Generate 64 random bits (8 bytes)
$entropy = openssl_random_pseudo_bytes(8);
// Create a string of 12 '0' bits and 52 '1' bits.
$x = 0x000FFFFFFFFFFFFF;
$first12 = pack("Q", $x);
// Set the first 12 bits to 0 in the random string.
$y = $entropy & $first12;
// Now set the first 12 bits to be 0[exponent], where exponent is randomly chosen between 1 and 1022.
// Here $e has a probability of 0.5 to be 1022, 0.25 to be 1021, etc.
$e = 1022;
while($e > 1) {
if(mt_rand(0,1) == 0) {
break;
} else {
--$e;
}
}
// Pack the exponent properly (add four '0' bits behind it and 49 more in front)
$z = "\0\0\0\0\0\0" . pack("S", $e << 4);
// Now convert to a double.
return unpack("d", $y | $z)[1];
}
请注意,上述代码仅适用于具有 Litte-Endian 字节顺序和 Intel 风格的 IEEE754 表示的 64 位机器。 (x64-兼容的计算机会有这个)。不幸的是,PHP 不允许位移超过int32 大小的边界,因此您必须为 Big-Endian 编写一个单独的函数。
你应该替换这一行:
$z = "\0\0\0\0\0\0" . pack("S", $e << 4);
与其对应的大端:
$z = pack("S", $e << 4) . "\0\0\0\0\0\0";
只有在函数被大量调用时才显着区别:10^9 或更多。
测试是否可行
很明显,尾数遵循一个很好的均匀分布近似值,但大量此类分布的总和(每个分布的概率和幅度都减半)是均匀的就不那么明显了。
跑步:
function randomNumbers() {
$f = 0.0;
for($i = 0; $i < 1000000; ++$i) {
$f += \math::rand_754_01();
}
echo $f / 1000000;
}
产生0.49999928273099(或接近 0.5 的类似数字)的输出。