Search code examples
c#randomrngcryptoserviceprovider

RNGCryptoServiceProvider with both min and max inclusive


I need a method, which returns random numbers from min to max, both numbers inclusive. I found some code in the article .NET Matters: Tales from the CryptoRandom from Stephen Toub and Shawn Farkas, where the method would look something like this:

// Note - maxValue is excluded!
public static int GetRandomIntBetween(int minValue, int maxValue)
{
    if (minValue > maxValue) throw new ArgumentOutOfRangeException("minValue");
    if (minValue == maxValue) return minValue;

    var rng = new RNGCryptoServiceProvider();
    var uint32Buffer = new byte[4];
    long diff = maxValue - minValue;

    while (true)
    {
        rng.GetBytes(uint32Buffer);
        uint rand = BitConverter.ToUInt32(uint32Buffer, 0);
        const long max = (1 + (long)int.MaxValue);
        long remainder = max % diff;
        if (rand < max - remainder)
        {
            return (int)(minValue + (rand % diff));
        }
    }
}

My attempt to make the maxValue inclusive:

public static int GetRandomIntBetween(int minValue, int maxValue)
{
    if (minValue > maxValue) throw new ArgumentOutOfRangeException("minValue");
    if (minValue == maxValue) return minValue;

    // Make maxValue inclusive.
    maxValue++;

    var rng = new RNGCryptoServiceProvider();
    var uint32Buffer = new byte[4];
    long diff = maxValue - minValue;

    while (true)
    {
        rng.GetBytes(uint32Buffer);
        uint rand = BitConverter.ToUInt32(uint32Buffer, 0);
        const long max = (1 + (long)int.MaxValue);
        long remainder = max % diff;
        if (rand < max - remainder)
        {
            return (int)(minValue + (rand % diff));
        }
    }
}

Looks strange, but it seems like I can keep the first two checks as they are and it still works even though the semantic is slightly different. Also the result data looks good. Did I miss something or is my change ok?

PS - I am asking this because generating random numbers is obviously quite a delicate matter and would like to be sure that my approach is correct.


Solution

  • Your changes are correct afaik, a random integer number between [a,b] is an random integer number between [a,b+1[.

    As long as maxValue is not int.MaxValue, then ++ would overflow, so it would be safer to not change maxValue and move the change to the calculation of diff:

    long diff = (long)maxValue - minValue + 1;
    

    However, the second check in the original function is obviously wrong, if minValue == maxValue, returning minValue is not an value between minValue and maxValue exclusively.