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++
Related posts (internal)
- 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
Related posts
- C++ Distribution |
- C++ random_device |
- C++ Random |
- C++ async & launch |
- C++ Atomic Operations |