mt_srand

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

mt_srand播下一个更好的随机数发生器种子

说明

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

seed 来给随机数发生器播种。 没有设定 seed 参数时,会被设为随时数。

注意: 不再需要用 srand()mt_srand() 给随机数发生器播种,因为现在是由系统自动完成的。

警告

由于 Mt19937(“梅森旋转算法”)引擎仅接受 32 位整数作为种子,因此尽管 Mt19937 的范围为 219937-1,但可能的随机序列数量仅限于 232(即 4,294,967,296)。

当依赖隐式或显式随机播种时,重复会出现得更早。根据生日问题,在少于 80,000 个随机生成的种子后,预计重复种子的概率为 50%。在随机生成大约 30,000 个种子后,重复种子的概率为 10%。

This makes Mt19937 unsuitable for applications where duplicated sequences must not happen with more than a negligible probability. 如果需要可重复的种子,Random\Engine\Xoshiro256StarStarRandom\Engine\PcgOneseq128XslRr64 引擎都支持更大的种子,它们不太可能随机碰撞。如果不需要再现性,Random\Engine\Secure 引擎提供加密安全随机性。

参数

seed

用线性同余生成器生成的值填充状态,该生成器使用解释为无符号 32 位整数的 seed 进行播种。

如果省略 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 现在可为 null。
7.1.0 srand() 成为 mt_srand() 的别名。
7.1.0 mt_rand() 成为使用梅森旋转(Mersenne Twister)算法的固定、正确版本。要使用旧行为,请使用 mt_srand() 并将 MT_RAND_PHP 作为第二个参数。

参见

  • mt_rand() - 通过梅森旋转(Mersenne Twister)随机数生成器生成随机值
  • mt_getrandmax() - 显示随机数的最大可能值
  • srand() - 播下随机数发生器种子

add a note add a note

User Contributed Notes 18 notes

up
6
e at juresah dot si
4 years ago
Please note that according to:
https://stackoverflow.com/a/11358829/2897386

The seed is automatically initialised with the current timestamp if not provided.

This means that your script will produce values of poor random quality if it's always run at a predictable time, for example by crontab. In that case it may be a good idea to initialise it manually from a cryptographically secure source.
up
6
Alderin1 at gmail dot com
16 years ago
I think Joe was a little confused by the wording.  The note meant that implementations of mt_rand() before the change would generate a different set of pseudorandom numbers than would implementations of mt_rand() after the change for the same seed.

That's how it reads for me, anyway.
up
10
slonmron_no_spam_please_ at yahoo dot com
17 years ago
Looks like mt_rand() gives same result for different seeds when the lowest bits are different only. Try this:

#!/usr/bin/php -q
<?php

$min
= -17;
$max = $min + 48; // 48 is to fit the results in my console

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);
}

?>

This is a snapshop of the results:
...
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
...

I found this occationally. I have no idea if it is a bug or not. In my real life I do not intend to use sequentional seeds. However, probably this may be important for somebody.
up
1
mrcheezy at hotmail dot com
21 years ago
Very good points above on seeds, thank you. If you would like to test a seed try using the code below. It will take between 5 and 20 seconds depending on your system and then will spit out the number of reused keys out of 100,000 attempts.

;  for ($i=0; $i<100000; $i++) {
;    mt_srand(hexdec(substr(md5(microtime()), -8)) & 0x7fffffff);
;    $rand = mt_rand();
;
;    ($arr[$rand] == '1') ? $k++ : $arr[$rand] = '1';
;  }
up
1
Nibbels / downfight.de
10 years ago
If you are new with seeding read my note.

I now understood seeding as a start-state of an algorithm. This algorithm generates a series of -following- pseudorandom numbers.
If you start generating from the same startvalue twice, you get the same series of random numbers twice in a row.

mt_srand(10); //start of your algorithm equals seeding set to 10
for($i=0;$i<10;$i++){
    echo mt_rand();
}

echo "<BR>";

mt_srand(10); //start of your algorithm equals seeding set back to 10
for($i=0;$i<10;$i++){
    echo mt_rand();
}

