Random number generation

Continuing with our emphasis on learning the standard library components of C++, we shall now look at functions and classes that provide us a means to generate random numbers.

Before C++11 (and it's successor C++14) became widely adopted, the standard way to do this was to use the srand and rand functions from <cstdlib>. You may be familiar with this older method, but I will not discuss it any further here because it uses standard library components from an older and cruder programming language. You should become accustomed to using the newer method especially designed for the C++ of the 21st century.

The modern way of generating random numbers in C++ is to use the components found in <random>. Here is the simplest way to generate a random integer:

#include <iostream> #include <random> using namespace std; int main() { cout << random_device{}() << endl; return 0; }

The data type random_device represents a non-deterministic random integer generator. The expression random_device{} creates a value of that data type, then the function call operator (denoted by the empty parenthesis) generates a random integer from this value.

The code in main has the same effect as the following:

random_device rd; cout << rd() << endl;

For the purposes of this example, storing an instance of random_device in a variable is not needed since we are only using it once.

The random_device data type is not usually used by itself, but used in conjuction with pseudo-random number generators and random number distributions.

Here is a program that displays a random integer in the range of 1 to 20:

#include <iostream> #include <random> using namespace std; int main() { default_random_engine prng(random_device{}()); uniform_int_distribution<int> idist(1, 20); cout << idist(prng) << endl; return 0; }

default_random_engine represents a pseudo-random number generator (PRNG). A PRNG generates numbers that seem random but are actually deterministic. Every PRNG must be initialized with a "seed" which is just an integer value. From the initial seed, a PRNG will recursively transform this value to give you the next number from the deterministic number sequence.

The reason we use a PRNG instead of random_device is because PRNGs have faster performance. For many operating systems, random_device is associated with a special file named /dev/random which is where data from environmental noise is accumulated into. Not only is reading from a file slower than performing a simple calculation, but /dev/random can be exausted when too much data is extracted from it, in which case reading from it will block until more data becomes available.

Therefore, the statement default_random_engine prng(random_device{}()); creates a PRNG variable initialized with a single seed value taken from an instance of random_device. Other than the way you initialize it, default_random_engine can be used the same way you would use a random_device: the function call operator generates the next random integer.

The RNGs and PRNGs in C++ only generate random integers from 0 to approximately 4.2 billion. To generate random numbers in a specific range we use a PRNG or RNG together with a random number distribution.

uniform_int_distribution distributes an integer generator's output over a uniform, closed interval. Such a data type is initialized with the minimum and maximum values of the interval and the function call operator will generate a value within this interval from a given integer generator.

Creating a default_random_engine should only happen once in your program, then you may use it multiple times for different random number distributions. Here is a program that generates 5 random integers from -100 to 100 and 5 random real numbers from -20.0 to 0.5:

#include <iostream> #include <random> using namespace std; int main() { default_random_engine prng(random_device{}()); uniform_int_distribution<int> idist(-100, 100); uniform_real_distribution<double> ddist(-20.0, 0.5); for (int i = 0; i < 5; ++i) cout << idist(prng) << ' '; cout << endl; for (int i = 0; i < 5; ++i) cout << ddist(prng) << ' '; cout << endl; return 0; }

uniform_int_distribution and uniform_real_distribution distributes random data into an integer range or a floating-point range respectively. The reason we must specify int or double to qualify these data types is because C++ supports different variations of integers and floating-point data types which you may look into on your own if you're interested.

random_device, default_random_engine, uniform_int_distribution, and uniform_real_distribution should be everything you need to start generating random numbers. If you look into what else <random> has to offer you will see that it has other types of PRNGs other than default_random_engine and other types of random number distributions that are more mathematically biased than the uniform distributions.