C++ random numbers | Guide to the C++11 random library

C++ random numbers | Guide to the C++11 random library

이 글의 핵심

Hands-on guide to modern C++ random: engines, distributions, seeding, reproducibility, and thread-safety basics with runnable examples.

Problems with rand()

// ❌ Legacy style
srand(time(0));
int x = rand() % 100;  // 0-99

// Issues:
// 1. Not a uniform distribution
// 2. Low statistical quality
// 3. Not thread-safe

Modern random numbers (C++11)

#include <random>

int main() {
    // Seed
    random_device rd;
    
    // Engine
    mt19937 gen(rd());
    
    // Distribution
    uniform_int_distribution<> dis(1, 100);
    
    // Generate
    for (int i = 0; i < 10; i++) {
        cout << dis(gen) << " ";
    }
}

Random engines

// Mersenne Twister (recommended)
mt19937 gen32;      // 32-bit
mt19937_64 gen64;   // 64-bit

// Linear congruential (fast, lower quality)
minstd_rand gen;

// Subtract-with-carry
ranlux24 gen;

Distributions

Uniform

// Integers
uniform_int_distribution<> intDis(1, 6);  // dice
int dice = intDis(gen);

// Reals
uniform_real_distribution<> realDis(0.0, 1.0);
double x = realDis(gen);

Normal

normal_distribution<> normalDis(100.0, 15.0);  // mean 100, stddev 15
double iq = normalDis(gen);

Other distributions

// Bernoulli (true/false)
bernoulli_distribution coinFlip(0.5);  // 50%
bool result = coinFlip(gen);

// Binomial
binomial_distribution<> binDis(10, 0.5);
int heads = binDis(gen);

// Poisson
poisson_distribution<> poisDis(4.0);
int events = poisDis(gen);

// Exponential
exponential_distribution<> expDis(1.0);
double time = expDis(gen);

Practical examples

Example 1: Dice histogram

#include <random>
#include <map>

int main() {
    random_device rd;
    mt19937 gen(rd());
    uniform_int_distribution<> dice(1, 6);
    
    map<int, int> histogram;
    
    // Roll 10000 times
    for (int i = 0; i < 10000; i++) {
        int roll = dice(gen);
        histogram[roll]++;
    }
    
    // Print histogram
    for (const auto& [value, count] : histogram) {
        cout << value << ": " << string(count / 100, '*') << endl;
    }
}

Example 2: Random string

string generateRandomString(size_t length) {
    static random_device rd;
    static mt19937 gen(rd());
    static uniform_int_distribution<> dis(0, 61);
    
    const string chars = 
        "0123456789"
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        "abcdefghijklmnopqrstuvwxyz";
    
    string result;
    for (size_t i = 0; i < length; i++) {
        result += chars[dis(gen)];
    }
    
    return result;
}

int main() {
    cout << generateRandomString(10) << endl;
    cout << generateRandomString(20) << endl;
}

Example 3: Shuffle

#include <algorithm>

int main() {
    vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    
    random_device rd;
    mt19937 gen(rd());
    
    shuffle(v.begin(), v.end(), gen);
    
    for (int x : v) {
        cout << x << " ";
    }
}

Example 4: Weighted discrete choice

#include <random>

int main() {
    random_device rd;
    mt19937 gen(rd());
    
    // Weights: 10%, 30%, 60%
    discrete_distribution<> dis({10, 30, 60});
    
    map<int, int> histogram;
    
    for (int i = 0; i < 10000; i++) {
        int choice = dis(gen);
        histogram[choice]++;
    }
    
    for (const auto& [choice, count] : histogram) {
        cout << "choice " << choice << ": " << count << " times" << endl;
    }
}

Seeding

// Time-based (not reproducible)
mt19937 gen1(time(0));

// random_device (recommended for non-fixed seeds)
random_device rd;
mt19937 gen2(rd());

// Fixed seed (reproducible)
mt19937 gen3(12345);

// Seed sequence
seed_seq seq{1, 2, 3, 4, 5};
mt19937 gen4(seq);

Common pitfalls

Pitfall 1: Creating an engine every call

// ❌ Inefficient
int getRandom() {
    random_device rd;
    mt19937 gen(rd());  // created every time (slow)
    uniform_int_distribution<> dis(1, 100);
    return dis(gen);
}

// ✅ static storage
int getRandom() {
    static random_device rd;
    static mt19937 gen(rd());
    static uniform_int_distribution<> dis(1, 100);
    return dis(gen);
}

Pitfall 2: Reseeding inside a loop

// ❌ Same short sequence each iteration
for (int i = 0; i < 10; i++) {
    mt19937 gen(12345);  // always the same seed
    cout << gen() << endl;
}

// ✅ Reuse one engine
mt19937 gen(12345);
for (int i = 0; i < 10; i++) {
    cout << gen() << endl;
}

Pitfall 3: Biased ranges with rand()

// ❌ Biased
int x = rand() % 100;  // not uniform

// ✅ Uniform distribution
uniform_int_distribution<> dis(0, 99);
int x = dis(gen);

Performance comparison

#include <chrono>

int main() {
    const int N = 10000000;
    
    // rand()
    srand(time(0));
    auto start = chrono::high_resolution_clock::now();
    for (int i = 0; i < N; i++) {
        int x = rand();
    }
    auto end = chrono::high_resolution_clock::now();
    cout << "rand(): " << chrono::duration_cast<chrono::milliseconds>(end - start).count() << "ms" << endl;
    
    // mt19937
    random_device rd;
    mt19937 gen(rd());
    start = chrono::high_resolution_clock::now();
    for (int i = 0; i < N; i++) {
        int x = gen();
    }
    end = chrono::high_resolution_clock::now();
    cout << "mt19937: " << chrono::duration_cast<chrono::milliseconds>(end - start).count() << "ms" << endl;
}

FAQ

Q1: rand() vs the random library?

A:

  • rand(): legacy, poor quality.
  • random: modern, higher quality, more flexible.

Q2: Must I always use random_device?

A: Use it for seeding. Generate numbers with an engine.

Q3: Which engine should I use?

A: In most cases mt19937 is a solid default.

Q4: How do I get reproducible sequences?

A: Use a fixed seed.

Q5: Is it thread-safe?

A: Give each thread its own engine (and distribution if needed).

Q6: Learning resources?

A:

  • cppreference.com
  • The C++ Standard Library (Nicolai Josuttis)
  • Effective Modern C++

  • C++ Random | overview of engines and distributions
  • C++ Distribution | probability distributions
  • C++ random_device | hardware seeding

Practical tips

Debugging

  • Fix compiler warnings first.
  • Reproduce with a small test case.

Performance

  • Do not optimize without profiling.
  • Define measurable goals first.

Code review

  • Catch common review issues early.
  • Follow team conventions.

Production checklist

Before coding

  • Is this the right approach?
  • Can teammates maintain it?
  • Does it meet performance needs?

While coding

  • Warnings cleared?
  • Edge cases covered?
  • Error handling adequate?

At review

  • Intent clear?
  • Tests sufficient?
  • Documentation where needed?

Keywords

C++, random, C++11, mt19937, uniform distribution


  • C++ Distribution |
  • C++ random_device |
  • C++ Random |
  • C++ async & launch |
  • C++ Atomic Operations |