Output is like:
502355954641584702211262118810740890731360749216120791137454651988317865160461082451610903986200
<BR>
502355954641584702211262118810740890731360749216120791137454651988317865160461082451610903986200

My conclusion: Don't preset your seed to the same number all the time if you want "alternating random numbers"

Greetings
up
0
dev at 10e12 dot net
14 years ago
What about this for an example...

(sorry for the funky line breaks but I have once
more reported the bug on this issue of word
wrapping to no avail)

Intending to use it in passing a "semi-guaranteed"
properly seeded random number to a client, then to
capture input from the user which must be encrypted
client side before being sent to the server again
  a) during the same session and,
  b) within a set time limit.

for more reading also see:
  AES Rijndael enc/dec routines for javaScript
  developed and tested by Herbert Hanewinkel,
  http://www.hanewin.net/encrypt/aes/aes.htm

<?php
/*
calling the function, prepping the data to send to
the client... , who then uses it in a javascript
implementation of AES Encrypt.
*/

function SHA256($str, $keyval=""){
    if (
$keyval!==""){//not null or empty
       
$sHash = mhash(Constant('MHASH_SHA256'),$str, $keyval);
    }else{
       
$sHash = mhash(Constant('MHASH_SHA256'),$str);
    }
   
// same as Binary 2 Hex conversion
   
return implode(unpack('H*',$sHash),'');
}

/*---------------------------------------------------
Radomizing the random data with fixed data for
the user and location If return value maximum
is set to zero (0) the function uses mt_getrandmax
If both retMin and retMax is zero (0) the function
uses mt_rand w.o. limits
*----------------------------------------------------*/
function local_prgn($retMin = 0, $retMax = 0){
// first get the session id of the request session
 
$sSrv = session_id();

// the following is valid only on the current server
 
$sSrv = implode(unpack($_SERVER['SERVER_NAME'].
            
$_SERVER['SERVER_ADDR'].$sSrv),'');

// the following applies only to the requesting client
 
$sReq = implode(unpack($_SERVER['REMOTE_ADDR'].
            
$_SERVER['REQUEST_TIME']),'');

// get a SHA256 seed based on the current values
 
$sSeed = SHA256($sSrv,$sReq);

// get a random value based on the uniqueness of the seed above
   
mt_srand($sSeed); // seed the random num gen

// some error handling and checking
   
if ($retMin > $retMax){
   
// swap vars if wrong order
       
$rx = $retMax; $retMax = $retMin; $retMin = $rx;

    } else if (
$retMin == $retMax){
   
//meaningless range, not very random
    //ensure for next check in this range
       
$retMin = 0; $retMax = 0;
    }
// select what we need to return
   
if ((($retMax == 0)&&($retMax = mt_getrandmax))||
(
$retMin==$retMax)){
        return
mt_getrandmax();
    }else{
        return
mt_getrandmax($retMin,$retMax);
    }
}

?>
up
0
Kapr
17 years ago
To slonmron:
Seed for random numbers generator should be initialized only once, before calling proper rand function. After that you give pseudorandom sequence by multiple calling rand. Initialization of random seed is used if 1) You have better source of random seed than implemented algorithm or 2) if You need always the same sequence of pseudorandom numbers. Example given by You shows only that first rand result strongly depends on seed, what is by definition. It is not a bug.
up
0
vbassassin at coderheaven dot com
18 years ago
"Better still: Use a 31-bit hash of microtime() as the seed. "

Correct me if i am wrong, but woudlnt using microtime() still limit the total seeds to 1,000,000 again? Since the 31-bit hash will always give the same hash for the same number, and in the microtime() function you could have 1,000,000 or less numbers. So in effect your still no better off at all :-p

Best regards,
scott

PS: I actually agree that PHP has pretty much resolved the issue and got as close as anyones going to get to solving the seeding issue by introducing the "Mersenne Twister" algorithm which creates a much larger pool than 1,000,000 numbers. Just because the mt_srand() function exists doesnt mean you HAVE to use it ;-) use it if you NEED a specific list of the same numbers (comes in handy for encryptions with passwords ;-)
up
0
maxim at php dot net
22 years ago
to : l_rossato@libero.it

