TL;DR: default_random_engine
doesn’t give you any guarantees. If you want numeric random, use mt19937_64
instead. If you want to generate security relevant randomness (keys, IVs, salts, passwords, …), find a CSPRNG seeded from random_device
or use random_device
. See below for fun analysis of the default_random_engine
behaviour of g++
.
default_random_engine
is implementation-defined
As mentioned in the comments, default_random_engine
has implementation-defined behaviour and you should probably not be using it at all. Implementation-defined means that the implementation is, for example, free to use the (in)famous XKCD rng:
class default_random_engine
{
public:
using result_type = uint32_t;
result_type min() const
{
return 1;
}
result_type max() const
{
return 6;
}
default_random_engine(int) {}; // we don’t care about the argument
result_type operator()() const
{
return 4; // chosen by fair dice roll
// guaranteed to be random
}
};
default_random_engine
doesn’t work well with seeds close to each other
However, actually it appears simply that the default_random_engine
(used in the GNU libstdc++) is highly biased with the seeds currently produced by time(0)
and/or in the resolution it is sampled with a uniform_int_distribution<int>(1, 6)
. Try to generate several random numbers at once in the same program. You will find outputs like these: 6 6 6 4 6 2 2 3
, 6 3 2 1 4 6 3 3
, …. In short, default_random_engine
has no guarantees, and may be a very bad RNG for any purpose (appearantly, even teaching about the random API, because it doesn’t appear random when generating a single diceroll seeded with UNIX epoch timestamps!).
So I ran the numbers. With a small program (see below), I calculated the probability distribution for the first dice roll number generated with default_random_engine
and mt19937_64
to be 6, plotted over the range of seeds. This is with g++ (Debian 6.1.1-11) 6.1.1 20160802 and libstdc++ 6.1.1-11.

The plot shows the probability to get a 6 with std::uniform_int_distribution<int> (1,6)
in the first draw after initialising the corresponding random engine with the seed on the X axis. In addition, the current unix time is drawn as vertical black line (seen only in the top range of the chart). As you can see, we are currently in a range where the integer distribution is very likely (100%) to output a 6 as the first number using default_random_engine
. The mt19937_64
in contrast is very close to the expected ⅙ probability. The bin size for the histogram is 10000 (or about 2.8 hours worth of seconds-since-unix-epoch).
Also, over the whole tested range (0..4e9-1), default_random_engine
actually produces the 6 as first number with a probability of approximately ⅙. It is just really bad at dealing with seeds which are "close" (±1000) to each other.
Conclusion
So, in a way, time(0)
is the culprit, because it is changing slowly. And also, (the standard library used by) Code::Blocks is the culprit because their default_random_engine
sucks.A proper RNG should not bias towards a certain result over such a large range of seeds. Just use a proper RNG, and don’t try to get things to work nicely with default_random_engine
, which isn’t guaranteed to be portable between compilers and standard libraries.
Use one of the numerically sound random number engines provided by the C++11 standard instead (such as the mentioned mt19937_64), if you need random numbers for numerical purposes (such as a game physics simulation).
If you need random numbers for anything security relevant (generate passwords, salts, IVs for cryptography, keys, …), do not use mt19937_64, but use a CSPRNG seeded using std::random_device
or std::random_device
directly.
Another note, as the tutorial is geared towards game development: As soon as network comes to play, you may in fact need the platform independent behaviour. In such cases, choosing a specific RNG engine with specific parameters is vital to achieve reproducible results across clients and between client and server.
Source
#include <array>
#include <cstdint>
#include <iostream>
#include <random>
#include <ctime>
void search()
{
static constexpr uint32_t SEARCH_MAX = 4000000000;
static constexpr uint32_t SEARCH_BLOCKS = 8;
static constexpr uint32_t SEARCH_BIN_SIZE = 10000;
static constexpr uint32_t SEARCH_BINS = SEARCH_MAX / SEARCH_BIN_SIZE;
static constexpr uint32_t SEARCH_STEP = 1;
static constexpr uint32_t OUTPUT_STEP = 1000000;
std::vector<uint32_t> histogram(SEARCH_BINS);
for (uint32_t &member: histogram) member = 0;
std::uniform_int_distribution<int> diceRoll(1, 6);
static constexpr uint32_t START = BLOCK * (SEARCH_MAX/SEARCH_BLOCKS);
static constexpr uint32_t END = START + SEARCH_MAX/SEARCH_BLOCKS;
for (uint32_t i = START; i < END; i+=SEARCH_STEP) {
rng_engine_to_use foo(i);
int roll = diceRoll(foo);
if (roll == 6) {
histogram[i/SEARCH_BIN_SIZE] += 1;
}
if (i % OUTPUT_STEP == 0) {
std::cerr << ((float)i / SEARCH_MAX * 100) << "% \r" << std::flush;
}
}
for (uint32_t i = START / SEARCH_BIN_SIZE; i < END/SEARCH_BIN_SIZE; ++i) {
std::cout << i*SEARCH_BIN_SIZE << " " << histogram[i] << std::endl;
}
}
int main()
{
search();
}
I #define
d BLOCK to be in 0..7, to build one version for each of my CPU cores, because the mt19937_64 takes quite some time to generate all those numbers (I only afterwards realised that the plot would not be readable when plotted over the whole range, but meh.). I replaced rng_engine_to_use
with mt19937_64
and default_random_engine
each and plotted the generated histogram (seen above).