PHP Random Numbers

Chapter 15 12 mins

Learning outcomes:

  1. What are pseudo-random numbers
  2. The rand() function
  3. The mt_rand() function
  4. Seeding via srand() or mt_srand()
  5. Random float in the range [0, 1)

Introduction

It's difficult to think about modern-day computing without the idea of random numbers. Random numbers are used in an array or varying computer disciplines such as games, simulations, randomized trials, even some randomized algorithms, and so on.

In this chapter, we aim to explore all the details of random numbers in PHP. Yes, that right — all of them.

In particular, we'll see what is a pseudo-random number in general, and the rand() and mt_rand() functions used to generate a new random number. We'll also explore the idea of manual seeding via the srand() and mt_srand() functions and finally how to obtain a random value in the range [0, 1) using rand() and a bit of arithmetic.

Let's begin.

What are pseudo-random nmbers?

Random numbers generated by computers fall in two categories: pseudo-random numbers and cryptographically-secure random numbers.

Right now, we are concerned with pseudo-random numbers.

Let's start by defining what is it.

A pseudo-random number is a random number generated by a computer.

As the term implies, a 'pseudo-random' number is not truly random.

In particular, pseudo-random numbers generated by computers form a sequence whereby consecutive elements don't follow any pattern (i.e. they're random), however, there is a period to the whole sequence after which the entire sequence merely repeats.

In other words, pseudo-random numbers are random but predictable. And that's exactly why they're called pseudo-random.

An algorithm that generates a pseudo-random number is called a pseudo-random number generator, or a PRNG in short.

Almost all programming languages today provide PRNGs to obtain pseudo-random numbers easily. In all concerns but cryptography and other sophisticated areas, these ordinary PRNGs provide sufficient amounts of randomness.

These days, one of the most common PRNG algorithms used in almost all mainstream programming languages is the Mersenne Twister algorithm, invented by Makoto Matsumoto and Takuji Nishimura in 1997.

How exactly it works is beyond the scope of this course, but to summarize it, it's made in such a way so as to give a very good distribution of random numbers and keep the period sufficiently large.

Once again, remember that PRNGs don't generate truly random numbers and likewise should NOT be used in such sophisticated areas as cryptography.

The opposite of a pseudo-random number is a cryptographically-secure random number. Sometimes, it's also known as a cryptographically-secure pseudo-random number.

It's generated using even more complex math than used in an ordinary PRNG in order to yield a truly random sequence of numbers with an enormously huge period.

As the name suggests, such numbers are used in cryptography so that it's near impossible for any hacker to break into a system by any kind of a bruteforce approach.

The rand() function

The rand() function is used to generate a random integer in PHP.

Here's its signature:

rand();
rand($min, $max);

It can be called with no arguments at all, or with exactly two arguments.

If rand() is called without any arguments, a random integer between 0 and getrandmax() is returned.

The getrandmax() function returns the maximum random integer that could be returned by rand(). It's typically -2147483647.

On the other hand, if rand() is called with two arguments $min and $max, a random integer between those two integers (both inclusive) is returned.

First, let's experiment with the rand() function without any arguments:

<?php

echo rand(), "\n";
echo rand(), "\n";
echo rand(), "\n";
1195515822 1316549381 1929456481

The output in your case would obviously be different than this.

Now, let's experiment with the function as called with two integer arguments:

<?php

echo rand(1, 10), "\n";
echo rand(1, 10), "\n";
echo rand(1, 10), "\n";
1 10 3

Note that the random integer returned in this second case might be any of the provided integers to rand(), as is in our case in the first two outputs 1 and 10. This is because they both are included in the range of the generated random number.

The first $min argument to rand() could even be negative, as shown below:

<?php

echo rand(-50, 50), "\n";
echo rand(-50, 50), "\n";
echo rand(-50, 50), "\n";
25 -12 -8

Moving on, if the range of the desired number is greater than the range 0getrandmax(), then low-quality random numbers are returned. Low-quality means that the generated numbers won't be well-distributed, because certain numbers in such huge ranges can't ever be obtained.

This is simply a mathematical characteristic of scaling a random integer between 0 and getrandmax() to obtain an integer in the a given range.

To boil it down, always make sure that the range of the desired random integer is within the limits 0 and getrandmax().