doing ...

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

theoretiaclly, makes just as much sense as

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

Once every while, depending on the microsecond resolution of your computer, the millisecond value will be a zero (0), and as I hope you know, in mathematics, any number multiplied by a zero becomes a zero itself.

(x * 0 = 0)

In real life, on a good machine, with a resolution to 1 million miliseconds per each second (i.e: Win2k server), you will be reduplicating your unique ID each million's ID issued. This means if you use it as your cookie encryption algorithm or a visitor ID, you will not exceed some million instances.

Futhermore, if that would be for a software development that you re-distribuite, installed on some weird old PC, where resolution can be as small as 100 milliseconds per second - a code with this uniqueness algorithm just wouldn't last any long.

Good Luck,

Maxim Maletsky
maxim@php.net

PHPBeginner.com
up
0
changminyang at hananet dot net
22 years ago
list($usec,$sec) = explode(" ",microtime());

/* Test: Each get rand sequence are 10time. */
/* ex) 5.3point meaning 5point integer + 3point decimal */

// case A:
// 5.0point - 1time
// 6.0point - 9time
$rand = (double)microtime()*1000000;

// case B:
// 8.6point - 1time
// 9.4point - 1time
// 9.5point - 7time
// 10.3point - 1time
$rand = (double)$sec * $usec;

// My case A:
// 8.0point - 10time
$rand = explode(".",$usec * $sec);
$rand = (double)substr($rand[0]*$rand[1],0,8);

// My case B:
// 9.0point - 9time
// 10.0point - 1time
$rand = explode(".",$usec * $sec);
$rand = $rand[0] + $rand[1];

mt_srand($rand);
srand($rand);

// P.S> My previous note is has wrong lines, sorry about it.  This is right.
up
-1
dev at 10e12 dot net
14 years ago
Sorry for the error in the previous...
Due to the glitch with the wordwrap I got annoyed and lost focus on the copy and paste move.

The last part of the actual function should read.

<?php
// select what we need to return
   
if ((($retMax == 0)&&($retMax = mt_getrandmax))||
(
$retMin==$retMax)){
        return
mt_rand();
    }else{
        return
mt_rand($retMin,$retMax);
    }

?>

And Nothing else of course...
up
-1
php dot net-comment at lucb1e dot com
10 years ago
@  fasaxc at yahoo dot com:

If you want truly random numbers, use a truly random source. Your system is rather unwieldy when you can simply call openssl_random_pseudo_bytes() for good randomness. Don't use microtime as a source of randomness.
up
-1
fasaxc at yahoo dot com
21 years ago
The best way to ensure a random seed is to do the following:
To start:
   1) get your initial seed with mt_srand(microtime() * 1000000)
   2) generate a random no. $random=mt_rand()
   3) save this number in a file (or database or whatever so that it is available next time the page is loaded)

Now, for each time your script is loaded :
   1) load the value you saved above and do $new_seed=($random+(microtime() * 1000000))%pow(2,32)
   2) mt_srand($new_seed);
   3) generate a new random no. $random=mt_rand()
   4) save that number back in the file/database

This procedure takes advantage not only of the randomness of microtime() but of all the previous calls to microtime() so your seed becomes better and better with time. It also generates good seeds even on platforms where microtime() doesn't take all the values it can.

Just using microtime() * 1000000 only results in 1000000 possible seeds (and less on some platforms as noted) - the function above gives 2^32 seeds with an avelanche effect accross multiple executions.
up
-1
josh at joshstrike dot com
13 years ago
mt_srand effectively performs a modulo % 2147483648 on positive integers over 32 bits, but with negative integers it instead adds 2147483648 to the value it gets.

Seeds with equal results:
2147483649 == 1
2147483648 == 0
2147483647 == -1
-2147483646 == 2
-2147483647 == 1
-2147483648 == 0

