std::uniform_real_distribution
.
There's a really good talk by S.T.L. from this year’s Going Native conference that explains why you should use the standard distributions whenever possible. In short, hand-rolled code tends to be of laughably poor quality (think std::rand() % 100
), or have more subtle uniformity flaws, such as in (std::rand() * 1.0 / RAND_MAX) * 99
, which is the example given in the talk and is a special case of the code posted in the question.
EDIT: I took a look at libstdc++’s implementation of std::uniform_real_distribution
, and this is what I found:
The implementation produces a number in the range [dist_min, dist_max)
by using a simple linear transformation from some number produced in the range [0, 1)
. It generates this source number using std::generate_canonical
, the implementation of which my be found here (at the end of the file). std::generate_canonical
determines the number of times (denoted as k
) the range of the distribution, expressed as an integer and denoted here as r
*, will fit in the mantissa of the target type. What it then does is essentially to generate one number in [0, r)
for each r
-sized segment of the mantissa and, using arithmetic, populate each segment accordingly. The formula for the resulting value may be expressed as
Σ(i=0, k-1, X/(r^i))
where X
is a stochastic variable in [0, r)
. Each division by the range is equivalent to a shift by the number of bits used to represent it (i.e., log2(r)
), and so fills the corresponding mantissa segment. This way, the whole of the precision of the target type is used, and since the range of the result is [0, 1)
, the exponent remains 0
** (modulo bias) and you don’t get the uniformity issues you have when you start messing with the exponent.
I would not trust implicity that this method is cryptographically secure (and I have suspicions about possible off-by-one errors in the calculation of the size of r
), but I imagine it is significantly more reliable in terms of uniformity than the Boost implementation you posted, and definitely better than fiddling about with std::rand
.
It may be worth noting that the Boost code is in fact a degenerate case of this algorithm where k = 1
, meaning that it is equivalent if the input range requires at least 23 bits to represent its size (IEE 754 single-precision) or at least 52 bits (double-precision). This means a minimum range of ~8.4 million or ~4.5e15, respectively. In light of this information, I don’t think that if you’re using a binary generator, the Boost implementation is quite going to cut it.
After a brief look at libc++’s implementation, it looks like they are using what is the same algorithm, implemented slightly differently.
(*) r
is actually the range of the input plus one. This allows using the max
value of the urng as valid input.
(**) Strictly speaking, the encoded exponent is not 0
, as IEEE 754 encodes an implicit leading 1 before the radix of the significand. Conceptually, however, this is irrelevant to this algorithm.
[min, max)
", because without a range restrictions, there are no uniform random number generators. :) – Garmon[min - n*ULP, max + n*ULP)
would be fine though for some value ofn
that I'm too lazy to think about but is probably 1. – Magenta[std::numeric_limits<double>::Min(), std::numeric_limits<double>::Max())
, rounded, then discarded if outside of[min, max)
. ;) – Garmonmin
andmax
can be any range. – Magentax
) with normal numbers when precision is paramount. – Magenta