The project contains several implementations of popular pseudo random number generator algorithms. Additionally it contains serializers to save and restore state of every generator in the library.
Dependencies: netstandard 2.1
Generators live in NPrng.Generators
namespace and at the moment the library supports the following pseudo random generators:
- SplittableRandom - port of Java's SplittableRandom class. Quite fast and generates statistically great results. In addition has
.Split()
method that generates a new but independent instance ofSplittableRandom
from the old one. Great whenever deterministic randomness is needed in parallel processing (e.g. Monte Carlo style algorithms). The generator should be used as the default one. - LinearCongruentGenerator - popular prng algorithm, widely used around. The implementation here uses well chosen multiplier so that the distribution is good enough (based on spectral analysis). Fast and good enough for non-secure purposes.
- Xoshiro256StarStar - a variant of shift-register generators. One of the fastest generators, with good enough distribution.
- SplitMix64 - the default prng in Java. Very fast but statistically quite poor. It is suited for seeding other algorithms.
- ChaCha20Generator - cryptographically secure prng, thus great statistically and extremely hard to predict output based on previous outputs. This is a great algorithm whenever security is necessary. Note that this generator is slower than previous generators, somewhere between 10-20x. However the word "slower" doesn't mean "slow". It is still fast enough if not generating numbers in a tight loop.
Each of the generators implements IPseudoRandomGenerator
interface and can be used as follows:
var seed = (ulong)DateTime.UtcNow.ToBinary();
var generator = new LinearCongruentGenerator(seed);
var number = generator.GenerateInRange(0, 333);
IMPORTANT: all generators are designed to generate 64bit numbers. If you want to generate 32bit integer you have to trim the result.
WARNING: None of the generators is thread safe. In order to use them in multithreaded environment there are several things you can do:
- Write a wrapper that locks the generator
- Make each thread use a separate instance, for example via static
ThreadLocal<IPseudoRandomGenerator>
member - Use
SplittableRandom
and its.Split()
method and pass new instances to threads directly
Each generator additionally has its own serializer class. These classes implement IPseudoRandomGeneratorSerializer
interface and are used to store and load current generator state. They live in NPrng.Serializers
namespace. Example:
var seed = (ulong)DateTime.UtcNow.ToBinary();
var generator = new LinearCongruentGenerator(seed);
generator.Generate();
generator.Generate();
var serializer = new LinearCongruentGeneratorSerializer();
var serializedState = serializer.WriteToString(generator);
Console.WriteLine(serializedState); // Base64 encoded binary data
var newGenerator = serializer.ReadFromString(serializedState);
// Test correctedness
var originalNo = generator.Generate();
var newNo = newGenerator.Generate();
Console.WriteLine(originalNo == newNo); // Should be true
The expected size of serialized binary data is the following:
- SplittableRandom: 16 bytes
- LinearCongruentGenerator: 8 bytes
- Xoshiro256StarStar: 32 bytes
- Splitmix64: 8 bytes
- ChaCha20Generator: 64 bytes
(De)serialization into a string utilizes base64 encoding.
Dependencies: net 6.0
All generators have a simple statistical test based on prime numbers. You can run them by simply doing
> dotnet test
in Tests\NPrng.Tests
folder.
Note however that PrngStatisticalTests.cs
can take quite sometime, even though it is parallelized. It takes around ~14s for all of them on my 12 core Ryzen9 cpu. A single threaded version of that test took around ~40s.