Importantly though, seeding with anything less than -2147483648 will always yield the same result as seeding with zero.
up
-1
fasaxc at yahoo dot co dot uk
21 years ago
In fact, here's an even better function than the one below assuming your install provides a random entropy daemon and you're running *nix (to check for the former type "head -c 6 /dev/urandom" on the command line if available - if you get 6 random characters you're set). N.B. php must be able to find the head program so it must be in your path and allowed if you're running safe mode.

The functions db_set_global() and db_get_global() I use to set/get a variable from a central database but you could save/restore the variable from a file instead or just use the function get_random_word().

<?

####################################
## returns a random 32bit integer.
## Passing a parameter of True gives a better random
## number but relies on the /dev/random device
## which can block for a long time while it gathers
## enough random data ie. DONT USE IT unless
##   a) You have an entropy generator attatched to
## your computer set to /dev/random -OR-
##   b) Your script is running locally and generating
## a good random number is very important
####################################
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;
}

--
EITHER - IF YOU'VE SET UP A DATABASE OF GLOBAL VARIABLES--

## If the seed is found in the database
if ($seed=db_get_global('
seed')) {
    # use mt_rand() to get the next seed
    mt_srand($seed);
    # then XOR that with a random word
    $seed=(mt_rand() ^ get_random_word());
} else {
## Make a completely new seed (First Run)
    # Generate the seed as a proper random no using /dev/random
    $seed=get_random_word(True);
    mt_srand($seed);
}

db_set_global('
seed',$seed);

--OR JUST--
mt_srand(get_random_word());

?>
up
-2
limo at anime42 dot com
14 years ago
I have spent the last couple of hours trying to track down a bug which affects mt_rand/rand and mt_srand/mt_rand.

OS is Debian 5.0.4 "Lenny".
PHP version is 5.3.2-0.dotdeb.1 with Suhosin-Patch (cli) (built: Mar  9 2010 11:42:01).

I have tried to fix the issue by appending the following lines into the .htaccess / apache2 main config file:

        php_value suhosin.mt_srand.ignore Off
        php_value suhosin.srand.ignore Off

This has helped a bit, stabilizing the beggining of the pseudo random number sequence, but the generator still fails after a fair number of iterations (roughly around 1K~3K.

*** Removing the Suhosin extension has resolved this issue, I am waiting for an official extension build that will work with 5.3.x so that I can reattach it into the php configuration. ***

Here is the code which ought to replicate the problem:

    $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( 'Pseudo-random sequence borked at #'.$i.'th iteration!');

    echo 'Your pseudo-random sequencer is working correctly.';
    exit( 0 );
up
-5
simon at labs dot coop
10 years ago
I can’t stress how important it is to seed your randomisation process in code! better still something we found in the BBS Days was if we didn’t seed from a token from outside our systems abstraction layer we would go in circles and so would our users. Here at chronolabs we offer a feed of randomly changing token on each impression, it also randomly displays a different number of them this is from http://seed.feeds.labs.coop in the example below I use DOM to load the XML, Extract the randomisation tokens and then with mt_srand and srand seed the random selecting processes! The following function when you call it will seed your random selection process in both the old and new random selection routines all you need to do is call the function! This will work with any version of PHP 5 and any earlier with DOM Objectivity.

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)]);
}

Remember when PHP says an integer this also include any character of the Ascii chart if you would like to see an example of this do the following:

<?php
   $a
= "000A";
   while(
$a!="001B") {
      echo
$a;
     
$a++;
   }
?>
up
-3
oxai NOSPAM post dot htnet dot hr
18 years ago
try this instead(!):

<?php

// randomizes MT's seed once per process.

function randomizeProcessSeed()
{
    static
$thisProcessHasBeenInitialized;

    if(
$thisProcessHasBeenInitialized ) return;

    list(
$usec, $sec) = explode(' ', microtime());
   
mt_srand( (10000000000 * (float)$usec) ^ (float)$sec );
   
   
$thisProcessHasBeenInitialized = true;
}
randomizeProcessSeed();
?>
To Top