The old way to get random numbers is with srand(time (NULL)) function for initialization, rand() to get the random number, and rand() % maxValue to get a random number between 0 and maxValue. But after I watched Stephan T. Lavavej’s statements on this video, the usage of simple rand() plus modulus paradigm was considered harmful and terrible. Why?
According to C++ documentation of rand(), you’ll see this..
Returns a pseudo-random integral value between 0 and RAND_MAX (0 and RAND_MAX included)
Now, see the documentation of RAND_MAX and you will see this..
This value is implementation dependent. It’s guaranteed that this value is at least 32767.
I think the 32767 number was too small for a random number distribution! Also old rand() implementation in most C/C++ compiler using Linear Congruential Generator (LCG) algorithm. Something like this in C code:
In the code above, linear congruential generators (LCG) suffer from extreme predictability in the lower bits. The n-th bit (starting from n= 0, the lowest bit) has a period of at most 2n + 1 (i.e., how long until the sequence takes to repeat). So the lowest bit has a period of just 2, the second lowest a period of 4, etc. The function above discards the lowest 16 bits, and the resulting output is at most 32767.
This is not the best generators around and not recommended for serious random-number generation needs, e.g. random usages on the game logic.
So.. What’s the Solution?
Don’t worry, as of C++11 there are much better random number generators available in C++. The C++ <random> header has some features:
- Random generator engines:
- Mersenne-twister (std::mersenne_twister_engine)
- LCG (std::linear_congruential_engine)
- Subtract with carry (std::subtract_with_carry_engine)
- Non-deterministic seed generator (std::random_device)
- Random number engine adaptors:
- Discard block (std::discard_block_engine)
- Independent bits (std::independent_bits_engine)
- Shuffle order (std::shuffle_order_engine)
- Collections of the random distribution algorithm: Uniform, Bernoulli, Poisson, Normal, Sampling.
- Predefined RNGs: minstd, mt19937, ranlux, knuth, default_random_engine.
Here is a simple example for C++11 random number generator. We will use 64-bit Mersenne Twister engine, which is usually used in most game logic. And an uniform random number distribution.
Mersenne Twister based on the prime 2 to the power of 19937–1. It’s a much higher-quality RNG than rand(), and slightly faster too (389ms to generate 100 million numbers versus 1170ms with rand()). It also produces full 64-bit unsigned outputs between 0 and 2 to the power of 64 - 1, rather than maxing out at a small number 32767.
C++11 also gives you some decent random number distributions. std::uniform_int_distribution gives you perfectly uniform numbers, without the bias of modulo — i.e. rand() % 10000 is more likely to give you a number between 0 and 999 than a number between 9000 and 9999, since 32767 is not a perfect multiple of 10000.
And last, the code seeds the random number generator using a high-precision clock. This is important for avoiding hacks specifically tailored to your code, since using a fixed seed means that anyone can determine what your RNG will output. So… randomizing using a high-resolution seed number can make your code unhackable!
Using the C++11 random library has some advantage over old rand() function, among them:
- C++ random engines like std::mersenne_twister_engine has much longer period than that of rand(), e.g. it will take its random sequence much longer to repeat itself.
- It much better statistical behavior.
- Several different random number generator engines can be initiated simultaneously with different seed, compared with the single “global” seed srand() function.
Have fun coding, and see you in the next article :)