Random Number Generation

The production of random numbers has always been important for computers applications. Neverthless, the C++ STL has historically provided a rather limited support adopted from the C standard library:

  • RAND_MAX, a macro that expands to an integer constant;
  • std::rand(), a function that produces a pseudo-random number in the closed interval [0, RAND_MAX];
  • std::srand(), a function to initialize (seed) a new sequence of such numbers.

The random functions introduced with C++11 improved enormously the way to generate random numbers in C++. Unfortunatly, the procedure necessary to perform such task is not straightforward and requires a little bit of knowledge. The generation of random numbers involves two different objects:

  • an engine returning unpredictable numbers, so that for each bit the likelihood of getting 0 is always the same as the likelihood of getting a 1;

  • a distribution transforming the sequence of numbers generated by an engine in order to fit a certain random variable distribution.

Given an engine variable e and a distribution variable d, each call d(e) produces a random number according to the distribution. In other words, the engine variable provides a source of randomness and the distribution variable shapes this source to fit the distribution. The following code generates a random integer according to uniform distribution in order to implement a simple function that simulates a roulette wheels:

  

#include <random>
  
int rouletteWheel ( )
{
   std::default_random_engine e{};
   std::uniform_int_distribution<int> d{0, 36};
   return d(e);
}

Every engine shall be always initialized through a value that is the starting point (or seed) of the generated sequences. In the example above the engine was default-initialized, but it is also possible to provide an explicit starting value (a seed) to its constructor:

 
   std::default_random_engine e{121181};

When a program is run multiple times, an engine will always produce the same sequence if it is always initialized with the same seed. Even if this behaviour can be very useful while debugging a program, in many contexts it is undesireable and problematic. The best way to avoid replicated sequences is to use an object of type random_device to obtain a seed:

 
std::random_device         rdev{};
std::default_random_engine e{rdev()};

All the above exampes uses default_random_engine as engine. This is an alias for an engine type selected by the library vendor, so code that uses this alias is not guarantee to generate identical sequences across all implementations. In addition to the default_random_engine, the library provides nine additional aliases for pre-configured engine. For all these engines, the C++11 standard requires that they produce identical results across all implementations. Among them, an engine that produces good results is the Mersenne twister engine mt19937. Anyway, explaining the differences between such engines goes far behond the purpose of this post.

Comments