On Stackoverflow there are many questions about generating uniformly distributed integers from a-priory unknown ranges. E.g.
- C++11 Generating random numbers from frequently changing range
- Vary range of uniform_int_distribution
The typical solution is something like:
inline std::mt19937 &engine()
{
thread_local std::mt19937 eng;
return eng;
}
int get_int_from_range(int from, int to)
{
std::uniform_int_distribution<int> dist(from, to);
return dist(engine());
}
Given that a distribution should be a lightweight object and there aren't performance concerns recreating it multiple times, it seems that even simple distribution may very well and usually will have some internal state.
So I was wondering if interfering with how the distribution works by constantly resetting it (i.e. recreating the distribution at every call of get_int_from_range
) I get properly distributed results.
There's a long discussion between Pete Becker and Steve Jessop but without a final word. In another question (Should I keep the random distribution object instance or can I always recreate it?) the "problem" of the internal state doesn't seem very important.
Does the C++ standard make any guarantee regarding this topic?
Is the following implementation (from N4316 - std::rand replacement) somewhat more reliable?
int get_int_from_range(int from, int to)
{
using distribution_type = std::uniform_int_distribution<int>;
using param_type = typename distribution_type::param_type;
thread_local std::uniform_int_distribution<int> dist;
return dist(engine(), param_type(from, to));
}
EDIT
This reuses a possible internal state of a distribution but it's complex and I'm not sure it does worth the trouble:
int get_int_from_range(int from, int to)
{
using range_t = std::pair<int, int>;
using map_t = std::map<range_t, std::uniform_int_distribution<int>>;
thread_local map_t range_map;
auto i = range_map.find(range_t(from, to));
if (i == std::end(range_map))
i = range_map.emplace(
std::make_pair(from, to),
std::uniform_int_distribution<int>{from, to}).first;
return i->second(engine());
}
Interesting question.
I've written code to test this with
uniform_int_distribution
andpoisson_distribution
. It's easy enough to extend this to test another distribution if you wish. The answer seems to be yes.Boiler-plate code:
Code for the actual tests:
Run it here.
Not that I know of. However, I would say that the standard makes an implicit recommendation not to re-create the engine every time; for any distribution
Distrib
, the prototype ofDistrib::operator()
takes a referenceURNG&
and not a const reference. This is understandably required because the engine might need to update its internal state, but it also implies that code looking like thisdoes not compile, which to me is a clear incentive not to write code like this.
I'm sure there are plenty of advice out there on how to properly use the random library. It does get complicated if you have to handle the possibility of using
random_device
s and allowing deterministic seeding for testing purposes, but I thought it might be useful to throw my own recommendation out there too:Run it here. Hope this helps!
EDIT My first recommendation was a mistake, and I apologise for that; we can't use a singleton engine like in the tests above, because this would mean that two uniform int distributions would produce the same random sequence. Instead I rely on the fact that
std::bind
copies the newly-created engine locally instd::function
with its own seed, and this yields the expected behaviour; different generators with the same distribution produce different random sequences.