I need to know how to generate a secure double defined on range [0.0 – 1.0] both inclusive.
I generate two big ulong
numbers with RNGCryptoServiceProvider
class, make sure that the second number is greater or equals to the first number and then divide the first number by the second number and cast result to double
. Ideally, numbers 0.0d and 1.0d should be included in this range, but they’re never included. It must be something with mantissa with double
values. Do you know how to fix it?
Code I used:
public static class Randomizer { private static readonly RandomNumberGenerator RngLow; private static readonly RandomNumberGenerator RngHigh; static Randomizer() { RngLow = new RNGCryptoServiceProvider(); RngHigh = new RNGCryptoServiceProvider(); } /// <summary> /// Returns a next double value defined on range [0,0 - 1,0] /// </summary> public static double NextDouble() { ulong low = GetNextLow(); ulong high = GetNextHigh(low); return ((double) low) / high; } private static ulong GetNextLow() { byte[] bytes = new byte[sizeof(ulong)]; RngLow.GetBytes(bytes); ulong number = BitConverter.ToUInt64(bytes, 0); return number; } private static ulong GetNextHigh(ulong low) { byte[] bytes = new byte[sizeof(ulong)]; while (true) { Array.Clear(bytes, 0, bytes.Length); RngHigh.GetBytes(bytes); ulong number = BitConverter.ToUInt64(bytes, 0); if (number < low) { continue; } return number; } } }
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 1
UPDATE 1
As Jeroen Mostert stated in the comments, this is not a secure random number generator because some results are more probable than others. For instance 1
is about twice as probable as .5
and four times as probable as 0.25
That’s a problem with the precision of double
. Eventhough double.MaxValue
is by order of magnitudes greater than ulong.MaxValue
(about 1.8 x 10^308
vs 1.9 x 10^19
) it only has a precision of about 15-17 digits (docs). So when casting a very large ulong to double, you lose some of the ulongs digits. Might not be a problem for most of your results, but for the border cases 0.0d and 1.0d. I don’t know, how many runs you took but:
-
0.0d
must occur if and only ifGetNextLow()
returns0ul
. That’s quite a rare situation for a 64bit random number generator. In any other case the result of the division must and will be different from0.0d
. -
1.0d
must occur if and only ifGetNextLow()
andGetNextHigh()
return the same number. That’s an even rarer situation for a 64bit random number generator. But even iflow == high
, because of the cast to double, you may lose precision, and the result of the division may differ from1.0d
(depending on how the division is implemented)
So I’d suggest something like the following
public static double NextDouble() { ulong low = GetNextLow(); if (low == 0) return 0.0d; ulong high = GetNextHigh(low); if (low == high) return 1.0d; return ((double) low) / high; }
UPDATE 2
Yet another problem with your code is, that getting the high
number may take quite long. Imagine low
got the random value ulong.MaxValue - 1
. Thus there are only two possible values for high
anymore: ulong.MaxValue - 1
and ulong.MaxValue
. But your random generator in GetNextHigh()
is generating numbers over and over until it hits one of these two allowed results. So you should also get rid of the GetNextHigh()
function, and just call GetNextLow()
twice and swap divisor and dividend if necessary.
public static double NextDouble() { ulong low = GetNextLow(); if (low == 0) return 0.0d; ulong high = GetNextLow(); if (low == high) return 1.0d; return low < high ? ((double) low) / high : ((double) high) / low; }
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0