We are talking about the range of the random integer here, not the minimum and maximum possible. Hence, we can call something like rand(-1 * getrandmax(), 0) or even rand(-2 * getrandmax(), -1 * getrandmax()). In both of these cases, the minimum is clearly not 0, but the range (i.e $max - $min) is within the range getrandmax() - 0.

The mt_rand() function

If we explore a little into the documentation of PHP, we see another function called mt_rand() that also allows us to get a random number in the same range 0getrandmax().

So then what's so special about mt_rand()? How does it differ from rand()?

Well, the reality is that by PHP 7.1.0, rand() and mt_rand() both generate a random number using the exact same implementation. In fact, as the documentation says, rand() is an alias of mt_rand().

This behavior wasn't there in PHP since the very advent of mt_rand() into the standard collection of global functions. That is, rand() generated a random number using a different approach which was slow and not as high-quality as the Mersenne Twister PRNG function mt_rand().

It was with PHP 7.1.0 that rand() was made to use mt_rand() internally.

Note, however, that rand() and mt_rand() aren't identical to one another as far as their operation is concerned. rand() allows the $max argument to be less than $min in order to preserve backwards compatibility with old PHP code. mt_rand() on the other hand, returns false in such a case.

Hence, from a usage perspective, there is really no difference between rand() and mt_rand(), given that we keep $max greater than or equal to $min.

There would be absolutely no difference if we use one or the other to generate a simple random number. Throughout this course, we'll use rand() instead of mt_rand() simply due to its short name.

Anyhow, now when you see mt_rand() in any code snippet out there, at least you'd know what it does.

Seeding a PRNG

A pseudo-random number generator has to be fed a starting value in order to begin computation of a new random integer.

This initiation step is called seeding. The initial value supplied is likewise called the seed.

Back in the day, programmers in PHP had to manually seed the PRNG by calling srand() (or mt_rand()) and providing it a given value. However, this requirement is not necessary now — it's automatically done inside rand() (or mt_rand()) if seeding hasn't been performed previously.

But if we want to start with a particular seed, there's nothing wrong with manually calling srand().

In the code below, we do exactly this:

<?php

srand(100)
echo rand();
1166953220

srand(100) seeds the PRNG used by PHP with the value 100. This value is used to compute the first random number returned by rand() (or mt_rand()), which in this case is 1166953220, after which every subsequent random integer is computed using the last value.

Calling rand() after srand() simply returns a random number based on the provided seed. If the seed is the same on multiple occasions, then the whole set of subsequent rand() calls would give the same set of random integers in both occasions as well.

In the code below, we demonstrate this idea:

<?php

srand(100);
echo rand(), "\n";
echo rand(), "\n";
echo rand(), "\n";

echo "\n";

srand(100);
echo rand(), "\n";
echo rand(), "\n";
echo rand(), "\n";
1166953220 1441295756 597793697 1166953220 1441295756 597793697

srand(100) is called twice followed by three calls to rand(). Notice the set of output generated by each of the two sets of statements here — they are both the same which confirms the fact that the same seed would always result in the same random sequence of integers.

As with rand() which is (almost) an alias of mt_rand(), the srand() function is an alias of mt_srand(). Call one or the other, it doesn't matter.

Now in almost all cases, we shouldn't be concerned with manually seeding the PRNG via srand() (or mt_srand()) unless and until we are extremely sure on what we are trying to achieve with a manual seed.

Ideally, seeding should be left to PHP to do all by itself, based on the timestamp at the time when the rand() call is made.

Random float in the range [0, 1)

So given the rand() function, how do we go on to obtain a floating-point number in the range [0, 1) i.e. greater than or equal to 0 and less than 1?

Well, it's superbly easy.

What we need to do is simply divide the random number that we get by calling rand() by getrandmax() + 1.

The maximum random could be getrandmax(), so to get a number that's less than 1 we add 1 to getrandmax() before putting it in the denominator of the division.

In the code below, we create a simple function random_float() that returns a random float from the set [0, 1):

<?php

function random_float() {
   return rand() / (getrandmax() + 1);
}

echo random_float(), "\n";
echo random_float(), "\n";
echo random_float(), "\n";
echo random_float(), "\n";
echo random_float(), "\n";
0.041215567849576 0.68517297459766 0.099202818237245 0.037415781989694 0.3945257011801

Superb.