Generate a secure double defined on range [0.0d – 1.0d] (both inclusive)

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 if GetNextLow() returns 0ul. 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 from 0.0d.
  • 1.0d must occur if and only if GetNextLow() and GetNextHigh() return the same number. That’s an even rarer situation for a 64bit random number generator. But even if low == high, because of the cast to double, you may lose precision, and the result of the division may differ from 1.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

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x