(Non-)Reproducible Random Number Sequences Reproducible Random Numbers Random
Reminder: Standard Library Paradigm
Sources of randomness are decoupled from distributions
- distribution objects produce random numbers
- uniform random bit engine objects act as sources of randomness
#include <random>
random_engine_type engine {seed};
distribution_type distribution {parameters,…};
auto random_value = distribution(engine);
Reproducible
Background
Given the same initial state, two pseudo random bit engine objects of the same type will produce exactly the same sequence of random bits.
Two distribution objects of the same type will produce the same sequence of random numbers if
- their initial state is the same (e.g., both default constructed or identical copies),
- the engines feeding them random bits are of the same type
- the engines were initialized the same, e.g., seeded identically
- the engines have performed the same number of operations
Unfortunately, there is no guarantee that a standard distribution type produces the same random number sequence on different platforms, i.e., using different standard library versions or implementations.
Seed
Seed Engine On Construction
std::mt19937_64 eng (123);
The integer seed type engine_type::result_type
is usually
an unsigned integer type.
using seed_type = std::mt19937_64::result_type;
seed_type const seed = 123;
std::mt19937_64 eng {seed};
Seed An Already Existing Engine
…
eng.seed(47);
Unpredictable
Non-Deterministic ⇒ std::random_device
std::random_device
random_device
std::random_device
represents a non-deterministic random number generator that e.g.,
uses a hardware entropy source.
std::random_device rd;
std::uniform_real_distribution<double> distr {-1.0, 1.0};
auto rndNum = distr(rd);
Test If Device Is Truly Non-Deterministic
Standard library implementations are allowed to use a pseudo-random
number engine as random_device
if there is no non-deterministic
entropy source available.
std::random_device rd;
bool non_deterministic = rd.entropy() > 0;
bool deterministic = rd.entropy() == 0;
Some (older) standard library implementations return 0
,
despite beeing non-deterministic.
Deterministic ⇒ Seed With Clock Seed With Clock Clock
Seed Engine On Construction
#include <chrono> // clocks
auto const ticks = std::chrono::system_clock::now().time_since_epoch().count();
std::mt19937_64 eng (ticks);
Seed An Already Existing Engine
#include <chrono> // clocks
…
auto const ticks = std::chrono::system_clock::now().time_since_epoch().count();
eng.seed(ticks);
This is still deterministic: given the same number of clock ticks as initial seed, the sequence of random numbers obtained via a pseudo-random engine of a particular type will always be the same.
The type used for clock ticks is usually a signed integer while the engine seed type is usually an unsigned integer.
Example
- custom function class that models n-sided dice
- takes integer seed as constructor argument
- here: seeded with system clock
#include <random>
#include <vector>
#include <iostream>
#include <algorithm>
#include <chrono>
template<int SIDES, typename Engine = std::mt19937_64>
class Dice {
using engine_type = Engine;
std::uniform_int_distribution<int> distr_;
engine_type urbg_;
public:
using seed_type = typename engine_type::result_type;
Dice() = default;
explicit
Dice(seed_type seed): distr_{1,SIDES}, urbg_{seed} {}
int operator () () { return distr_(urbg_); }
};
int main () {
// make D20 and seed with clock:
auto const ticks = std::chrono::system_clock::now(). time_since_epoch().count();
Dice<20> d20 (ticks);
// generate sequence of dice rolls:
std::vector<int> rolls (10);
std::generate(begin(rolls), end(rolls), d20);
// print results:
for (int x : rolls) std:: cout << x <<' ';
std::cout << '\n';
}
Comments…