Random Number Generation Random Numbers Random
Basic Philosophy
#include <random>
random_engine_type engine {seed};
distribution_type distribution {parameters,…};
auto random_value = distribution(engine);
Sources of randomness are decoupled from distributions
- random numbers are produced by distributions
- distributions use
uniform random bit engines
as sources of randomness
Advantages of this design
- no single global state, several independent random engines possible
- new distribution types can make use of existing engines
- can change randomness sources while keeping distribution types (e.g., change a deterministic engine for one that uses hardware entropy)
Examples
#include <random>
#include <iostream>
int main () {
// fixed seed
auto const seed = 123;
// Mersenne Twister random engine:
std::mt19937 urbg {seed};
// generate random ints ∈ [1,6]
std::uniform_int_distribution<int> distr1 {1, 6};
auto const value1 = distr1(urbg);
auto const value2 = distr1(urbg);
std::cout << "value1: " << value1 << '\n';
std::cout << "value2: " << value2 << '\n';
// generate random floats ∈ [-1.2,6.25)
std::uniform_real_distribution<float> distr2 {-1.2f, 6.25f};
auto const value3 = distr2(urbg);
…
std::cout << "value3: " << value3 << '\n';
}
Boolean Values ("Coin Flip") Boolean Values Boolean
#include <random>
#include <iostream>
int main () {
auto const seed = 123;
auto urbg = std::mt19937 {seed};
// unfair coin (40% 'true'):
double const p = 0.4;
auto flip = std::bernoulli_distribution{p};
if (flip(urbg)) // 40% chance
std::cout << "heads\n";
else // 60% chance
std::cout << "tails\n";
}
#include <random>
#include <iostream>
int main () {
auto const seed = 123;
auto urbg = std::mt19937 {seed};
double const mu = 4.0;
double const sigma = 0.7;
auto norm = std::normal_distribution<double>{mu,sigma};
auto value = norm(urbg);
std::cout << "value: " << value << '\n';
}
Integers With Individual Probabilities Discrete Distribution Discrete
#include <random>
#include <iostream>
int main () {
auto const seed = std::random_device{}();
auto urbg = std::mt19937{seed};
std::vector<double> ws {1.0, 1.5, 0.5, 2.0};
std::discrete_distribution<int> distr {begin(ws), end(ws)};
std::vector<int> histo (ws.size(), 0);
int const N = 100000;
for (int k = 0; k < N; ++k) {
auto const i = distr(urbg);
++histo[i];
}
std::cout << "Histogram:\n";
for (auto x : histo) {
auto const size = int(30 * x/double(N));
std::cout << std::string(size,'-') << "o\n";
}
}
Histogram:
------o
--------o
---o
-----------o
Seed
- with an integer of type
engine_type::result_type
- or with a seed sequence
- either in the constructor:
engine_type { seed }
- or with member function
.seed( seed };
#include <random>
#include <chrono> // clocks
#include <iostream>
int main () {
auto e = std::mt19937{};
// seed engine with a constant
e.seed(123);
// … or with system clock ticks
auto const ticks = std::chrono::system_clock::now().time_since_epoch().count();
e.seed(ticks);
// … or with hardware entropy
auto const hes = std::random_device{}();
e.seed(hes);
// … or with a seed sequence
std::seed_seq s {1,5,3,7,0,9};
e.seed(s);
auto distr = std::uniform_real_distribution{-11.0, 15.3};
std::cout << distr(e) << '\n';
}
#include <random>
#include <iostream>
int main () {
auto const seed = std::random_device{}();
auto coin_flip = [
// init-capture engine + distribution:
urbg = std::mt19937{seed},
distr = std::bernoulli_distribution{0.5}
]() mutable -> bool { return distr(urbg); };
// use generator:
std::cout << coin_flip() << '\n';
auto roll = [
urbg = std::mt19937{seed},
distr = std::uniform_int_distribution<int>{1,6}
]() mutable -> int { return distr(urbg); };
std::cout << roll() << '\n';
}
#include <random>
#include <iostream>
class DiceRoll {
using engine_type = std::mt19937;
// engine + distribution as members:
engine_type urbg_;
std::uniform_int_distribution<int> distr_;
public:
using seed_type = engine_type::result_type;
// constructor:
explicit
DiceRoll (int sides, seed_type seed = 0) noexcept:
urbg_{seed}, distr_{1,sides} {}
// allows to re-seed
void seed (seed_type s) noexcept { urbg_.seed(s); }
// call operator:
int operator () () noexcept { return distr_(urbg_); }
};
int main () {
auto const seed = std::random_device{}();
DiceRoll roll_d20 {20, seed};
std::cout << roll_d20() << '\n';
}
#include <vector>
#include <iostream>
#include <algorithm>
#include <random>
int main () {
// 32 bit mersenne twister engine
auto const seed = std::random_device{}();
auto reng = std::mt19937{seed};
std::vector<int> v {0,1,2,3,4,5,6,7,8};
shuffle(begin(v)+2, begin(v)+7, reng);
for (int x : v) { std::cout << x <<' '; } // 0 1 … 7 8
std::cout << '\n';
}
#include <vector>
#include <iostream>
#include <algorithm>
#include <random>
int main () {
// 32 bit mersenne twister engine
auto const seed = std::random_device{}();
auto reng = std::mt19937{seed};
std::vector<int> v {2,3,4,5,6};
std::ranges::shuffle(v, reng);
for (int x : v) { std::cout << x <<' '; }
std::cout << '\n';
}
Distribution Types Overview Distributions Distributions
Interface
Construction
distribution_type distr; // with default params
distribution_type distr { parameter_object };
distribution_type distr { parameter1, parameter2,… parameterN };
Generating Values
auto random_value = distribution_object(engine_object);
Common Accessors
distr
.min
()
→ smallest obtainable valuedistr
.max
()
→ largest obtainable valuedistr
.param
()
→ parameter objectdistr
.reset
()
: reset internal state
Parameter Objects
distribution_type::param_type pars { parameter1, parameter2,… parameterN };
distribution_type distr1 { pars };
distribution_type distr2 { pars };
distribution_type distr3 { distr1.param() };
Distribution-Specific Parameter Accessors
distr
.a()
.b()
.m()
.n()
.s()
.alpha()
.beta()
.lambda()
.mean()
.stddev()
…
Uniform
uniform_int_distribution<IntegerType> {a=0,b=∞}
uniform_real_distribution<RealType> {a=0,b=1}
Sampling
discrete_distribution<IntegerType>
piecewise_constant_distribution<RealType>
piecewise_linear_distribution<RealType>
Bernoulli
bernoulli_distribution {p=0.5}
binomial_distribution<IntegerType> {t=1,p=0.5}
negative_binomial_distribution<IntegerType> {k=1,p=0.5}
geometric_distribution<IntegerType> {p=0.5}
Normal
normal_distribution<RealType> {μ=0,σ=1}
lognormal_distribution<RealType> {m=0,s=1}
chi_squared_distribution<RealType> {n=1}
cauchy_distribution<RealType> {a=0,b=1}
fisher_f_distribution<RealType> {m=1,n=1}
student_t_distribution<RealType> {n=1}
Poissson
poisson_distribution<IntegerType> {μ=1}
exponential_distribution<RealType> {λ=1}
gamma_distribution<RealType> {α=1,β=1}
weibull_distribution<RealType> {a=1,b=1}
extreme_value_distribution<RealType> {a=0,b=1}
Engine Types Overview Engines Engines
Common Engine Interface
Construction |
engine_type eng;
engine_type eng { IntegerSeed };
engine_type eng { SeedSequence }; |
Seeding |
eng.seed(IntegerSeed);
eng.seed(SeedSequence); |
Advance Internal State |
eng.discard(steps); |
Result & Seed Type |
engine_type::result_type |
std::minstd_rand0 // 1969 by Lewis, Goodman, Miller
std::minstd_rand // 1993 by Park, Miller, Stockmeyer
std::mt19937 // 32-bit, Matsumoto and Nishimura, 1998
std::mt19937_64 // 64-bit, Matsumoto and Nishimura, 2000
Adaptors
Engines based on adaptors:
std::ranlux24 // discard_block_engine
std::ranlux48 // discard_block_engine
std::knuth_b // shuffle_order_engine
depends on compiler/platform; often a linear congruential engine
represents a non-deterministic random number generator that e.g., uses a hardware entropy source.
Standard library implementations are allowed to use a pseudo-random
number engine as random_device
if there is no non-deterministic
entropy source available.
Test If Device Is Truly Non-Deterministic
std::random_device rd;
bool non_deterministic = rd.entropy() > 0;
bool deterministic = rd.entropy() == 0;
auto distr = std::uniform_real_distribution{-1.0,1.0};
auto num = distr(rd);
Some (older) standard library implementations return 0
despite its random_device
beeing non-deterministic.
Comments…