Php – Why would rand() return a negative value when min and max values are positive

PHPrandom

I have a simple piece of PHP code which requires a random number to be created. However, even though the input is always positive, it sometimes returns a negative output.

Here's my debug code:

$debt = rand($this->gdp * 0.02, $this->gdp * 0.17);
echo "<p>GDP: ".$this->gdp." rand(".$this->gdp * 0.02."  , ".$this->gdp * 0.17.") = <strong>".$debt."</strong></p>";

Here's an example output:

GDP: 219254674605 rand(4385093492.1 , 37273294682.85) = 75276999

GDP: 345015694865 rand(6900313897.3 , 58652668127.05) = -1636353016

GDP: 90445390920 rand(1808907818.4 , 15375716456.4) = -165604705

GDP: 3412849650 rand(68256993 , 580184440.5) = 347516196

GDP: 2939111315 rand(58782226.3 , 499648923.55) = 119181875

GDP: 26369065 rand(527381.3 , 4482741.05) = 3632416

GDP: 215838135 rand(4316762.7 , 36692482.95) = 28784811

GDP: 511763530 rand(10235270.6 , 86999800.1) = 39954394

GDP: 42416245 rand(848324.9 , 7210761.65) = 3974882

GDP: 75090235 rand(1501804.7 , 12765339.95) = 5201966

So why would a rand() of two positive numbers give a negative return?

Any help would be much appreciated!

Best Answer

Because you're seeing an integer overflow in the arguments.

According to the rand() documentation it takes two int values as arguments. On a 32-bit machine those are 32 bit as well (at least for PHP). So when you pass arguments larger than 231 − 1 they overflow and start at −231 again.

Apparently, if you need larger values you'll have to code it yourself. While simply creating a 64-bit number from two 32-bit numbers works as intended, you can't simply do a modulo operation with your maximum value since that skews the distribution. For a good implementation how to generate a uniformly-distributed random integer between 0 and a certain upper bound you can take a look at java.util.Random.nextInt(int) and adapt accordingly for 64 bit integers.

mt_rand() while usually a nice choice for random numbers because it uses MT19937 instead of a poor LCG, doesn't help here either as its arguments are ints as well.


Another option you might want to consider if you don't require every possible value to be picked eventually:

  • Generate a random floating-point value between 0 and 1 by calling

    $rnd = mt_rand() / (double)mt_getrandmax()
    
  • Determine the range of numbers you need:

    $min = $this->gdp * 0.02;
    $max = $this->gdp * 0.17;
    $range = $max - $min;
    
  • Multiply this by the previously-obtained random floating-point value and add the minimum:

    $value = $min + $range * $rnd
    
  • Now you have a random value between your chosen boundaries. It's approximately uniformly distributed, although there are discrete steps between adjacent random numbers as you are stretching 32 bits of randomness over a larger number of bits. If that's no problem for you, go ahead, though.

Related Topic