I was searching for a pseudo-random number generator for C# and stumbled upon Tales from the CryptoRandom by Stephen Toub and Shawn Farkas, so I tried to implement a variation of their code.
The main difference with their implementation is mine returns inclusive ranges (e.g [0,1]
and [min,max]
instead of [0,1)
and [min,max)
),
namespace Albireo.SecureRandom
{
using System;
using System.Diagnostics.Contracts;
using System.Security.Cryptography;
public static class SecureRandom
{
private static readonly RNGCryptoServiceProvider Generator = new RNGCryptoServiceProvider();
public static byte GetByte()
{
var buffer = new byte[1];
Generator.GetBytes(buffer);
return buffer[0];
}
public static double GetDouble()
{
var buffer = new byte[8];
Generator.GetBytes(buffer);
return BitConverter.ToDouble(buffer, 0);
}
public static short GetInt16()
{
var buffer = new byte[2];
Generator.GetBytes(buffer);
return BitConverter.ToInt16(buffer, 0);
}
public static short GetInt16(short minimum, short maximum)
{
Contract.Requires<ArgumentException>(minimum < maximum, "minimum < maximum");
Contract.Ensures(Contract.Result<short>() >= minimum, "result >= minimum");
Contract.Ensures(Contract.Result<short>() <= maximum, "result <= maximum");
return (short) (minimum + (Sample() * (maximum - minimum)));
}
public static int GetInt32()
{
var buffer = new byte[4];
Generator.GetBytes(buffer);
return BitConverter.ToInt32(buffer, 0);
}
public static int GetInt32(int minimum, int maximum)
{
Contract.Requires<ArgumentException>(minimum < maximum, "minimum < maximum");
Contract.Ensures(Contract.Result<int>() >= minimum, "result >= minimum");
Contract.Ensures(Contract.Result<int>() <= maximum, "result <= maximum");
return (int) (minimum + (Sample() * (maximum - minimum)));
}
public static long GetInt64()
{
var buffer = new byte[8];
Generator.GetBytes(buffer);
return BitConverter.ToInt64(buffer, 0);
}
public static long GetInt64(long minimum, long maximum)
{
Contract.Requires<ArgumentException>(minimum < maximum, "minimum < maximum");
Contract.Ensures(Contract.Result<long>() >= minimum, "result >= minimum");
Contract.Ensures(Contract.Result<long>() <= maximum, "result <= maximum");
return (long) (minimum + (Sample() * (maximum - minimum)));
}
public static decimal Sample()
{
Contract.Ensures(Contract.Result<decimal>() >= 0, "result >= 0");
Contract.Ensures(Contract.Result<decimal>() <= 1, "result <= 1");
var buffer = new byte[8];
Generator.GetBytes(buffer);
return (BitConverter.ToUInt64(buffer, 0) & 0x7FFFFFFFFFFFFFFF) / (decimal) long.MaxValue;
}
}
}
I see there's already a similar question, but it lacks the actual implementation.
Since I'm not really skilled in mathematics, the main answers I'm looking for are:
- Is
Sample()
's implementation correct? It should return a number between [0,1] by getting a randomlong
, stripping its sign and dividing it bylong.MaxValue
to obtain the result. - Is the computation performed in the various "ranged" methods correct? Usually to get a random value between
min
andmax
you domin + (rnd * (max - min + 1))
becausernd
is [0,1), but here it's [0,1]. - Is there a way to have
Sample()
return a value between [0,1) instead of [0,1] to tons of code or wasting of "incorrect" values? Do note that the code is compiled with the/checked
flag so arithmetic overflows/underflows throw an exception. - How do I test a PRNG? By definition its results are... random, i.e. I can test
GetInt16(short, short)
as long as I want, but the numberOfGeneratedTestValues+1 call could still return an illegal value.
Any other comment is still appreciated.
Notes:
- I do know there is no XML documentation (yet).
- The class and its methods are static because
RNGCryptoServiceProvider
is thread safe, so there should be no reason to not have them static.