Math 函数
在线手册:中文 英文
PHP手册

mt_srand

(PHP 4, PHP 5)

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

说明

void mt_srand ( int $seed )

seed 来给随机数发生器播种。从 PHP 4.2.0 版开始,seed 参数变为可选项,当该项为空时,会被设为随时数。

Example #1 mt_srand() 范例

<?php
// seed with microseconds
function make_seed()
{
    list(
$usec$sec) = explode(' 'microtime());
    return (float) 
$sec + ((float) $usec 100000);
}
mt_srand(make_seed());
$randval mt_rand();
?>

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

参见 mt_rand()mt_getrandmax()srand()

参数

seed

An optional seed value

返回值

没有返回值。

更新日志

版本 说明
Since 4.2.0 The seed becomes optional and defaults to a random value if omitted.
Since 5.2.1 The Mersenne Twister implementation in PHP now uses a new seeding algorithm by Richard Wagner. Identical seeds no longer produce the same sequence of values they did in previous versions. This behavior is not expected to change again, but it is considered unsafe to rely upon it nonetheless.

范例

Example #2 mt_srand() example

<?php
// seed with microseconds
function make_seed()
{
  list(
$usec$sec) = explode(' 'microtime());
  return (float) 
$sec + ((float) $usec 100000);
}
mt_srand(make_seed());
$randval mt_rand();
?>

参见


Math 函数
在线手册:中文 英文
PHP手册
PHP手册 - N: 播下一个更好的随机数发生器种子

用户评论:

richard at damon-family dot org (31-Mar-2012 08:01)

I think the meaning of

"---
Since 5.2.1
Mersenne Twister in PHP now use new algorythm of reinitializing, developed by Richard Wagner
Same seeds not produce same chains of values.
---"

Is that the sequence you get out of version 5.2.1 or greater is different than the sequence you got from the versions prior to that for a given seed, i.e. something changed in the pseudo-random number generator to change the sequences.

fwrk at bk dot ru (15-Feb-2012 11:19)

God bless my PHP 5.3.6, there is NO such strange behavior like this:

"---
Since 5.2.1
Mersenne Twister in PHP now use new algorythm of reinitializing, developed by Richard Wagner
Same seeds not produce same chains of values.
---"

O_\

One's more: God bless my PHP 5.3.6, there is NO such strange behavior!
Same seeds produce SAME chains of values.
And it MUST be so!
M.T. IS PSEUDO random number generator. It is useful feature.

If we need truly random we will initialize our web-applications by mt_srand(Microtemperature_of_CPU)

josh at joshstrike dot com (25-Oct-2011 02:29)

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.

limo at anime42 dot com (14-Apr-2010 03:33)

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

dev at 10e12 dot net (23-Mar-2010 12:44)

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...

dev at 10e12 dot net (23-Mar-2010 11:56)

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

?>

Alderin1 at gmail dot com (20-Nov-2007 06:55)

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.

Hayley Watson (13-Nov-2007 11:53)

I don't know what Joe is referring to, but the following code never seems to output 'FOO!' however many times I run it:

<?php
$trials
= 1000;
$trial_length = 500;
$seed = rand(); // Not mt_rand!
$s = array_fill(0, $trials, '');
for(
$j=0; $j<$trials; ++$j)
{
   
mt_srand($seed); // Same seed => same results (?)
   
for($i=0; $i<$trial_length; ++$i)
    {
       
$s[$j] .= chr(mt_rand()); // Exploit the fact that chr($n)==chr($n%256)
   
}
}
for(
$j=0; $j<$trials; ++$j)
{
    for(
$i=$j+1; $i<$trials; ++$i)
    {
        if(
$s[$i]!=$s[$j]) echo "FOO! Different random strings generated from the same seed!\n";
    }
}
$sample = rand(0,$trials-1);
echo
0,"\t",base64_encode($s[0]),"\n";
echo
$sample,"\t",base64_encode($s[$sample]);
?>

Joe (03-Sep-2007 12:59)

