iostream formatting
The random library separates two things: an engine produces a stream of bits from a seed, and a distribution turns that stream into numbers in a range. The engine is deterministic, the distribution shapes the result, and the seed decides where the stream starts.
That split is why a program can be reproducible and still look random: seed with a constant and every run gives the same sequence, seed with random_device and every run differs. This lesson shows both and ends with the reseeding mistake that produces the same number twice.
A distribution object holds state of its own and must live as long as the sequence it produces. Constructing it inside the loop, like the engine, restarts it and produces a biased or repeating sequence.
The engine's state is the whole sequence: copying the engine copies the position, which is how a test can replay the same numbers twice from one starting point.
A distribution may also be cached: constructing it once per program and passing it to the engine keeps its internal state, which some distributions use.
An engine and a distribution
std::mt19937 is a Mersenne Twister engine with a fixed seed here, so the sequence is the same on every run. The uniform distribution maps the engine's output to the range 1 to 6 inclusive.
The program rolls five dice and prints them. The numbers look random and are reproducible, which is what a test needs.
The engine produces 32-bit unsigned values and nothing else. Every range, every shape and every floating point value comes from a distribution on top of it.
#include <iostream>
#include <random>
int main()
{
std::mt19937 engine{42};
std::uniform_int_distribution<int> dice{1, 6};
for (int i = 0; i < 5; ++i) {
std::cout << dice(engine) << ' ';
}
std::cout << '\n';
}
g++ -std=c++23 -Wall -Wextra engine.cpp -o engine
./engine
3 5 6 2 5
A seed from the system
std::random_device reads entropy from the operating system. Seeding an engine with its result makes the program non-reproducible on purpose.
The program seeds an engine from random_device and prints one number in 1 to 100. The value changes between runs, so the output on this page is one run's answer.
A program that must be reproducible for tests seeds with a constant and prints it, or takes the seed as an argument. A program that shuffles a game seeds from random_device, and does it once, not on every call.
#include <iostream>
#include <random>
int main()
{
std::random_device source;
std::mt19937 engine{source()};
std::uniform_int_distribution<int> percent{1, 100};
std::cout << percent(engine) << '\n';
}
g++ -std=c++23 -Wall -Wextra seed.cpp -o seed
./seed
13
A normal distribution
The distributions are not limited to integers. A normal distribution has a mean and a standard deviation, and it produces doubles clustered around the mean.
The program samples eight values with mean 10 and deviation 2, and prints them with one decimal. With a fixed seed the list is reproducible, so the page shows the same numbers on every run.
A uniform_real_distribution gives values in an interval, a bernoulli_distribution gives true with a probability, and a discrete_distribution gives weighted choices. All of them take the engine as their only argument when called.
#include <iomanip>
#include <iostream>
#include <random>
int main()
{
std::mt19937 engine{7};
std::normal_distribution<double> around{10.0, 2.0};
std::cout << std::fixed << std::setprecision(1);
for (int i = 0; i < 8; ++i) {
std::cout << around(engine) << ' ';
}
std::cout << '\n';
}
g++ -std=c++23 -Wall -Wextra distribution.cpp -o distribution
./distribution
8.6 7.8 9.9 10.8 7.8 8.2 9.7 8.4
Shuffling
std::shuffle reorders a range with an engine, and every permutation is equally likely. It is the tool for cards, questions and any list that should come out in a random order.
The program shuffles 1 to 6 with a fixed seed and prints the result. The same seed gives the same permutation, which makes a test that depends on the order stable.
std::sample picks a subset without replacement when the whole range should not be shuffled. Both take the engine by reference, and neither reseeds it.
#include <algorithm>
#include <iostream>
#include <numeric>
#include <random>
#include <vector>
int main()
{
std::vector<int> cards(6);
std::iota(cards.begin(), cards.end(), 1);
std::mt19937 engine{1};
std::shuffle(cards.begin(), cards.end(), engine);
for (int card : cards) {
std::cout << card << ' ';
}
std::cout << '\n';
}
g++ -std=c++23 -Wall -Wextra shuffle.cpp -o shuffle
./shuffle
2 1 3 6 4 5
Reseeding on every call
An engine seeded with the same value produces the same stream. A function that constructs the engine on every call, or reseeds it, therefore returns the same number each time, which looks like a broken random generator.
The program's roll() seeds a fresh engine on each call and prints the same value twice. The output is the evidence: two calls, one number repeated.
The fix is one engine that lives as long as the program or the object and is passed to the distribution by reference. Seeding is a decision made once.
#include <iostream>
#include <random>
int roll()
{
std::mt19937 engine{42};
std::uniform_int_distribution<int> dice{1, 6};
return dice(engine);
}
int main()
{
std::cout << roll() << ' ' << roll() << '\n';
}
g++ -std=c++23 -Wall -Wextra reseed.cpp -o reseed
./reseed
3 3
Exercises
- Roll two dice a thousand times with a fixed seed and count how often each sum appears.
- Shuffle a vector of ten names and print the first three.
- Write a function that returns true with probability 0.3 using bernoulli_distribution and count the trues in a thousand calls.
Questions
A new set of questions in a new order every time. Choose one answer for each, then check.
-
What does an engine produce?
Answer
A deterministic stream of bits from a seed
The engine is the generator, and the same seed gives the same stream.
-
What does a distribution do?
Answer
It shapes the engine's output into a range and a pattern
The distribution maps the uniform bits to the requested range or shape.
-
What does
std::random_devicegive?Answer
A seed from the system's entropy source
The device is used to seed an engine, not as the generator itself.
-
What does
std::shuffleneed?Answer
A uniform random bit generator
The algorithm permutes the range using the engine's stream.
-
What does this program print?
#include <iostream> #include <random> int main() { std::mt19937 engine{7}; std::uniform_int_distribution<int> dice{1, 6}; std::cout << dice(engine) << ' ' << dice(engine); }Answer
1 2
The seed 7 fixes the stream, and the first two dice values are 1 and 2.
-
What does this program print?
#include <iostream> #include <random> int main() { std::mt19937 first{7}; std::mt19937 second{7}; std::uniform_int_distribution<int> dice{1, 6}; std::cout << dice(first) << ' ' << dice(second) << ' ' << dice(first) << ' ' << dice(second); }Answer
1 1 2 2
The same seed gives the same sequence, so the interleaved draws agree in pairs: 1, 1, 2, 2.
-
What does this program print?
#include <iostream> #include <random> int main() { std::mt19937 engine{1}; std::uniform_int_distribution<int> coin{0, 1}; int heads = 0; for (int i = 0; i < 100; ++i) heads += coin(engine); std::cout << (heads > 0) << ' ' << (heads < 100); }Answer
1 1
A hundred fair coin flips give both outcomes, so both comparisons are true.
-
What does this program print?
#include <algorithm> #include <iostream> #include <random> #include <vector> int main() { std::vector<int> values{1, 2, 3}; std::mt19937 engine{7}; std::shuffle(values.begin(), values.end(), engine); int sum = 0; for (int v : values) sum += v; std::cout << sum; }Answer
6
The shuffle permutes the three values, and their sum does not change.
-
Why is a distribution object usually kept outside the loop?
Answer
It may keep state between calls
The distribution is an object like the engine, and re-creating it can reset its state.
-
What happens when a program reseeds the engine on every call?
Answer
It repeats the same first value
Each call starts the same sequence from the same seed, so the first draw repeats.
The code on this page was built with g++ 15.2.0 and run on Alpine Linux 3.24.3 (x86_64) in a Docker container.
Article author: Arthur Isaev