C++ Random
- 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() (from <cstdlib>) is a holdover from C. Avoid it for any serious work:
- Implementation-defined quality. Some implementations have terrible statistical properties.
- Limited range.
RAND_MAXis only guaranteed to be at least 32767 — too small for many uses. rand() % nis biased unlessndividesRAND_MAX + 1.- Single global state. Not thread-safe, and seeding affects the whole program.
- Not reproducible across implementations.
Use <random> (C++11) instead.
2. Engines and Distributions
<random> separates two concerns:
- Engine — the source of raw randomness. Produces uniformly distributed integers.
- Distribution — shapes engine output into the 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 you'll usually pick from:
| 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 large amounts of randomness from random_device — it's typically backed by /dev/urandom or similar and is 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: with the same seed and the same engine, you'll get the same sequence — across platforms, even. Distributions, however, are not required to be cross-platform consistent: two implementations may map the same engine output to different real numbers. Don't rely on cross-platform reproducibility of uniform_real_distribution results.
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
<random> engines are not thread-safe. Sharing one across threads requires external synchronization, which destroys throughput.
The 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);
}
For most use cases this gives near-perfect scaling and avoids the need for any locking.