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
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.
You can generate a 32 bit integer like this:
$rand = unpack("l", openssl_random_pseudo_bytes(4));
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);