I wrote a small function to return a random in a given range:
int random_in_range(int min, int max) {
std::random_device rd;
std::mt19937 rng(rd());
std::uniform_int_distribution<int> uni(min, max);
return uni(rng);
}
But I read somewhere that you should only seed a random number generator once leading me to believe that the function should really be:
std::random_device rd;
std::mt19937 rng(rd());
int random_in_range(int min, int max) {
std::uniform_int_distribution<int> uni(min, max);
return uni(rng);
}
I later tested both to see if one was clearly better than the other (in terms of randomness) and got results which do not make things any clearer.
First example result with 10 runs, making a decision of 1 or 0:
for (int i = 0; i < 10; i++) {
cout << first_example(0, 1);
}
>0100100001
The second example result with 10 runs, making a decision of 1 or 0:
for (int i = 0; i < 10; i++) {
cout << second_example(0, 1);
}
>1011000110
The two results don't seem too strange leading me to be confused about how I should initialize random number generators. Basically, what I am asking is: which of these two example (or something else if both are wrong) would be used in order to guarantee the lowest amount of bias?
3 Answers 3
If you were going to get a number from random_device
at every call, you might as well just use it directly:
int random_in_range(int min, int max) {
std::random_device rd;
std::uniform_int_distribution<int> uni(min, max);
return uni(rd());
}
std::random_device
is intended to be a front-end for a truly random bit source. The major shortcoming is that in many cases it has fairly limited bandwidth, so you'd prefer to avoid calling it every time you need a number.
If you do want to use mt19937
(a perfectly fine idea in many cases) I'd personally use a function-object instead of a function:
class random_in_range {
std::mt19937 rng;
public:
random_in_range() : rng(std::random_device()()) {}
int operator()(int low, int high) {
std::uniform_int_distribution<int> uni(low, high);
return uni(rng);
}
};
This does have some shortcoming though: people may use a temporary of this type in a loop:
for (int i=0; i<10; i++)
std::cout << random_in_range()(0, 1);
...which puts you back where you started. You need to do something like:
random_in_range r;
for (int i=0; i<10; i++)
std::cout << r(0, 1);
...to get the results you want (i.e., seed once, call multiple times).
-
1\$\begingroup\$ Another significant point about
std::random_device
is that it is generally non-deterministic rather than a PRNG. Your point about using it sparingly is important. \$\endgroup\$Edward– Edward2015年08月21日 01:22:13 +00:00Commented Aug 21, 2015 at 1:22 -
\$\begingroup\$ I know is does not matter for
uniform_int_distribution
, but I seem to remember that distributions should also be kept from one iteration to the other (see for example the poisson_distribution example). \$\endgroup\$Matthieu M.– Matthieu M.2015年08月21日 13:05:18 +00:00Commented Aug 21, 2015 at 13:05
Here's how Bjarne Stroustrup did it:
// random number generator from Stroustrup:
// http://www.stroustrup.com/C++11FAQ.html#std-random
int rand_int(int low, int high)
{
static std::default_random_engine re {};
using Dist = std::uniform_int_distribution<int>;
static Dist uid {};
return uid(re, Dist::param_type{low,high});
}
The principal difference is that the random engine re
is static
so there is only one initialization (and therefore seed).
Also note that a sample of 10 runs is too short to conclude much. Testing random number generators (RNGs) or psuedo-random number generators (PRNGs) is quite complex. See http://csrc.nist.gov/groups/ST/toolkit/rng/index.html for a thorough explanation of the purpose, theory and actual source-code for tools to do a good job of testing PRNGs.
-
2\$\begingroup\$ This function is not thread safe. \$\endgroup\$Siyuan Ren– Siyuan Ren2015年08月21日 06:09:20 +00:00Commented Aug 21, 2015 at 6:09
-
1\$\begingroup\$ @SiyuanRen: Indeed, for thread safety, the
static
bits should be thread local. \$\endgroup\$Matthieu M.– Matthieu M.2015年08月21日 13:07:12 +00:00Commented Aug 21, 2015 at 13:07 -
1\$\begingroup\$ The context of Stroustrup's code was to show a simple random number generator that could be used by beginning students, not to show the universally "best" way to do it. Beginners are unlikely to write multithreaded code. \$\endgroup\$Edward– Edward2015年08月21日 13:33:23 +00:00Commented Aug 21, 2015 at 13:33
-
2\$\begingroup\$ @Edward - beginners are far too likely to write (bad) multithreaded code. Nevertheless, you're absolutely right that simple examples should be simple. \$\endgroup\$Pete Becker– Pete Becker2015年08月21日 13:42:50 +00:00Commented Aug 21, 2015 at 13:42
You can simply make your re-usable objects thread_local
statics:
int random_in_range(int min, int max) {
thread_local static std::mt19937 mt(std::random_device{}());
thread_local static std::uniform_int_distribution<int> pick;
// assuming param_type is lighter weight to construct
// than a uniform_int_distribution
return pick(mt, decltype(pick)::param_type{min, max});
}
rng
out of your function, too. Moving the random device outside is not sufficient. \$\endgroup\$