In some cases it is necessary to be able to create same sequence of pseudo-random numbers. E.g. to run repeatable simulation run with different parameters. Since php 5.2.1 it is not possible even using same seed in mt_srand function. For such cases use srand instead.

Kapr (14-Jun-2007 06:25)

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.

slonmron_no_spam_please_ at yahoo dot com (16-Mar-2007 07:44)

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.

oxai NOSPAM post dot htnet dot hr (09-Sep-2006 07:14)

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();
?>

gigs (12-Jan-2006 07:13)

used the little script from mrcheezy at hotmail dot com and got much better results using

mt_srand(crc32(microtime()));

vbassassin at coderheaven dot com (01-Dec-2005 04:57)

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

(12-Sep-2005 02:03)

It's better to use the following method instead of the one in the documentation metioned:

<?php
mt_srand
((double)(microtime() ^ posix_getpid()));
?>

Otherwise people requesting the script at the same time could get the same generated number.

Schrecktech (21-Jan-2005 02:16)

::: My seeding report :::

For FreeBSD 4.8-STABLE PHP 4.3.10 it was good enough and faster to not seed at all...although I did not test the random pseudo terminal. 

In detail only seeding once by a previous seed using the hex version (case 3 below) for larger numbers and longer run times faired the best.  For small numbers it was better to not seed at all (let alone faster).  Play with the $loop and $max numbers to see...

<?
function get_a_good_seed
(
    $choice = 3
)
{
    static $seed;
    $seed = 0;
    switch($choice)
    {
        case 1:
            // from php.net documentation.
            list($usec, $sec) = explode(' ', microtime());
            $seed = (float) $sec + ((float) $usec * 1000000);
            break;
        case 2:
            // from high primary number.
            $seed = (double)microtime()*1000003;
            break;
        case 3:
            // from a good random source.
            $seed = hexdec(substr(md5(microtime()), -8)) & 0x7fffffff;
            break;
        case 4:
            // from random terminal
            // NOT TESTED
            break;
        default:
            break;
    }
    return($seed);
}
function load_last_random
(
)
{
    static $last;
    static $handle;
    // NOTE: create this file with a random integer...
    $handle = fopen("random.txt", "r");
    while (!feof($handle)) {
        $last = fgets($handle, 4096);
    }
    fclose($handle);
    return($last);
}

function save_last_random
(
    $save = 0
)
{
    $handle = fopen("random.txt", "w");
    fwrite($handle, $save);
    fclose($handle);
}

function seed_random_number_generator
(
    $type = 3,
    $ignore_generated_already = FALSE
)
{
    static $generator_seeded = FALSE;
    if($ignore_generated_already || !$generator_seeded)
    {
        // 1) load last with time...force integer.
        // 2) generate a new random seed and save for next time...
        mt_srand(( load_last_random()+get_a_good_seed($type) ) & 0x7fffffff);
        save_last_random(mt_rand());
        $generator_seeded= TRUE;
    }
}
?>

fasaxc at yahoo dot co dot uk (25-Jul-2003 04:01)

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

?>

fasaxc at yahoo dot com (18-Jul-2003 12:14)

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.

mrcheezy at hotmail dot com (17-Feb-2003 10:14)

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

mattb at columbia dot edu (12-Dec-2002 08:54)

This is effectively the same thing, but it uses a more efficient bit mask instead of a substring. Here's the verbose version:

    function mkseed()
    {
        $hash = md5(microtime());
        $loWord = substr($hash, -8);
        $seed = hexdec($loWord);
        $seed &= 0x7fffffff;

        return $seed;
    }

Or, if you're a real hacker, this is a more concise version:

    function mkseed()
    {
        return hexdec(substr(md5(microtime()), -8)) & 0x7fffffff;
    }

Note, the range on the md5 sum is really arbitrary. You could use whatever portion you like, just so long as it's 32 bits.

Now just call mt_srand() (once) as follows:

    mt_srand(mkseed());

Of course, unless you wanted the mkseed() function for something else, you could skip all that jive and just do this somewhere in a global include file:

    <?php

   
