The .Net Framework base class library (BCL) includes a pseudo-random number generator
for non-cryptography use in the form of the System.Random
class.
Math.NET Numerics provides a few alternatives with different characteristics
in randomness, bias, sequence length, performance and thread-safety. All these classes
inherit from System.Random
so you can use them as a drop-in replacement
even in third-party code.
All random number generators (RNG) generate numbers in a uniform
distribution. In practice you often need to sample random numbers with a different
distribution, like a Gaussian or Poisson. You can do that with one of our probability
distribution classes, or in F# also using the Sample
module. Once parametrized,
the distribution classes also provide a variety of other functionality around probability
distributions, like evaluating statistical distribution properties or functions.
We need to reference Math.NET Numerics and open the namespaces for random numbers and probability distributions:
|
Let's sample a few random values from a uniform distributed variable \(X\sim\mathcal{U}(0,1)\), such that \(0 \le x < 1\):
|
In F# we can do exactly the same, or alternatively use the Random
module:
let samples = Random.doubles 1000
// overwrite the whole array with new random values
Random.doubleFill samples
// create an infinite sequence:
let sampleSeq = Random.doubleSeq ()
// take a single random value
let rng = Random.shared
let sample = rng.NextDouble()
let sampled = rng.NextDecimal()
If you have used the .Net BCL random number generators before, you have likely noticed a few differences: we used special routines to create a full array or sequence in one go, we were able to sample a decimal number, an we used static functions and a shared default instance instead of creating our own instance.
Math.NET Numerics provides a few alternative random number generators in their own types.
For example, MersenneTwister
implements the very popular mersenne twister algorithm. All these types
inherit from System.Random
, are fully compatible to it and can also be used exactly the same way:
|
However, unlike System.Random they can be made thread safe, use much more reasonable
default seeds and have some convenient extra routines. The SystemRandomSource
class that
was used above uses System.Random to generate random numbers internally - but with all the extras.
Out of the box, System.Random
only provides Next
methods to sample integers
in the [0, Int.MaxValue) range and NextDouble
for floating point numbers in the [0,1) interval.
Did you ever have a need to generate numbers of the full integer range including negative numbers,
or a System.Decimal
? Extending discrete random numbers to different ranges or types is non-trivial
if the distribution should still be uniform over the chosen range. That's why we've added a few extensions
methods which are available on all RNGs (including System.Random itself):
System.Decimal
, uniform in the range [0.0, 1.0)All RNGs can be initialized with a custom seed number. The same seed causes the same number sequence to be generated, which can be very useful if you need results to be reproducible, e.g. in testing/verification. The exception is cryptography, where reproducible random number sequences would be a fatal security flaw, so our crypto random source does not accept a seed.
In the code samples above we did not provide a seed, so a default seed was used.
If no seed is provided, System.Random
uses a time based seed equivalent to the
one below. This means that all instances created within a short time-frame
(which typically spans about a thousand CPU clock cycles) will generate
exactly the same sequence. This can happen easily e.g. in parallel computing
and is often unwanted. That's why all Math.NET Numerics RNGs are by default
initialized with a robust seed taken from the CryptoRandomSource
if available,
or else a combination of a random number from a shared RNG, the time and a Guid
(which are supposed to be generated uniquely, worldwide).
let someTimeSeed = RandomSeed.Time() // not recommended
let someGuidSeed = RandomSeed.Guid()
let someRobustSeed = RandomSeed.Robust() // recommended, used by default
Let's generate random numbers like before, but this time with custom seed 42:
let samplesSeeded = Random.doublesSeed 42 1000
Random.doubleFillSeed 42 samplesSeeded
let samplesSeqSeeded = Random.doubleSeqSeed 42
Or without the F# Random module, e.g. in C#:
|
Up to now we've used only SystemRandomSource
, but there's much more:
Let's sample a few uniform random values using Mersenne Twister in C#:
|
In F# you can use the constructor as well, or alternatively use the Random
module.
In case of the latter, all objects will be cast to their common base type System.Random
:
// By using the normal constructor (random1 has type MersenneTwister)
let random1 = MersenneTwister()
let random1b = MersenneTwister(42) // with seed
// By using the Random module (random2 has type System.Random)
let random2 = Random.mersenneTwister ()
let random2b = Random.mersenneTwisterSeed 42 // with seed
let random2c = Random.mersenneTwisterWith 42 false // opt-out of thread-safety
// Using some other algorithms:
let random3 = Random.crypto ()
let random4 = Random.xorshift ()
let random5 = Random.wh2006 ()
Generators make certain claims about how many random numbers they can generate until the whole sequence repeats itself. However, this only applies if you continue to sample from the same instance and its internal state. The generator instances should therefore be reused within an application if long random sequences are needed. If you'd create a new instance each time, the numbers it generates would be exactly as random as your seed - and thus not very random at all.
Another reason to share instances: most generators run an initialization routine before they can start generating numbers which can be expensive. Some of them also maintain their internal state in large memory blocks, which can quickly add up when creating multiple instances.
Unfortunately the two generators provided by .NET are not thread-safe and thus cannot be
shared between threads without manual locking. But all the RNGs provided by Math.NET Numerics,
including the SystemRandomSource
and CryptoRandomSource
wrappers, are thread-safe by default,
unless explicitly disabled by a constructor argument or by setting Control.ThreadSafeRandomNumberGenerators
(which is used if the constructor argument is omitted).
For convenience a few generators provide a thread-safe shared instance
|
Or with the F# module:
let a = Random.systemShared
let b = Random.mersenneTwisterShared
// or if you don't care, simply
let c = Random.shared;
For non-uniform random number generation you can use the wide range of probability
distributions in the MathNet.Numerics.Distributions
namespace.
|
See Probability Distributions for details.