PHP的mt_rand()函数的最小值是多少,如何在32位Linux系统上计算32位整数

4
mt_rand()函数的最小值是多少?32位和64位机器的值是否相同?如何使用mt_rand()生成32位整数(注意不需要高度随机)?
背景:我有一台64位开发物理服务器和一台32位生产VPS。刚意识到生产服务器没有生成跨越完整范围的PK。为了找出问题所在,我运行了以下脚本。64位机器从未(或者至少我从未见过)匹配,但32位机器大约有50%的匹配率。
<?php

date_default_timezone_set('America/Los_Angeles');
ini_set('display_errors', 1);
error_reporting(E_ALL);

$count=0;
for ($i = 0; $i <= 10000; $i++) {
    $rand=2147483648+mt_rand(-2147483647,2147483647); //Spans 1 to 4294967295 where 0 is reserved
    if($rand==2147483649){$count++;}
}
echo('mt_getrandmax()='.mt_getrandmax().' count='.$count);

输出

mt_getrandmax()=2147483647 count=5034

生成PK(私钥) - PK指非对称密钥对中的私钥吗? - VolkerK
@VolkerK PK=数据库表主键,是公开的。很多人会认为它甚至不需要是随机的,应该使用自增的PK,有些人会认为应该使用高度随机的PK,但我不敢问这样一个充满个人观点的问题 :) 我的唯一目的是让PK不会透露记录的年龄。 - user1032531
3个回答

3

简要概述:要获取可能的所有整数范围内的随机整数,请使用以下方法:

function random_integer() {
    $min = defined('PHP_INT_MIN') ? PHP_INT_MIN : (-PHP_INT_MAX-1);
    return mt_rand($min, -1) + mt_rand(0, PHP_INT_MAX);
}

针对PHP 7,您可以使用random_int()函数。


在底层实现 (1, 2),PHP是这样实现的:

$number = random_number_between_0_and_0x7FFFFFFF_using_Mersenne_Twister;
$number = $min + (($max - $min + 1.0) * ($number / (0x7FFFFFFF + 1.0)));

注意 $max - $min。当最大值设置为顶部,并且最小值为任何负数时,会发生溢出。因此,最大的范围是PHP_INT_MAX。如果您的最大值是PHP_INT_MAX,则您的最小值必须为0
现在让我们来看一下背景故事。PHP实现了32位Mersenne Twister算法。这使我们得到介于[0和2^31-1)之间的随机整数。如果您要求任何其他范围,则PHP使用一个简单的分箱函数对该数字进行缩放。该分箱函数包括可以导致溢出的减法,产生了这个问题。
因此,如果您想获得比PHP中的整数表示更大的范围,您必须将区间加在一起,如下所示:
mt_rand(PHP_INT_MIN, -1) + mt_rand(0, PHP_INT_MAX);

请注意,自PHP 7以来,PHP_INT_MIN可用,因此在那之前您需要计算适合您环境的最小值。
另外,注意到2^31-1是getrandmax()返回的值。人们错误地认为,在64位机器上getrandmax()将返回2^63-1。这是不正确的。getrandmax()返回算法将返回的最大整数,该整数始终为2^31-1。

2
您可以使用以下代码生成一个 32 位整数:
$rand = unpack("l", openssl_random_pseudo_bytes(4));

问题是,如果你只需要32位,为什么要浪费32字节的空间? - Sven
确实。已更改为4个字节。 :) - OIS
我的随机需求非常低,而且我不希望为 openssl_random_pseudo_bytes() 所需要的服务器开销买单。请查看我在原帖下发表的评论。 - user1032531

1
这是PHP文档中提到的一个问题。

This works fine on 64 bit Linux:

  <?php
     printf ("%08x\n", mt_rand (0, 0xFFFFFFFF));
   ?>

but on our 32 bit Linux development server, it's always yielding 00000000.

On that same machine, this:

<?php
printf ("%08x\n", mt_rand (0, 0xFFFFFFF0));
?> 

seems to always yield either 00000000 or a number in the range fffffff2 to ffffffff. This:

<?php
printf ("%08x\n", mt_rand (0, 0xFFFFFF00));
?> 

gives numbers where the last two digits vary, and so on through at least 0xF0000000.

However, this:

<?php
   printf ("%08x\n", mt_rand (0, 0x7FFFFFFF));
?>

works fine

一个错误报告已经添加

目前还没有消息表明PHP是否会修复这个问题。

在此期间,您可以在max_rand之间使用mt_rand函数,那么您应该就没问题了。

示例用法

$rand=mt_rand(1,2147483647)+mt_rand(0,2147483647); 

谢谢Carl,如果我找不到更好的解决方案,我打算像你提出的那样去做。我的主要担忧是我不知道这是发生了什么,我认为PHP文档应该得到更新。 - user1032531
我同意。我是在查看所有用户评论时发现了这个问题。希望他们能在下一个版本中修复它。 - cjds
我有什么遗漏吗?根据代码中的定义(https://github.com/php/php-src/blob/master/ext/standard/php_rand.h#L48),`mt_rand` 的最大值为[2 ** 31](https://marc.info/?l=php-internals&m=142889549207950&w=2)。 - bishop
我很困惑。2**31等于2147483648。 - cjds
抱歉,我的意思是2的31次方减1,即(1 << 31) - 10x7FFFFFFF。 :) - bishop
注意,这里有一个补丁修复了这些已报告的错误。如果这些补丁在您的环境中能解决问题,请让我知道。 - bishop

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