TRAL.RANDOMNESS — Random Number Class Library for C#

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.

RandomGenerator hierarchy

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.

Xoshiro256++ algorithm hierarchy

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:

Leave a Reply

Your email address will not be published. Required fields are marked *