The standard library has a large number of random distributions, which are supposed to be used in conjunction with some random number generator.

Which of the generators should be used in which cases, and if this is a pseudo-random generator, how can it be initialized?

  • I would like to hear the comment put minus. - VladD
  • one
    @VladD I suspect the traditional reason "user winds his karma, you have to punish him." Balanced by pluses) - Nick Volynkin

2 answers 2

Pseudo Random Number Generators

In the standard library there are three pseudo-random number generators (PRNG) and one almost (sic!) Random number generator - random_device .

linear_congruential_engine , linear congruential generator - the easiest, fastest and lowest quality. Uses the formula x[i] = x[i - 1] * A + B Equivalent to the rand() function. The internal state is one random number, completely predictable.

subtract_with_carry_engine , the late Fibonacci generator is slightly better than the linear congruent generator, has a state of several random numbers.

mersenne_twister_engine , Mersen’s whirlwind is the most qualitative output of random numbers with a practically infinite period, a large state - 624 numbers (2.5 Kb).

The recommended std::mt19937 is std::mt19937 , one of the variants of the Mersen Vortex.

All PRNG are deterministic, so in order for the issue not to be repeated, their state must be initialized with some kind of unique sequence of numbers. To do this, you can use the current time, the more precisely - the better, for example:

 auto now = std::chrono::high_resolution_clock::now(); std::mt19937 random_generator(now.time_since_epoch().count()); 

In some cases, it may be that the current time is not enough, and an additional source of entropy is needed. To do this, you can use the seed_seq utility, which aligns data from several sources of entropy:

 auto now1 = (uint64_t)std::chrono::high_resolution_clock::now().time_since_epoch().count(); uint64_t thread_id = std::hash<std::thread::id>{}(std::this_thread::get_id()); std::random_device random_device; uint64_t entropy = random_device(); auto now2 = (uint64_t)std::chrono::high_resolution_clock::now().time_since_epoch().count(); std::seed_seq seeds{now1, now2, thread_id, entropy}; std::mt19937 random_generator(seeds); 

( seed_seq accepts initializer_list<T> , so the arguments must be of the same type).

Knowing that Mersen’s Vortex has such a larger state, it may be random_device() to completely fill it with random numbers, for example, by calling random_device() 624 times. But it makes no practical sense. The goal is to get a unique sequence of random numbers, and in practice it is enough to simply use the current time.

It should be understood that none of the standard PRNG is cryptographically stable, even the issuance of the Mersen Vortex can be predicted by receiving only 624 numbers. Therefore, the use of a large initialization sequence does not provide any security advantages.

std::random_device

random_device is a random number generator, the implementation of which depends on the standard library.
Depending on the platform and implementation of the standard library, this may be:

  • A cryptographically secure system generator of truly random numbers, incl.
    • rdrand processor rdrand (libc ++)
    • read /dev/random or /dev/urandom (libstdc ++)
    • call RtlGenRandom() on Windows (vc ++)
  • ordinary PRNG which always gives the same numbers (MinGW)

Moreover, if the implementation uses /dev/(u)random and this file cannot be opened, an exception will be thrown.

Also reading /dev/random can block the work of the program until the moment when the system has accumulated enough entropy.

Therefore, std::random_device should be used only on those systems and compilers that are known to work there as RNG, and be prepared for the fact that this function is very slow compared to PRNG.

( random_device has an entropy() function, but it is useless, because on many platforms it always returns 0 even if RNG is used and not PRNG.)

Distributions

Classes of distributions use the output of random number generators to convert it into a sequence having the desired distribution.

Some classes of distributions, for example, the normal distribution and its derivatives, have an internal state ; therefore, it is not necessary to create a new distribution object for each random number.

 std::mt19937 generator; std::normal_distribution<double> distribution(0, 1); std::vector<double> data; for (auto i = 0; i != 100; ++i) { data.push_back(distribution(generator)); // OK } for (auto i = 0; i != 100; ++i) { // ПЛОХО, не используется состояние распределения data.push_back(std::normal_distribution<double>(3, 1)(generator)); } 

However, even if you create a new distribution for each number, the shape of the distribution changes slightly.

Examples

Replacing code from ::rand() from <cstdlib>

 ::srand(::time(0)); for (int i = 0; i != n; i++) std::cout << ::rand() << '\0'; 

will either use mt19937

 std::mt19937 rand(::time(0)); // Лучше использовать high_resolution_clock for (int i = 0; i != n; i++) std::cout << rand() << '\0'; 

or using random_device (this is normal for programs of the "Hello World" level)

 std::random_device rand; for (int i = 0; i != n; i++) std::cout << rand() << '\0'; 

A more complex example with a functor-generator of random numbers:

 #include <iostream> #include <random> #include <cstdint> #include <chrono> #include <thread> #include <utility> struct GaussGenerator { GaussGenerator(double mean, double stddev, std::uint32_t seed) : engine_(seed), distribution_(mean, stddev) {} GaussGenerator(double mean, double stddev) : distribution_(mean, stddev) { using namespace std; seed_seq seeds{ (uint64_t)chrono::high_resolution_clock::now().time_since_epoch().count(), (uint64_t)chrono::system_clock::now().time_since_epoch().count(), (uint64_t)hash<thread::id>{}(this_thread::get_id()), }; engine_.seed(seeds); } double operator()() { return distribution_(engine_); } std::mt19937 engine_; std::normal_distribution<double> distribution_; }; int main() { GaussGenerator rand(0, 1); for (int i = 0; i != 5; ++i) std::cout << rand() << '\n'; } 
  • Comments are not intended for extended discussion; conversation moved to chat . - Nicolas Chabanovsky

Apparently, the increasing complexity of the software does not give rest to many people and they (to the best of their ability) are trying to cope with it.

A brief search on the network of PRNG implementations bore fruit - PCG, A Family of Better Random Number Generators .

They write about their shortcomings that

  • New

  • Although it is less trivial to predict than many mainstream generators, it doesn’t have to be considered crypographically secure. The PCG family is unknown at this time.

Naturally, following the link you can download ready-made libraries and documentation for several languages.

And here is a sample code of one of the generators:

 // *Really* minimal PCG32 code / (c) 2014 ME O'Neill / pcg-random.org // Licensed under Apache License 2.0 (NO WARRANTY, etc. see website) typedef struct { uint64_t state; uint64_t inc; } pcg32_random_t; uint32_t pcg32_random_r(pcg32_random_t* rng) { uint64_t oldstate = rng->state; // Advance internal state rng->state = oldstate * 6364136223846793005ULL + (rng->inc|1); // Calculate output function (XSH RR), uses old state for max ILP uint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u; uint32_t rot = oldstate >> 59u; return (xorshifted >> rot) | (xorshifted << ((-rot) & 31)); } 
  • <random> is also a ready-made library, and no need to download. It is clear that you can write the generator yourself, especially linear congruent, as here. But why, if there is ready. - Abyx
  • @Abyx, and this is already ready, only completely new (15th year link to github ). What for? And what if it is better than the old? - avp
  • this is the usual linear congruential generator plus a weak hash function. The quality of the LKG is known - it is bad. I am not a mathematician, but it seems that adding a hash function should not significantly change the situation. However, even if it is really better than the other generators, in practice, no matter which one to use. - Abyx 2:49 PM