Random Number Generators

So-called random number generators (RNG's) produce sequences of numbers that, for the purposes of the user, appear to be random. Numerics.NET contains a number of different random number generators, which vary in speed and quality.

Extensions to Standard Random Number Generation

The .NET framework already contains a random number generator. Prior to .NET 6.0, it was based on an algorithm from Donald Knuth's The Art of Computer Programming. For several reasons, this generator is not ideal. It scores badly on some tests of randomness and has a serious bias in the least significant bit. Nevertheless, the System.Random class defines a number of methods that are the accepted standard in the .NET framework.

In .NET 6.0, the implementation was replaced with the much better xoshiro128** and xoshiro256** generators. Methods for generating single-precision floating-point numbers and 64 bit integers were also added. In .NET 8.0, a few more methods were added to select random items from a set of choices.

The ExtendedRandom class extends System.Random with some additional methods for the management of random number generators. ExtendedRandom is the base class for all random number generators in Numerics.NET.

The Restart method allows you to reset the random number generator to its original state. After restarting, the generator always produces the same sequence of random numbers. It is also possible to pass a seed to the Restart(Int32) method. In this case, the generator is initialized with the specified seed.

The ExtendedRandom class makes several methods that were added in more recent .NET versions available to earlier versions of the .NET, including: NextInt64 and its overloads, NextSingle, and Shuffle().

The GetItems() method returns a number of items randomly chosen from a set of choices. It is equivalent to the method of the same name in .NET 8.0 and later and has the same set of overloads. This method gets items with replacement. If the choices are all different, then the same element can still appear more than once in the output.

The GetItemsWithoutReplacement method, which also has the same set of overloads, returns items randomly chosen without replacement. If the choices are all different, then the same element will not appear more than once in the output. This also means that the output cannot have more elements than there are choices.

All random number generators can be serialized. This means that their current state can be saved. When their state is restored at a later time, the random sequence can be continued from the point where it was saved.

In addition, several extension methods are provided generating sequences of random numbers, and for generating random numbers from any statistical distribution. These extension methods also work on the standard .NET Random class. These are defined in the RandomExtensions class.

One group of methods is used to obtain the next sample from a distribution. Next method, which returns a random sample from a discrete distribution, and the NextDouble method, which returns a random sample from a continuous distribution. These methods take the probability distribution as an argument.

The RandomExtensions class defines a Fill method with several overloads that fill existing arrays and vectors with a sequence of random numbers. Overloads of this method correspond to overloads of the Next and NextDouble methods, with an extra argument that specifies the object that is to receive the random numbers.

Finally, the AsParallel(Random) method wraps the random number generator in a thread-safe wrapper so it can be safely called from multiple threads.

The PCG Family

The PCG family of random number generators provide fast, simple, space-efficient random number generation. It is a relatively recent (2014) development by Melissa E. O'Neill that addresses most of the potential issues of other generators. Because of its speed and compactness, it is the default random number generator in Numerics.NET as of version 9.

The PCG family of generators is implemented by the Pcg32 class. It has two constructors. The first constructor has no arguments and uses a random seed based on the system time. The second constructor takes two 64 bit unsigned integers. The first is the initial state (seed). The second is an optional stream identifier.

C#
var pcg1 = new Pcg32();
var pcg2 = new Pcg32(99ul, 0x1234567890abcdefUL);

The PCG family has the option of jumping ahead by an arbitrary number of steps in the sequence. This is done using the Skip(Int64) method.

Note that in general, skipping a certain number of steps is not equivalent to getting and discarding the same number of random numbers from the generator. Obtaining a random number within a certain range or from a specific distribution may require more than one random value from the generator. For example, the NextDouble() method always uses two 32-bit values.

The Mersenne Twister

The Mersenne Twister is a random number generator with an extremely long period - 219937, or roughly 1 followed by 6,000 zeros. It is relatively fast, and has good randomness properties. It is a variation on a generalized feedback shift register generator. It is of excellent quality and is comparable to other generators in speed.

The Mersenne Twister is implemented by the MersenneTwister class. It has three constructors. The first constructor has no arguments. The second constructor takes one integer seed value. The third constructor takes an array of integers containing the seed values. The maximum length of this array is 623.

C#
var mersenne1 = new MersenneTwister();
var mersenne2 = new MersenneTwister(99);
var mersenne3 = new MersenneTwister(new int[] { 99, 17, int.MaxValue });

The RANLUX Generator

The RANLUX suite of random number generators was developed by M. Luescher in 1994. It generates 24 bit pseudo-random numbers. To reduce correlation between successive values, a number of generated random numbers is skipped occasionally. The period of is roughly 10171.

The RANLUX suite is a prime example of the trade-off between quality and speed. Depending on the luxury level, the random numbers are from pretty good to perfect. The highest luxury level (3) produces 24 bit numbers which are proven to be totally random. This randomness comes at a cost: the best generator is about 3 times slower than the least good.

The RANLUX generators are implemented by the RanLux class. The luxury level is defined by the RanLuxLuxuryLevel enumeration. It can take on the following values:

Value

Description

Default

The fastest algorithm with some correlation between random values.

Better

A medium-speed algorithm with reduced correlation between random values.

Best

A slower algorithm without any correlation between random values.

The RanLux class has four constructors. The first constructor has no arguments. It uses the default (fastest) luxury level, and uses a time-based seed. The second constructor has one argument: a RanLuxLuxuryLevel that specifies the luxury level. The third constructor takes one integer argument that is the initial seed value. The fourth constructor takes an integer seed and a RanLuxLuxuryLevel value. The following code snippet illustrates these constructors:

C#
var ranLux1 = new RanLux();
var ranLux2 = new RanLux(99);
var ranLux3 = new RanLux(RanLuxLuxuryLevel.Better);
var ranLux4 = new RanLux(99, RanLuxLuxuryLevel.Best);

The Generalized Feedback Shift Register Generator

A Generalized Feedback Shift Register (GFSR) pseudo-random number generator combines 4 values from a long sequence of registers to produce each new 32 bit random number. Our implementation uses the values proposed by the generator's developer Robert Ziff. It has a very long period of about 102917.

Initialization time for this type of generator is relatively long. Generation of actual random numbers is very fast.

The generalized feedback shift register generator is implemented by the GfsrGenerator class. It has three constructors. The first constructor has no arguments. The second constructor takes one integer seed value. The third constructor takes an array of integers containing the seed values. The maximum length of this array is 16383.

C#
var gfsr1 = new GfsrGenerator();
var gfsr2 = new GfsrGenerator(99);
var gfsr3 = new GfsrGenerator(new int[] { 99, 17, int.MaxValue });

Choosing a Random Number Generator

No random number generator is perfect for every application. In fact, with the increase in computing power in recent decades have come an increasing number of failures of random number generators that were thought to be well-behaved. The application itself has become the testing ground for the quality of pseudo-random numbers.

We recommend testing your application with at least two different random number generators. If the results are significantly different, then at least one of the generators is not suitable. If the results are similar, you may wish to choose the faster of the two.

The PCG generator generally yields fast and high quality results. It is suitable for most applications.

References

Donald E. Knuth, (Vol 2, 3rd Ed, 1997), Addison-Wesley.

M. Loescher, “A portable high-quality random number generator for lattice field theory calculations”, , 79 (1994) 100-110.

Makoto Matsumoto and Takuji Nishimura, “Mersenne Twister: A 623-dimensionally equidistributed uniform pseudorandom number generator”. , Vol. 8, No. 1 (Jan. 1998), Pages 3-30

Melissa E. O'Neill, “PCG: A Family of Simple Fast Space-Efficient Statistically Good Algorithms for Random Number Generation”,

Robert M. Ziff, “Four-tap shift-register-sequence random-number generators”, , 12(4), Jul/Aug 1998, pp 385-392.