/*
        Global include file. Make sure this gets included before using
        mt_rand().
    */

   
if (!isset($_MyApp_MtSrand)
        || !
$_MyApp_MtSrand)
    {
       
$_MyApp_MtSrand = true;
       
mt_srand(hexdec(substr(md5(microtime()), -8)) & 0x7fffffff);
    }

   
?>

ghaecker_no_spam at idworld dot net (21-Sep-2002 09:20)

The range of unique seeds using this method is a bit over 2 billion.  This approach also prevents re-seeding.

function seed_mt_rand() {
static $done;
if (!$done) {
$hash = md5(microtime());
$length = ((substr($hash,0,1) < '8') ? 8 : 7 );
mt_srand((int)base_convert(substr($hash,0,$length),16,10));
$done = TRUE;
}
}

maxim at php dot net (13-Jun-2002 10:57)

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

changminyang at hananet dot net (20-Feb-2002 10:00)

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.

l_rossato at libero dot it (12-Nov-2001 04:47)

Try this much simpler approach:
list($usec,$sec)=explode(" ",microtime());
mt_srand($sec * $usec);

This won't cause an overflow during the float-to-integer conversion.
Of course, if you only need ONE random number just take $sec * $usec! Otherwise, call mt_srand just once at the start of your program.

roundeye_no_spam at roundeye dot net (04-Nov-2001 01:23)

Since I've seen this problem in so many other manual comments I'm writing a note here.  Basically, calling mt_srand() every time you call mt_rand() defeats the whole purpose of a generator like the Mersenne Twister, and will greatly reduce the quality of the number stream you produce.  For cryptographic or security purposes this is A Bad Thing.
<br>
DON'T do this:
<br>
function bad_pick_random_number($min, $max) {
  mt_srand();
  return mt_rand($min, $max);
}
<br>
Instead, call mt_srand() *once* early in the execution of your program and pick your numbers separately after that:
<br>
function good_pick_random_number($min, $max) {
   return mt_rand($min, $max);
}
<br>
Of course, if you're doing that sort of thing you might as well just call mt_rand() directly.

trobinson-a-t-gksystems-d-o-t-com (02-Nov-2001 10:15)

I see a problem with seeding with:
mt_srand((double)microtime()*1000000);
Too few seeds.
This chooses the seed from a space of only one million seeds. This is a shame, because the Mersenne Twister algorithm has a period of 2**19937-1.
So of all the possible starting points in the MT sequence, the above seed only chooses at random from among 1000000 starting points, or about 0.000...0001% of the possible starting points, where "..." is a string of about 5986 more zeros!
In real life, this means that if you note the first few random numbers, when you see the same first few numbers again, the chances are very good that you have the same seed and the rest of the sequence will be the same too.

If you generate a random "unique" 100-character key for say cookie use, seeding as above, you will likely get a duplicate key after only 1000 keys or so.
Better: To supplement the results from mt_rand, also hash in the entire contents of the microtime() string.
Better still: Use a 31-bit hash of microtime() as the seed.

Problem 2. Way too few seeds on some hardware. It is bad to rely on a clock ticking every microsecond. I have used computers where 100ms was the finest-resolution clock available. That would be just 10 different seeds, as the results from (double)microtime() would look like:
0.10000000
0.20000000
etc.

php_public at macfreek dot nl (07-Oct-2001 04:57)

The seed is not good. The make_seed() function returns a big number (around 1.0E14), this number is bigger then 2147483647, the max. size of an integer. If the result of make_seed is first converted to an integer, which -on my setup- always becomes -2147483648. So, the above code always returns the same seed and I always end up with the same random numbers.

Note: This might be related to a bug noted on http://www.php.net/manual/en/language.types.integer.php. But I couldn't confirm this, since it will be fixed in PHP 4.0.7, but that isn't released yet at the time of writing)

I was also not able to fix it by return the seed modulo 2147483647.
So for now, I just suggest change the seed function as:

function make_seed() {
    return (double)microtime()*1000000;
}

See also: uniqid()