K2A-B1-24
C++ Random
Created 2018-06-15Updated 2026-02-28programming-language-stack / c_or_cpp / language
- Description: A note on
<random>— engines, distributions, seeding, common patterns, and whyrand()is not enough - My Notion Note ID: K2A-B1-24
- Created: 2018-06-15
- Updated: 2026-02-28
- License: Reuse is very welcome. Please credit Yu Zhang and link back to the original on yuzhang.io
Table of Contents
- 1. Why Not
rand()? - 2. Engines and Distributions
- 3. Common Patterns
- 4. Seeding
- 5. Distributions Reference
- 6. Thread Safety
1. Why Not rand()?
rand()(<cstdlib>) is a C holdover. Avoid for serious work:
- Implementation-defined quality — some impls have terrible statistical properties.
- Limited range —
RAND_MAXguaranteed only ≥ 32767. Too small for many uses. rand() % nbiased unlessndividesRAND_MAX + 1.- Single global state — not thread-safe; seeding affects whole program.
- Not reproducible across implementations.
- Use
<random>(C++11).
2. Engines and Distributions
<random>separates 2 concerns:
- Engine — source of raw randomness. Produces uniformly distributed integers.
- Distribution — shapes engine output into desired distribution (uniform real, normal, Bernoulli, etc.).
#include <random>
std::random_device rd; // OS-provided entropy (slow, high-quality)
std::mt19937 gen{rd()}; // engine, seeded from rd
std::uniform_int_distribution<int> dist{1, 6}; // dice roll
int roll = dist(gen);
Engines:
| Engine | Quality | Speed | Use for |
|---|---|---|---|
std::mt19937 |
Good | Fast | General-purpose 32-bit |
std::mt19937_64 |
Good | Fast | General-purpose 64-bit |
std::random_device |
Implementation-defined; usually OS entropy (may be deterministic on some implementations) | Slow | Seeding only, not bulk generation |
std::minstd_rand |
Mediocre | Very fast | Speed-critical, low-stakes |
- Don't generate bulk randomness from
random_device— typically backed by/dev/urandom; meant for seeding.
3. Common Patterns
#include <random>
#include <vector>
#include <algorithm>
// One-shot setup (avoid reseeding per call)
static std::mt19937 gen{std::random_device{}()};
// Uniform integer in [min, max], INCLUSIVE
int dice() {
std::uniform_int_distribution<int> dist{1, 6};
return dist(gen);
}
// Uniform real in [min, max)
double pct() {
std::uniform_real_distribution<double> dist{0.0, 1.0};
return dist(gen);
}
// Pick a random element
template <typename T>
const T& pick(const std::vector<T>& v) {
std::uniform_int_distribution<size_t> dist{0, v.size() - 1};
return v[dist(gen)];
}
// Shuffle
std::vector<int> v = {1, 2, 3, 4, 5};
std::shuffle(v.begin(), v.end(), gen);
// Coin flip
std::bernoulli_distribution coin{0.5};
bool heads = coin(gen);
4. Seeding
// Seed from OS entropy (recommended)
std::mt19937 gen{std::random_device{}()};
// Seed from a fixed value (reproducible — for tests, replays)
std::mt19937 gen{42};
// Seed with a sequence of values for better state
std::random_device rd;
std::seed_seq seed{rd(), rd(), rd(), rd()}; // 128 bits of entropy
std::mt19937 gen{seed};
- Reproducibility: same seed + same engine -> same sequence (cross-platform).
- But distributions are not required to be cross-platform consistent — two impls may map same engine output to different reals. Don't rely on cross-platform
uniform_real_distributionreproducibility.
5. Distributions Reference
| Distribution | Use for |
|---|---|
uniform_int_distribution |
Dice, array indices, etc. |
uniform_real_distribution |
Continuous uniform (e.g. percentages) |
bernoulli_distribution |
Coin flip with probability p |
binomial_distribution |
Number of successes in n trials |
poisson_distribution |
Count of events in fixed time |
normal_distribution |
Gaussian (mean, stddev) |
lognormal_distribution |
Lognormal |
exponential_distribution |
Time between Poisson events |
discrete_distribution |
Weighted choice from a list |
geometric_distribution, negative_binomial_distribution |
Discrete |
gamma_distribution, weibull_distribution, student_t_distribution, chi_squared_distribution, etc. |
Statistical sampling |
6. Thread Safety
- Engines are not thread-safe. Sharing across threads -> needs sync -> kills throughput.
- Standard pattern: one engine per thread, seeded independently.
thread_local std::mt19937 tls_gen{
std::random_device{}() +
std::hash<std::thread::id>{}(std::this_thread::get_id())
};
int dice() {
std::uniform_int_distribution<int> dist{1, 6};
return dist(tls_gen);
}
- Near-perfect scaling, no locking needed for most cases.