Time and Random Numbers

Seeding Random Generators

When no seed is provided to a generator, the generator is started with some fixed value, and the exact same sequence will be generated on each execution. This fixed starting value is replaced by whatever you pass it, making the output deterministic, same every time, or random, different every time.

A constantly provided seed makes the generator's output fully deterministic.

#include <iostream>
#include <random>

int main()
{
    // Use a fixed seed to make the generated sequence repeatable
    std::mt19937 eng{ 42 };
    std::cout << eng() << '\n';
    std::cout << eng() << '\n';
}

Here, the generator named eng is seeded using 42. The first value produced by the call eng() is an actual number, and the second produced by the next call to eng() is another one. Each time this program is run, it outputs the same two numbers because the seed will always be the same 42. This is useful in situations that involve reproducing a generated sequence; e.g. when testing.

A nondeterministic seed can be extracted from an std::random_device.

#include <iostream>
#include <random>

int main()
{
    std::random_device rd;
    // Initialize eng with the value returned by rd
    std::mt19937 eng{ rd() };
    std::cout << eng() << '\n';
}

The rd object produces an unsigned value which can be used to seed our eng generator. The value returned by rd() will normally vary between calls; therefore, the first number printed by eng() will be unique for every execution of the program. This is the common practice to generate a fresh sequence, for simulations, games, etc.

If one call to the std::random_device isn't considered enough source of entropy, several calls can be combined using std::seed_seq:

#include <iostream>
#include <random>

int main()
{
    std::random_device rd;
    // Store the value produced by this function call
    std::seed_seq seq{ rd(), rd(), rd() };
    std::mt19937 eng{ seq };
    std::cout << eng() << '\n';
}

Here, three separate values were obtained from rd, then supplied to a seed_seq object seq. This seq object is then used to seed the eng generator. The seed_seq internally combines the provided sequence to obtain a single, longer, more uniformly distributed value to feed to the generator. The first number is again unique, but more entropy has been provided to the generator than a simple call to rd.

A frequently made mistake is seeding using the low resolution of the system clock. Calling std::mt19937 eng{ static_cast<std::mt19937::result_type>(std::time(nullptr)) } can mean the program can be run twice in under a second, yielding identical sequences of numbers. Seeding using only the current time is more or less using std::random_device with an implementation defined, fixed seed value.

A generator default constructed and not later seeded behaves exactly like a generator created using a constant, implementation defined value. The generator uses the same deterministic sequence of numbers every time, which often isn't desired for production. Using a constant seed, or seeding from std::random_device, is the typical first step after the generator has been declared.

So, seeding controls whether our generator produces reproducible or unique output at each execution.

0 comments

No comments yet. Be the first.

Add a comment

About CppLearn

CppLearn is an interactive, free reference for modern C++. In each lesson, you'll find out how the language works, rather than just lines of code that you can copy and paste. We cover concepts starting from beginner lessons on fundamentals and proceed to templates, move semantics, smart pointers, and areas that usually trap or scare C++ learners.