PHP 日本研討會 2024

mt_srand

(PHP 4, PHP 5, PHP 7, PHP 8)

mt_srand設定 Mersenne Twister 亂數產生器的種子

描述

mt_srand(?int $seed = null, int $mode = MT_RAND_MT19937): void

seed 設定亂數產生器的種子,若未給定 seed 則使用隨機值。

注意不需要使用 srand()mt_srand() 來設定亂數產生器的種子,因為這會自動完成。

注意

由於 Mt19937(「Mersenne Twister」)引擎只接受單一 32 位元整數作為種子,因此可能的隨機序列數目僅限於 232 (亦即 4,294,967,296),儘管 Mt19937 的巨大週期為 219937-1。

當依賴隱式或顯式的隨機種子時,重複將會更早出現。根據生日問題,在隨機產生少於 80,000 個種子後,重複種子的預期機率為 50%。隨機產生約 30,000 個種子後,發生重複種子的機率為 10%。

這使得 Mt19937 不適合用於必須避免以高於可忽略機率重複序列的應用程式。如果需要可重現的種子,Random\Engine\Xoshiro256StarStarRandom\Engine\PcgOneseq128XslRr64 引擎都支援更大的種子,這些種子不太可能隨機衝突。如果不需要可重現性,Random\Engine\Secure 引擎提供密碼學安全的隨機性。

參數

seed

使用線性同餘產生器產生的值填滿狀態,該產生器以 seed(解譯為無號 32 位元整數)作為種子。

如果省略 seed 或為 null,將使用隨機的無號 32 位元整數。

mode

使用下列其中一個常數來指定要使用的演算法實作。

  • MT_RAND_MT19937:自 PHP 7.1.0 起提供的正確 Mt19937 實作。
  • MT_RAND_PHP 使用不正確的 Mersenne Twister 實作,此實作在 PHP 7.1.0 之前用作預設值。此模式可用於向後相容性。

警告

自 PHP 8.3.0 起,此功能已棄用。強烈建議不要依賴此功能。

回傳值

不會傳回任何值。

變更記錄

版本 描述
8.3.0 seed 現在可為 null。
7.1.0 srand() 已成為 mt_srand() 的別名。
7.1.0 mt_rand() 已更新為使用 Mersenne Twister 演算法的修正版、正確版本。若要還原為舊行為,請使用第二個參數為 MT_RAND_PHPmt_srand()

參見

  • mt_rand() - 透過 Mersenne Twister 亂數產生器產生隨機值
  • mt_getrandmax() - 顯示可能的最大隨機值
  • srand() - 設定亂數產生器的種子

新增註解

使用者貢獻註解 17 則註解

6
Alderin1 at gmail dot com
17 年前
我認為 Joe 對於措詞有點困惑。註解的意思是,在變更之前 mt_rand() 的實作,會產生與變更之後 mt_rand() 的實作不同的偽隨機數集,即使是相同的種子。

反正我讀起來是這樣的。
5
e at juresah dot si
4 年前
請注意,根據
https://stackoverflow.com/a/11358829/2897386

如果未提供,種子會使用目前的時間戳記自動初始化。

這表示,如果您的腳本總是在可預測的時間執行(例如使用 crontab),則會產生品質較差的隨機值。在這種情況下,最好從加密安全的來源手動初始化。
10
slonmron_no_spam_please_ at yahoo dot com
17 年前
當最低位元僅不同時,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
...

我偶爾發現這種情況。我不知道這是不是錯誤。在我的實際生活中,我並不打算使用連續的種子。不過,這可能對某些人來說很重要。
1
Nibbels / downfight.de
10 年前
如果您剛接觸種子,請閱讀我的註解。

我現在將種子理解為演算法的開始狀態。這個演算法會產生一系列 - 接下來的 - 偽隨機數。
如果您從相同的開始值開始產生兩次,您會連續得到相同的隨機數列兩次。

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

我的結論:如果您想要「交替的隨機數」,請勿一直將種子預設為相同的數字

問候
0
dev at 10e12 dot net
14 年前
這個例子如何...

(抱歉,行分隔符號有點怪,但我已經
針對這個單字換行的問題回報過一次
錯誤,但沒有效果)

打算使用它來傳遞「半保證」的
正確設定種子的隨機數給用戶端,然後
從使用者擷取輸入,必須將其加密
用戶端,然後再傳送回伺服器
a)在同一個會期中,且
b)在設定的時間限制內。

如需更多閱讀,另請參閱
用於 javaScript 的 AES Rijndael 編碼/解碼常式
由 Herbert Hanewinkel 開發和測試,
http://www.hanewin.net/encrypt/aes/aes.htm

