TRAL.RANDOMNESS is a random number class library for C#. It provides common PRNG algorithms and a random number generator class.
Also on: Nuget
This project is licensed under Apache, Version 2.0.
Quick Start
Install the NuGet Release or download and build the source.
Basic Routines
Here, we create a default RandomGenerator
instance and demonstrate a selection of routines:
using Tral.Randomness;
...
// This will initialize generator
var rand = new RandomGenerator();
// Integers
uint u32 = rand.Next32();
ulong u64 = rand.Next64();
int i32 = rand.NextInt(5, 10);
long i64 = rand.NextLong(-5, 10);
// Doubles
var d1 = rand.NextDouble();
var d2 = rand.NextDouble(0, 10);
var d3 = rand.NextOpenDouble();
var d4 = rand.NextStdNormal();
// Bytes
var b = rand.NextBytes(64);
// Shuffle
var shuffled = new int[]{0, 1, 2, 3, 4};
rand.Shuffle(shuffled);
// Strings
var s1 = rand.NextString(3, "TheAlphabet");
var s2 = rand.NextString(10, IRandomRoutines.AlphaNumericMixed);
The RandomGenerator
class hierarchy is shown below.
Note that it inherits a generic type variant, which implements interfaces IRandomGenerator
and IRandomRoutines
. The non-generic RandomGenerator
subclass employs a default general purpose (non-cryptographic) algorithm. At the time of writing, this is Xoshiro256++, but may in principle be subject to change in the future. More below.
Global Singleton
For convenience, we may also use a singleton provided:
int i32 = RandomGenerator.Global.NextInt(5, 10);
The Global
instance value is thread local.
Algorithms
An algorithm is a class which inherits the Algorithms.IRandomAlgorithm
interface and implements the underlying source of “randomness”. In practice, we do not use IRandomAlgorithm
instances directly, rather we declare an instance of RandomGenerator<TAlgo>
as follows:
using Tral.Randomness;
using Tral.Randomness.Algorithms;
...
// Xoshiro256++
var xopp256 = new RandomGenerator<Xoshiro256pp>();
// Xoshiro256**
var xoss256 = new RandomGenerator<Xoshiro256ss>();
// WELL512
var well512 = new RandomGenerator<Well512>();
// MT19937-32
var mt32 = new RandomGenerator<MersenneTwister32>();
// MT19937-64
var mt64 = new RandomGenerator<MersenneTwister64>();
// ISAAC-32
var isaac32 = new RandomGenerator<Isaac32>();
// ISAAC-64
var isaac64 = new RandomGenerator<Isaac64>();
// SplitMix64
var split64 = new RandomGenerator<SplitMix64>();
// RAND48
var rand48 = new RandomGenerator<Rand48>();
It is useful to note that we can pass references to any of the above examples simply as instances of IRandomGenerator
or IRandomRoutines
, without having to specify the algorithm type.
The Xoshiro256pp
type, shown above, is a class which implements IRandomAlgorithm
via a hierarchy below.
Any class which properly implements IRandomAlgorithm
may be used with RandomGenerator<TAlgo>
, although it won’t be seedable or jumpable unless it also implements the respective interface.
Important. Only IRandomRoutines.Next()
can be expected to return values matching known test vectors of the respective algorithm. All other routines derive their results from this in an implementation specific manner.
Seeding and Jumping
We can seed and jump an instance of RandomGenerator<TAlgo>
as follows:
// Not randomized (false)
var rand = new RandomGenerator<Xoshiro256pp>(false);
// Initialize
rand.Randomize();
// How many bytes do we need to seed?
Console.WriteLine(rand.SeedLength);
// Xoshiro256++ requires 32 bytes
rand.SetSeed(seedBytes);
// We can also seed with a simple integer, although how
// this is done by RandomGenerator is implementation specific.
rand.SetSeed(98765);
// And, where supported:
if (rand.IsJumpable)
{
rand.Jump();
}
Implementing a New Algorithm
It is possible to construct a new algorithm by implementing the interface IRandomAlgorithm
or, more appropriately, for pseudo random generators: either the ISeedableAlgorithm
or IJumpableAlgorithm
interface. It would be appropriate to implement only IRandomAlgorithm
where the source is not seedable, such as with a generator utilizing external entropy as a source of randomness.
Here, by way of example, we present a simple but potentially useful implementation using the System RNGCryptoServiceProvider
class as the underlying generator. In effect, all we are doing is wrapping an instance of RNGCryptoServiceProvider
with the IRandomAlgorithm
interface so that we may use it with RandomGenerator<TAlgo>
, thus providing us with an extended API.
public class CryptoAlgorithm : IRandomAlgorithm
{
private readonly RNGCryptoServiceProvider _provider = new RNGCryptoServiceProvider();
/// <summary>
/// Implements <see cref="IRandomAlgorithm.AlgorithmName"/>.
/// </summary>
public string AlgorithmName { get; } = "RNGCryptoServiceProvider";
/// <summary>
/// Implements <see cref="IRandomAlgorithm.MaxNext"/>.
/// </summary>
public ulong MaxNext { get; } = uint.MaxValue;
/// <summary>
/// Implements <see cref="IRandomAlgorithm.Next"/>.
/// </summary>
public ulong Next()
{
byte[] bytes = new byte[4];
_provider.GetBytes(bytes);
return BitConverter.ToUInt32(bytes);
}
}
Now, we may use our new algorithm as follows:
var rand = new RandomGenerator<CryptoAlgorithm>();
int i32 = rand.NextInt(5, 10);
Discussion
The RNGCryptoServiceProvider
class inherits IDisposable
and we should technically dispose of it. However, IRandomAlgorithm
does not inherit IDisposable
as it would be undesirable to require the caller to dispose of random number generators. Fortunately, it appears the RNGCryptoServiceProvider.Dispose()
method does nothing when default constructed [*]. Therefore, disposal is not strictly necessary. Alternatively, if this was a problem, we could create a new instance of RNGCryptoServiceProvider
within the CryptoAlgorithm.Next()
method.
The IRandomAlgorithm.AlgorithmName
property is simply a short friendly name which we assign a suitable value.
Now, IRandomAlgorithm.MaxNext
is important and gets the maximum inclusive value of the IRandomAlgorithm.Next()
method. This can be any value greater than 0, as the RandomGenerator<TAlgo>
class will use it to populate the required bits in an unbiased fashion. Even a MaxNext
value of +1 will work, though it will be very inefficient. The most efficient (i.e. fastest) values of MaxNext
to use, where possible, are 2^64-1 and 2^32-1 respectively. In our example, above, we have chosen to populate the result of Next()
with 4 bytes. Therefore the value is 2^32-1.
[*] See this discussion on Stack Overflow.
Closing Remarks
I wrote this library primarily because I was interested in doing so — the philosophical distinction between psuedo and “true” randomness is perhaps the most profoundly interesting topic in the Universe. This library was created for use in EVOLGEN, a genetic algorithm, which was itself created for use in a financial trading algorithm (hence the namespace prefix “TRAL”).
Acknowledgements
Implementations of C# algorithms were created with reference to the following:
- The ISAAC algorithm was created by Bob Jenkins and is in the public domain. See: http://burtleburtle.net/bob/rand/isaacafa.html
- Mersenne Twister (MT) was originally developed by Makoto Matsumoto and Takuji Nishimura in 1996/1997.
- Xoshiro algorithms are derived from code put in the public domain by Sebastiano Vigna. Refer to: http://xoroshiro.di.unimi.it