HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMajor

Seed std::mt19937 from std::random_device

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
stdfromseedrandom_devicemt19937

Problem

Many people seed their Mersenne Twister engines like this:

std::mt19937 rng(std::random_device{}());


However, this only provides a single unsigned int, i.e. 32 bits on most systems, of seed randomness, which seems quite tiny when compared to the 19937 bit state space we want to seed. Indeed, if I find out the first number generated, my PC (Intel i7-4790K) only needs about 10 minutes to search through all 32 bit numbers and find the used seed. (I know that MT is not a cryptographic RNG, but I just did that to get a feel for how small 32 bit really is in these days.)

I am trying to build a function to properly seed a mt19937 and came up with this:

#include 
#include 
#include 

auto RandomlySeededMersenneTwister () {
    // Magic number 624: The number of unsigned ints the MT uses as state
    std::vector random_data(624);
    std::random_device source;
    std::generate(begin(random_data), end(random_data), [&](){return source();});
    std::seed_seq seeds(begin(random_data), end(random_data));
    std::mt19937 seededEngine (seeds);
    return seededEngine;
}

int main() {
    auto rng = RandomlySeededMersenneTwister();
    for (int i = 0; i < 10; ++i)
        std::cout << rng() << "\n";    
}


This does look like a safe solution to me; however, I have learned that problems with RNG are often times quite subtle.

Provided std::random_device produces good, random data on my system, does the code give me a correctly seeded std::mt19937?

Solution

-
Well, first off, why do you use a std::vector for a comparatively small sequence of known length? A raw array or std::array suffice and avoids any dynamic allocation.

-
Next, avoid needless magic numbers. Use std::mt19937::state_size instead of manually specifying 624.

-
Why do you use a lambda? A simple std::ref(source) suffices.

The seeding itself looks perfectly fine and there's no actual error anywhere in your code.

template
auto ProperlySeededRandomEngine () -> typename std::enable_if::type {
    std::random_device source;
    std::random_device::result_type random_data[(N - 1) / sizeof(source()) + 1];
    std::generate(std::begin(random_data), std::end(random_data), std::ref(source));
    std::seed_seq seeds(std::begin(random_data), std::end(random_data));
    return T(seeds);
}


You could avoid the need for random_data by using counting and transforming iterators as detailed in "Sequence iterator? Isn't there one in boost?".

This is not simpler, but maybe more efficient:

template
T ProperlySeededRandomEngine () {
    std::random_device source;
    auto make_iter = [&](std::size_t n) {
    return boost::make_transform_iterator(
        boost::counting_iterator(n), [&](size_t){return source();});
    };
    std::seed_seq seeds(make_iter(0), make_iter((N - 1) / sizeof(source()) + 1));
    return T(seeds);
}


On coliru

If you can upgrade to C++20, use ranges and views (godbolt):

template
T ProperlySeededRandomEngine () {
    std::random_device source;
    auto random_data = std::views::iota(std::size_t(), (N - 1) / sizeof(source()) + 1)
        | std::views::transform([&](auto){ return source(); });
    std::seed_seq seeds(std::begin(random_data), std::end(random_data));
    return T(seeds);
}

Code Snippets

template<class T = std::mt19937, std::size_t N = T::state_size * sizeof(typename T::result_type)>
auto ProperlySeededRandomEngine () -> typename std::enable_if<N, T>::type {
    std::random_device source;
    std::random_device::result_type random_data[(N - 1) / sizeof(source()) + 1];
    std::generate(std::begin(random_data), std::end(random_data), std::ref(source));
    std::seed_seq seeds(std::begin(random_data), std::end(random_data));
    return T(seeds);
}
template<class T = std::mt19937, std::size_t N = T::state_size * sizeof(typename T::result_type)>
T ProperlySeededRandomEngine () {
    std::random_device source;
    auto make_iter = [&](std::size_t n) {
    return boost::make_transform_iterator(
        boost::counting_iterator<std::size_t>(n), [&](size_t){return source();});
    };
    std::seed_seq seeds(make_iter(0), make_iter((N - 1) / sizeof(source()) + 1));
    return T(seeds);
}
template<class T = std::mt19937, std::size_t N = T::state_size * sizeof(typename T::result_type)>
T ProperlySeededRandomEngine () {
    std::random_device source;
    auto random_data = std::views::iota(std::size_t(), (N - 1) / sizeof(source()) + 1)
        | std::views::transform([&](auto){ return source(); });
    std::seed_seq seeds(std::begin(random_data), std::end(random_data));
    return T(seeds);
}

Context

StackExchange Code Review Q#109260, answer score: 42

Revisions (0)

No revisions yet.