What is PHP's mt_rand() minimum value and how

2019-06-26 04:11发布

问题:

What is the minimum value allowed for mt_rand()? Is it the same value for 32 bit and 64 bit machines? How could I generate a 32 bit integer using mt_rand() (note that it doesn't need to be highly random)?

BACKGROUND WHY I AM ASKING: I have a 64 bit development physical server and a 32 bit production VPS. Just realized the production server was not generating PKs spanning the full range. To figure out what is going on, I ran the following script. The 64 bit machine never (or at least I've never witnessed) matches, but the 32 bit matches about 50% of the time.

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

output

mt_getrandmax()=2147483647 count=5034

回答1:

TL;DR: To get a random integer in the full range of possible integers, use:

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

For PHP 7, you can use random_int().


Under the hood (1, 2), PHP is doing this:

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

Notice $max - $min. When max is set to the top end and min is anything negative, an overflow occurs. Therefore, the maximum range is PHP_INT_MAX. If your maximum value is PHP_INT_MAX, then your minimum is necessarily 0.

Now for the back story. PHP implements the 32-bit Mersenne Twister algorithm. This gives us random integers between [0, and 2^31-1). If you ask for any other range, PHP scales that number using a simple binning function. That binning function includes a subtraction that can lead to overflow, and this problem.

Thus if you want to get a range larger than could be represented by an integer in PHP, you have to add intervals together, like so:

mt_rand(PHP_INT_MIN, -1) + mt_rand(0, PHP_INT_MAX);

Note that PHP_INT_MIN is available since PHP 7, so you'll need to calculate a suitable minimum for your environment before then.


As an aside, notice that 2^31-1 is what getrandmax() returns. People mistakenly believe that on a 64-bit machine getrandmax() will return 2^63-1. That's not true. getrandmax() returns the maximum integer the algorithm will return, which is always 2^31-1.



回答2:

You can generate a 32 bit integer like this:

$rand = unpack("l", openssl_random_pseudo_bytes(4));


回答3:

This is a problem that is noted in the PHP docs over here

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

A bug report is added here.

There has been no word whether PHP is fixing this yet.

In the meantime you can use the mt_rand function between the max_rand and you should be fine

Example Usage

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


标签: php linux random