C++ Random


  • Description: A note on <random> — engines, distributions, seeding, common patterns, and why rand() 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()?

  • rand() (<cstdlib>) is a C holdover. Avoid for serious work:
  1. Implementation-defined quality — some impls have terrible statistical properties.
  2. Limited rangeRAND_MAX guaranteed only ≥ 32767. Too small for many uses.
  3. rand() % n biased unless n divides RAND_MAX + 1.
  4. Single global state — not thread-safe; seeding affects whole program.
  5. Not reproducible across implementations.
  • Use <random> (C++11).

2. Engines and Distributions

  • <random> separates 2 concerns:
  1. Engine — source of raw randomness. Produces uniformly distributed integers.
  2. 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_distribution reproducibility.

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.