Search code examples
c++randomdistribution

c++ - deterministic alternative to std::uniform_XXX_distribution with mt19937?


I need a way to get deterministic sequences of ints and doubles.

template <class U>
constexpr auto get_random_value (std::mt19937 &gen, U min_value, U max_value)->U
{
    if constexpr ( std::is_same_v <U, double> or std::is_same_v <U, float> ){
        std::uniform_real_distribution <U> distrib( min_value, max_value );
 
        return distrib( gen );
    }
    else if constexpr ( std::is_same_v <U, u32> or std::is_same_v <U, i32> ){
        std::uniform_int_distribution distrib( min_value, max_value );

        return distrib( gen );
    }
    else {
        throw std::runtime_error( "error value type" );
    }
}

My issue is that one day to another, the same seeded value will lead to different results. The distribution is to blame because it goes a long way to avoid the pitfall of the modulo.

But I need a precise way to always be certain that a sequence will always be the same starting from a given seed. And I need an unbiased partition (so % and rand() are out).

What kind of implementation will guarantee this?


Solution

  • The distributions in the C++ standard are not portable ("seed-stable"), in the sense that the result can change between different implementations (e.g. Microsoft STL vs gcc libstdc++ vs clang libc++) or even different versions (e.g. Microsoft changed their implementation before). The standard simply does not prescribe a specific algorithm, with the intention to allow implementations to select the one with the best performance for each platform. So far, there is only a proposal (D2059R0) to do something about this. Note, however, that the generators are actually portable.

    I have yet to see a library that guarantees portability. However, in practice boost.random is known to produce reproducible result across platforms (see e.g. here or here or here). Also, Google's abseil library explicitly states that they do not provide a stability guarantee, but seems to produce the same result on different platforms nevertheless. Here is a live example on godbolt where you can see that to some extent (well, at least Linux vs Windows for a tiny selection of parameters). The major point is not to update the libraries without checking for a breaking change. Also compare e.g. this blog post. Or you could also implement a specific algorithm yourself (see e.g. here) or simply copy the code from one the libraries to your code base and thereby effectively freezing its version.

    For distributions involving floating point arithmetic, you also have the problem that the arithmetic itself is, generally speaking, far from stable across platforms since stuff like automatic vectorization (SSE2 vs AVX etc) or FPU settings might change behavior. See this blog post for more information. How far distributions, including the above mentioned libraries, are affected by this, I unfortunately do not know. The small example on godbolt mentioned above does at least not show any problem with -ffast-math, which is a good sign.

    Whatever you do, I highly recommend to back up your choice by appropriate automatic tests (unit tests etc.) that catch any potential deviations in behavior.