<?php
/*
呼叫函數,準備要傳送給
客戶端的資料...,然後客戶端會在 javascript
實作的 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);
}
}

?>
0
Kapr
17 年前
給 slonmron
隨機數產生器的種子應該只在呼叫適當的 rand 函數之前初始化一次。之後,您可以透過多次呼叫 rand 來提供虛擬隨機序列。如果 1) 您有比已實作演算法更好的隨機種子來源,或 2) 如果您始終需要相同的虛擬隨機數序列,則使用隨機種子的初始化。您提供的範例只顯示第一個 rand 結果很大程度取決於種子,這根據定義是如此。這不是錯誤。
0
vbassassin at coderheaven dot com
19 年前
「更好的做法:使用 microtime() 的 31 位元雜湊作為種子。」

如果我錯了請指正我,但使用 microtime() 仍然會將總種子限制為 1,000,000 嗎?因為相同的數字的 31 位元雜湊始終會產生相同的雜湊,而在 microtime() 函數中,您可能會得到 1,000,000 或更少的數字。因此,實際上您並沒有變得更好 :-p

誠摯的問候,
scott

附註:我實際上同意 PHP 幾乎已經解決了這個問題,並且透過引入「梅森旋轉演算法」盡可能接近解決種子問題,該演算法建立的池比 1,000,000 個數字大得多。僅僅因為存在 mt_srand() 函數並不表示您「必須」使用它 ;-) 如果您「需要」相同的數字的特定清單,請使用它(對於使用密碼的加密非常方便 ;-)
0
mrcheezy at hotmail dot com
21 年前
以上關於種子的觀點非常好,謝謝。如果您想測試種子,請嘗試使用以下程式碼。它會根據您的系統花費 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';
; }
0
maxim at php dot net
22 年前
給:l_rossato@libero.it

正在執行...

list($usec,$sec)=explode(" ",microtime());
$unique = mt_srand($sec * $usec);

理論上,與

list($usec,$sec)=explode(" ",microtime());
$unique = $usec + 0;

一樣有意義。

每隔一段時間,取決於您電腦的微秒解析度,毫秒值將為零 (0),而且我希望您知道,在數學中,任何數字乘以零都會變成零。

(x * 0 = 0)

在現實生活中,在一個良好的機器上,每秒解析度為 100 萬毫秒(即:Win2k 伺服器),您將在每個發出的百萬 ID 中重複您的唯一 ID。這表示如果您將其用作 Cookie 加密演算法或訪客 ID,您將不會超過數百萬個實例。

此外,如果這是一個您重新發佈的軟體開發,安裝在一些奇怪的老舊 PC 上,解析度可能低至每秒 100 毫秒,具有此唯一性演算法的程式碼將無法持續太久。

祝您好運,
Maxim Maletsky

maxim@php.net
0
changminyang at hananet dot net
22 年前
list($usec,$sec) = explode(" ",microtime());

/* 測試:每個 get 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);

// 附註> 我之前的筆記有錯誤的行,很抱歉。這是正確的。
-1
dev at 10e12 dot net
14 年前
很抱歉之前有錯誤...
由於 wordwrap 的小故障,我很惱火,並且失去了複製貼上動作的專注力。

實際函數的最後一部分應該讀作。

<?php
// 選擇我們需要傳回的內容
if ((($retMax == 0)&&($retMax = mt_getrandmax))||
(
$retMin==$retMax)){
return
mt_rand();
}else{
return
mt_rand($retMin,$retMax);
}

?>

當然,沒有其他內容了...
-1
php dot net-comment at lucb1e dot com
10 年前
@ fasaxc at yahoo dot com

如果您想要真正隨機的數字,請使用真正隨機的來源。當您可以簡單地呼叫 openssl_random_pseudo_bytes() 來獲得良好的隨機性時,您的系統相當笨拙。請勿使用 microtime 作為隨機性的來源。
-1
fasaxc at yahoo dot com
21 年前
確保隨機種子的最佳方法是執行以下操作
首先
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 個種子,並且在多次執行中具有雪崩效應。
-1
josh at joshstrike dot com
13 年前
mt_srand 對 32 位元以上的正整數有效地執行模數 % 2147483648,但對於負整數,它會改為將 2147483648 加到它取得的值。

具有相同結果的種子
2147483649 == 1
2147483648 == 0
2147483647 == -1
-2147483646 == 2
-2147483647 == 1
-2147483648 == 0

但重要的是,以任何小於 -2147483648 的值作為種子將始終產生與以零作為種子相同的結果。
-1
fasaxc at yahoo dot co dot uk
21 年前
事實上,如果您的安裝環境提供了隨機熵守護進程,並且您正在執行 *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);
# 然後將其與一個隨機單字進行 XOR 運算
$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());

?>
-2
limo at anime42 dot com
14 年前
我花了過去幾個小時試圖追蹤一個影響 mt_rand/rand 和 mt_srand/mt_rand 的錯誤。

作業系統是 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 );
-5
simon at labs dot coop
10 年前
我再怎麼強調在程式碼中為隨機化過程設定種子值的重要性都不為過!更好的是,我們在 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++;
}
?>
To Top