我认为 Joe 对措辞有点困惑。注释的意思是,在更改之前实现的 mt_rand() 将为相同的种子生成与更改之后实现的 mt_rand() 不同的伪随机数集。
至少对我来说是这样。
(PHP 4, PHP 5, PHP 7, PHP 8)
mt_srand — 为 Mersenne Twister 随机数生成器播种
用 seed
为随机数生成器播种,如果没有提供 seed
,则使用随机值。
注意: 不需要使用 srand() 或 mt_srand() 为随机数生成器播种,因为这是自动完成的。
因为 Mt19937 (“Mersenne Twister”) 引擎只接受单个 32 位整数作为种子,所以尽管 Mt19937 的周期高达 219937-1,但可能的随机序列数量仅限于 232 (即 4,294,967,296)。
当依赖于隐式或显式随机播种时,重复项会更早出现。根据生日问题,在随机生成不到 80,000 个种子后,重复种子以 50% 的概率出现。随机生成大约 30,000 个种子后,重复种子的概率为 10%。
这使得 Mt19937 不适合于重复序列不能以超过可忽略不计的概率发生的应用。如果需要可重现的播种,Random\Engine\Xoshiro256StarStar 和 Random\Engine\PcgOneseq128XslRr64 引擎都支持更大的种子,这些种子不太可能随机碰撞。如果不需要可重现性,Random\Engine\Secure 引擎提供了密码学安全的随机性。
seed
用线性同余生成器生成的数值填充状态,该生成器以 seed
作为无符号 32 位整数进行播种。
如果省略了 seed
或为 null
,则会使用随机的无符号 32 位整数。
mode
使用以下常量之一来指定要使用的算法的实现。
MT_RAND_MT19937
: 正确的 Mt19937 实现,从 PHP 7.1.0 开始可用。MT_RAND_PHP
使用错误的 Mersenne Twister 实现,该实现是直到 PHP 7.1.0 之前的默认实现。此模式可用于向后兼容。此功能已从 PHP 8.3.0 开始弃用。强烈建议不要依赖此功能。
不返回任何值。
版本 | 描述 |
---|---|
8.3.0 |
seed 现在可以为空。 |
7.1.0 | srand() 已变为 mt_srand() 的别名。 |
7.1.0 |
mt_rand() 已更新 为使用修复后的正确版本的 Mersenne Twister 算法。要回退到旧的行为,请使用 mt_srand(),并将 MT_RAND_PHP 作为第二个参数。 |
我认为 Joe 对措辞有点困惑。注释的意思是,在更改之前实现的 mt_rand() 将为相同的种子生成与更改之后实现的 mt_rand() 不同的伪随机数集。
至少对我来说是这样。
请注意,根据
https://stackoverflow.com/a/11358829/2897386
如果未提供,种子会自动使用当前时间戳初始化。
这意味着,如果您的脚本始终在可预测的时间运行,例如通过 crontab,它将产生质量较差的随机值。在这种情况下,最好手动从密码学安全的来源进行初始化。
看起来,当最低位不同时,mt_rand() 会为不同的种子提供相同的结果。试试这个
#!/usr/bin/php -q
<?php
$min = -17;
$max = $min + 48; // 48 是为了让结果适合我的控制台
for ($testseed=$min; $testseed<$max; $testseed++)
{
mt_srand( $testseed );
$r = mt_rand();
printf("mt_srand( 0x%08x ): mt_rand() == 0x%08x == %d\n", $testseed, $r, $r);
}
?>
这是结果的快照
...
mt_srand( 0xfffffffc ): mt_rand() == 0x0a223d97 == 170016151
mt_srand( 0xfffffffd ): mt_rand() == 0x0a223d97 == 170016151
mt_srand( 0xfffffffe ): mt_rand() == 0x350a9509 == 889885961
mt_srand( 0xffffffff ): mt_rand() == 0x350a9509 == 889885961
mt_srand( 0x00000000 ): mt_rand() == 0x71228443 == 1898087491
mt_srand( 0x00000001 ): mt_rand() == 0x71228443 == 1898087491
mt_srand( 0x00000002 ): mt_rand() == 0x4e0a2cdd == 1309289693
mt_srand( 0x00000003 ): mt_rand() == 0x4e0a2cdd == 1309289693
...
我偶尔会遇到这种情况。我不知道这是否是错误。在我的实际应用中,我不打算使用连续的种子。但是,这可能对某些人来说很重要。
如果您是播种新手,请阅读我的注释。
我现在理解播种是算法的起始状态。此算法会生成一系列“后续”的伪随机数。
如果您从相同的起始值开始生成两次,您将连续两次获得相同的随机数序列。
mt_srand(10); // 您的算法的开始等于播种设置为 10
for($i=0;$i<10;$i++){
echo mt_rand();
}
echo "<BR>";
mt_srand(10); // 您的算法的开始等于播种设置为 10
for($i=0;$i<10;$i++){
echo mt_rand();
}
输出类似于
502355954641584702211262118810740890731360749216120791137454651988317865160461082451610903986200
<BR>
502355954641584702211262118810740890731360749216120791137454651988317865160461082451610903986200
我的结论是:如果您想要“交替的随机数”,请不要始终将您的种子预设为相同的数字。
致敬
对于这个例子怎么样...
(抱歉,换行符很奇怪,但我曾经
更多用户报告了有关此 Word 问题的错误。
包装无效)
意图在传递“半保证”时使用它。
正确播种的随机数给客户端,然后到
捕获来自用户的输入,该输入必须加密
客户端在发送回服务器之前
a) 在同一个会话期间,以及
b) 在设定的时间限制内。
有关更多信息,另请参阅
用于 JavaScrip 的 AES Rijndael 加密/解密例程
由 Herbert Hanewinkel 开发和测试,
http://www.hanewin.net/encrypt/aes/aes.htm
<?php
/*
调用函数,准备要发送给
客户端的数据......,然后客户端在 JavaScrip 中使用它
AES 加密的实现。
*/
function SHA256($str, $keyval=""){
if ($keyval!==""){// 不为空或不为空
$sHash = mhash(Constant('MHASH_SHA256'),$str, $keyval);
}else{
$sHash = mhash(Constant('MHASH_SHA256'),$str);
}
// 与二进制转十六进制转换相同
return implode(unpack('H*',$sHash),'');
}
/*---------------------------------------------------
使用固定数据对随机数据进行随机化
用户和位置 如果返回值最大值
设置为零 (0),则该函数使用 mt_getrandmax
如果 retMin 和 retMax 均为零 (0),则该函数
使用 mt_rand 无限制
*----------------------------------------------------*/
function local_prgn($retMin = 0, $retMax = 0){
// 首先获取请求会话的会话 ID
$sSrv = session_id();
// 以下仅在当前服务器上有效
$sSrv = implode(unpack($_SERVER['SERVER_NAME'].
$_SERVER['SERVER_ADDR'].$sSrv),'');
// 以下仅适用于请求客户端
$sReq = implode(unpack($_SERVER['REMOTE_ADDR'].
$_SERVER['REQUEST_TIME']),'');
// 获取基于当前值的 SHA256 种子
$sSeed = SHA256($sSrv,$sReq);
// 获取基于上述种子唯一性的随机值
mt_srand($sSeed); // 播种随机数生成器
// 一些错误处理和检查
if ($retMin > $retMax){
// 如果顺序错误,则交换变量
$rx = $retMax; $retMax = $retMin; $retMin = $rx;
} else if ($retMin == $retMax){
// 无意义的范围,不太随机
// 确保在下一个检查中在这个范围内
$retMin = 0; $retMax = 0;
}
// 选择我们需要返回的内容
if ((($retMax == 0)&&($retMax = mt_getrandmax))||
($retMin==$retMax)){
return mt_getrandmax();
}else{
return mt_getrandmax($retMin,$retMax);
}
}
?>
给 slonmron
随机数生成器的种子应该只初始化一次,在调用正确的 rand 函数之前。之后,您通过多次调用 rand 来提供伪随机序列。随机数种子的初始化用于 1) 您有比实现的算法更好的随机数种子来源,或者 2) 如果您需要始终使用相同的伪随机数序列。您给出的示例仅表明第一个 rand 结果强烈依赖于种子,这是定义的一部分。这不是错误。
"更好的是:使用 microtime() 的 31 位哈希作为种子。"
如果我错了,请纠正我,但是使用 microtime() 不会将总种子数再次限制为 1,000,000 吗?因为 31 位哈希对于相同数字将始终给出相同的哈希值,并且在 microtime() 函数中,您可能有 1,000,000 个或更少的数字。因此,实际上您仍然没有得到任何改善 :-p
此致敬礼,
scott
PS:我实际上同意 PHP 已经基本上解决了这个问题,并且通过引入“梅森旋转器”算法(该算法创建了一个比 1,000,000 个数字大得多的池)接近解决播种问题。仅仅因为存在 mt_srand() 函数并不意味着您必须使用它 ;-) 如果您需要特定的一组相同数字(在使用密码进行加密时很有用 ;-)),请使用它。
上面关于种子的观点非常好,谢谢。如果您想测试一个种子,请尝试使用下面的代码。它将根据您的系统需要 5 到 20 秒的时间,然后会输出 100,000 次尝试中重复密钥的数量。
; for ($i=0; $i<100000; $i++) {
; mt_srand(hexdec(substr(md5(microtime()), -8)) & 0x7fffffff);
; $rand = mt_rand();
;
; ($arr[$rand] == '1') ? $k++ : $arr[$rand] = '1';
; }
致:[email protected]
正在做......
list($usec,$sec)=explode(" ",microtime());
$unique = mt_srand($sec * $usec);
理论上,与以下方法意义相同
list($usec,$sec)=explode(" ",microtime());
$unique = $usec + 0;
偶尔,根据计算机的微秒分辨率,毫秒值将为零 (0),我希望您知道,在数学中,任何数字乘以零都将变为零本身。
(x * 0 = 0)
在现实生活中,在一台性能良好的机器上,以每秒 100 万毫秒的分辨率(即:Win2k 服务器),您将每发出 100 万个 ID 就重复一次您的唯一 ID。这意味着如果您将其用作您的 cookie 加密算法或访问者 ID,您将不会超过数百万个实例。
此外,如果这是为了您重新分发的软件开发,安装在一些奇怪的旧 PC 上,那里的分辨率可能低至每秒 100 毫秒——使用此唯一性算法的代码不会持续很长时间。
祝您好运,
Maxim Maletsky
[email protected]
PHPBeginner.com
list($usec,$sec) = explode(" ",microtime());
/* 测试:每次获取 rand 序列为 10 次。 */
/* 例如)5.3 点表示 5 点整数 + 3 点小数 */
// 案例 A
// 5.0 点 - 1 次
// 6.0 点 - 9 次
$rand = (double)microtime()*1000000;
// 案例 B
// 8.6 点 - 1 次
// 9.4 点 - 1 次
// 9.5 点 - 7 次
// 10.3 点 - 1 次
$rand = (double)$sec * $usec;
// 我的案例 A
// 8.0 点 - 10 次
$rand = explode(".",$usec * $sec);
$rand = (double)substr($rand[0]*$rand[1],0,8);
// 我的案例 B
// 9.0 点 - 9 次
// 10.0 点 - 1 次
$rand = explode(".",$usec * $sec);
$rand = $rand[0] + $rand[1];
mt_srand($rand);
srand($rand);
// 补充说明> 我之前的笔记有错误的行,对此表示歉意。这是正确的。
抱歉,之前的代码有误...
由于 wordwrap 出现故障,我感到很恼火,并且对复制粘贴操作失去了关注。
实际函数的最后部分应该读作。
<?php
// 选择我们需要返回的内容
if ((($retMax == 0)&&($retMax = mt_getrandmax))||
($retMin==$retMax)){
return mt_rand();
}else{
return mt_rand($retMin,$retMax);
}
?>
当然,除此之外没有其他内容...
@ fasaxc at yahoo dot com
如果您想要真正的随机数,请使用真正的随机数源。当您可以简单地调用 openssl_random_pseudo_bytes() 来获得良好的随机性时,您的系统相当笨拙。不要将 microtime 用作随机数源。
确保随机种子的最佳方法是执行以下操作
首先
1) 使用 mt_srand(microtime() * 1000000) 获取初始种子
2) 生成一个随机数。$random=mt_rand()
3) 将此数字保存在文件中(或数据库或任何可以在下一次加载页面时获得的地方)
现在,对于每次加载脚本时
1) 加载您之前保存的值并执行 $new_seed=($random+(microtime() * 1000000))%pow(2,32)
2) mt_srand($new_seed);
3) 生成一个新的随机数。 $random=mt_rand()
4) 将该数字保存回文件/数据库
此过程不仅利用了 microtime() 的随机性,还利用了之前所有对 microtime() 的调用,因此您的种子会随着时间的推移而变得越来越好。即使在 microtime() 无法获取所有值的平台上,它也能生成良好的种子。
仅使用 microtime() * 1000000 仅产生 1000000 个可能的种子(如上所述,在某些平台上更少) - 上述函数通过多次执行的雪崩效应提供了 2^32 个种子。
mt_srand 在 32 位正整数上有效地执行模运算 % 2147483648,但在负整数上,它会将 2147483648 添加到它获得的值。
具有相同结果的种子
2147483649 == 1
2147483648 == 0
2147483647 == -1
-2147483646 == 2
-2147483647 == 1
-2147483648 == 0
重要的是,使用小于 -2147483648 的任何值进行播种始终会产生与使用零进行播种相同的結果。
事实上,以下函数比下面的函数更好,假设您的安装提供了一个随机熵守护进程,并且您正在运行 *nix(要检查前者,请在命令行中键入“head -c 6 /dev/urandom”,如果可用 - 如果您得到 6 个随机字符,则已设置)。注意:php 必须能够找到 head 程序,因此它必须在您的路径中,并且如果您正在运行安全模式,则必须允许它。
我使用的函数 db_set_global() 和 db_get_global() 用于从中央数据库设置/获取变量,但您可以从文件而不是数据库中保存/恢复变量,或者只使用函数 get_random_word()。
<?
####################################
## 返回一个随机的 32 位整数。
## 传入一个 True 参数会得到一个更好的随机数
## 但依赖于 /dev/random 设备
## 它可能会阻塞很长时间,直到它收集到
## 足够的随机数据,即:除非
## a) 您的计算机上连接了一个熵生成器到
## /dev/random - 或 -
## b) 您的脚本正在本地运行,并且生成
## 一个好的随机数非常重要
####################################
function get_random_word($force_random=False) {
if ($force_random) {
$u='';
} else {
$u='u';
}
$ran_string=shell_exec("head -c 4 /dev/{$u}random");
$random=ord(substr($ran_string,0,1))<<24 |
ord(substr($ran_string,1,1))<<16 |
ord(substr($ran_string,2,1))<<8 |
ord(substr($ran_string,3,1));
return $random;
}
-- 要么 - 如果您已经设置了全局变量的数据库 - --
## 如果种子在数据库中找到
if ($seed=db_get_global('seed')) {
# 使用 mt_rand() 获取下一个种子
mt_srand($seed);
# 然后与一个随机单词进行异或运算
$seed=(mt_rand() ^ get_random_word());
} else {
## 生成一个全新的种子(首次运行)
# 使用 /dev/random 作为适当的随机数生成种子
$seed=get_random_word(True);
mt_srand($seed);
}
db_set_global('seed',$seed);
-- 或只是 - --
mt_srand(get_random_word());
?>
我花了几个小时试图追踪影响 mt_rand/rand 和 mt_srand/mt_rand 的一个 bug。
操作系统是 Debian 5.0.4 “Lenny”。
PHP 版本为 5.3.2-0.dotdeb.1,包含 Suhosin-Patch (cli)(构建时间:2010 年 3 月 9 日 11:42:01)。
我尝试通过在 .htaccess / apache2 主配置文件中追加以下几行来修复这个问题
php_value suhosin.mt_srand.ignore Off
php_value suhosin.srand.ignore Off
这起到了一点作用,稳定了伪随机数序列的开头,但在相当多次迭代(大约 1K~3K)之后,生成器仍然会失败。
*** 删除 Suhosin 扩展程序已经解决了这个问题,我正在等待一个与 5.3.x 兼容的官方扩展程序版本,这样我就可以把它重新附加到 php 配置中。 ***
以下代码应该可以复制这个问题
$len = 100000;
$min = 0;
$max = 99;
$t = (int)(microtime(true)*0xFFFF);
$a = array();
srand( $t );
for ( $i = 0; $i < $len; $i ++ )
$a[$i] = rand( $min, $max );
$b = array();
srand( $t );
for ( $i = 0; $i < $len; $i ++ )
$b[$i] = rand( $min, $max );
for ( $i = 0; $i < $len; $i ++ )
if ( $a[$i] !== $b[$i] )
die( '伪随机序列在第 #'.$i.' 次迭代时出错!');
echo '您的伪随机序列生成器正常工作。';
exit( 0 );
我无法强调在代码中为您的随机化过程播种的重要性!更棒的是,我们在 BBS 年代发现的是,如果我们没有从我们系统抽象层之外的令牌进行播种,我们就会陷入循环,我们的用户也会如此。在 chronolabs,我们在每次显示时提供一个随机变化的令牌馈送,它还会随机显示不同数量的令牌,这来自 http://seed.feeds.labs.coop,在下面的示例中,我使用 DOM 加载 XML,提取随机化令牌,然后使用 mt_srand 和 srand 对随机选择过程进行播种!以下函数在调用时将为您在旧的和新的随机选择例程中都播种随机选择过程,您只需调用该函数即可!这将适用于任何版本的 PHP 5 及其早期版本,并具有 DOM 目的性。
function makeRandomSeeded() {
$file = 'http://seed.feeds.labs.coop/';
$doc = new DOMDocument();
$doc->loadHTMLFile($file);
$skip = array('This feed can', 'Current mode is');
$elements = $doc->getElementsByTagName('description');
foreach($elements as $element) {
$seed = $element->nodeValue;
$found = false;
foreach($skip as $find) {
if (substr($seed, 0, strlen($find))==$find) {
$found = true;
}
}
if ($found==false)
$seeds[] = $seed;
}
shuffle($seeds);
mt_srand($seeds[mt_rand(0, count($seeds)-1)]);
srand($seeds[mt_rand(0, count($seeds)-1)]);
}
记住,当 PHP 说整数时,这也包括 ASCII 图表中的任何字符,如果您想查看示例,请执行以下操作
<?php
$a = "000A";
while($a!="001B") {
echo $a;
$a++;
}
?>