Generating Random Data

Generating random data is fundamental to scientific computing, machine learning, and simulations. Numerics.NET provides high-performance APIs for generating uniform random values in bulk, creating random vectors and matrices, and sampling from probability distributions, all with full support for reproducibility and modern performance optimizations.

Getting Individual Random Numbers

Random sources provide methods for generating individual random values of various types. These methods are useful when you need a small number of random values or when the number needed is not known in advance:

Method

Description

Next()

Returns a non-negative random integer

Next(Int32)

Returns a non-negative random integer less than the specified maximum

Next(Int32, Int32)

Returns a random integer within a specified range

NextInt64()

Returns a non-negative random 64-bit integer

NextInt64(Int64)

Returns a non-negative random 64-bit integer less than the specified maximum

NextInt64(Int64, Int64)

Returns a random 64-bit integer within a specified range

NextDouble()

Returns a random floating-point number in the range [0.0, 1.0)

NextSingle()

Returns a random single-precision floating-point number in the range [0.0, 1.0)

NextBytes(Byte[])

Fills the elements of a specified array of bytes with random numbers

NextBytes(Span<Byte>)

Fills the elements of a specified span of bytes with random numbers

NextUInt32()

Returns a random 32-bit unsigned integer

NextUInt64()

Returns a random 64-bit unsigned integer

For best performance when generating many values, use the bulk Fill methods described in the next section instead of repeatedly calling these scalar methods.

Filling Arrays and Spans

The modern way to generate multiple random values is to fill an existing span or array using span-first APIs. These methods write directly to your destination buffer, avoiding unnecessary allocations:

C#
var rng = RandomSources.Create(42);

// Fill an array with random doubles in [0, 1)
var values = new double[1000];
rng.Fill(values);

// Fill a span with random integers in [0, 100)
var indices = new int[50];
rng.Fill(indices.AsSpan(), maxValue: 100);

// Fill with integers in a range [10, 50)
var rangeValues = new int[50];
rng.Fill(rangeValues.AsSpan(), minValue: 10, maxValue: 50);

The Fill methods available on random sources write values directly into the provided span. These methods come in several variants:

Method

Description

Fill(Span<Double>)

Fills a span with random double-precision values in [0.0, 1.0)

Fill(Span<Single>)

Fills a span with random single-precision values in [0.0, 1.0)

Fill(Span<Int32>)

Fills a span with non-negative random integers

Fill(Span<Int32>, Int32)

Fills a span with random integers in [0, maxValue)

Fill(Span<Int32>, Int32, Int32)

Fills a span with random integers in [minValue, maxValue)

Fill(Span<Int64>)

Fills a span with non-negative random 64-bit integers

Fill(Span<Int64>, Int64)

Fills a span with random 64-bit integers in [0, maxValue)

Fill(Span<Int64>, Int64, Int64)

Fills a span with random 64-bit integers in [minValue, maxValue)

Fill(Span<UInt32>)

Fills a span with random 32-bit unsigned integers

Fill(Span<UInt64>)

Fills a span with random 64-bit unsigned integers

Common Data Structures

Numerics.NET provides factory methods for creating random vectors and matrices with common distributions, as well as utilities for shuffling and permutations.

Random Vectors

Create random vectors using the static methods on Vector<T>:

C#
var rng1 = RandomSources.Create(42);

// Create a random vector with uniform [0, 1) values
var uniformVector = Vector.Random(length: 100, rng1);

// Create a random vector with uniform [min, max) values
var rangeVector = Vector.RandomUniform(
    length: 100, 
    min: -1.0, 
    max: 1.0, 
    rng1);

// Create a random vector with normal distribution
var normalVector = Vector.RandomNormal(
    length: 100,
    mean: 0.0,
    standardDeviation: 1.0,
    rng1);

Available distributions include:

Method

Description

Random

Uniform distribution in [0, 1)

RandomUniform

Uniform distribution in a specified range

RandomNormal

Normal (Gaussian) distribution

Random Matrices

Similarly, create random matrices using the static methods on Matrix<T>:

C#
var rng2 = RandomSources.Create(42);

// Create a random matrix with uniform [0, 1) values
var uniformMatrix = Matrix.Random(rowCount: 10, columnCount: 20, rng2);

// Create a random matrix with normal distribution
var normalMatrix = Matrix.RandomNormal(
    rowCount: 10,
    columnCount: 20,
    mean: 0.0,
    standardDeviation: 1.0,
    rng2);

// Create a random matrix with uniform range
var rangeMatrix = Matrix.RandomUniform(
    rowCount: 10,
    columnCount: 20,
    min: -5.0,
    max: 5.0,
    rng2);

These methods use the same distribution options as vectors.

Shuffling and Permutations

Numerics.NET provides extension methods for shuffling arrays, lists, and spans in-place using the Fisher-Yates algorithm:

C#
var rng3 = RandomSources.Create(42);

// Shuffle an array in-place
int[] items = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
rng3.Shuffle(items);

// Shuffle a list
var list = new List<string> { "Alice", "Bob", "Charlie", "Diana" };
rng3.Shuffle(list);

// Shuffle a span
Span<int> span = stackalloc int[] { 10, 20, 30, 40, 50 };
rng3.Shuffle(span);

The Shuffle extension method is available on any IRandomSource and works with arrays, lists, and spans. The shuffle is performed in-place, modifying the original collection.

  Note

Starting with .NET 8.0, System.Random also includes a Shuffle method. Numerics.NET extends this functionality to earlier framework versions and provides additional overloads for Numerics.NET random sources.

Sampling from Probability Distributions

While uniform random numbers are the foundation, real-world applications often need values drawn from specific probability distributions. Numerics.NET provides seamless integration between random sources and probability distributions.

Basic Distribution Sampling

Every probability distribution in Numerics.NET can be paired with a random source to generate samples. Use the Sample method to draw a single value, or Sample overloads to fill arrays and spans:

C#
var rng = RandomSources.Create(42);

// Create a normal distribution with mean=100, stddev=15
var normalDist = new NormalDistribution(mean: 100.0, standardDeviation: 15.0);

// Sample a single value
double sample = normalDist.Sample(rng);

// Sample multiple values into an array
var samples = new double[1000];
normalDist.Sample(rng, samples);

This approach works with any continuous or discrete distribution in Numerics.NET. The distribution object encapsulates the parameters (mean, standard deviation, etc.), while the random source provides the underlying randomness.

Common Distributions

Here are examples of sampling from frequently used distributions:

Normal (Gaussian) Distribution

The normal distribution is ubiquitous in statistics and simulation. Generate normally distributed values with specified mean and standard deviation:

C#
var rng1 = RandomSources.Create(42);
var normal = new NormalDistribution(mean: 0.0, standardDeviation: 1.0);

// Generate 10,000 standard normal values
var values = new double[10000];
normal.Sample(rng1, values);

// Verify: mean ≈ 0, standard deviation ≈ 1
double mean = values.Mean();
double stddev = values.StandardDeviation();

Exponential Distribution

The exponential distribution models waiting times and lifetimes:

C#
var rng2 = RandomSources.Create(42);
var exponential = new ExponentialDistribution(scale: 0.5);

// Sample waiting times
var waitingTimes = new double[1000];
exponential.Sample(rng2, waitingTimes);

Poisson Distribution

The Poisson distribution models count data and rare events:

C#
var rng3 = RandomSources.Create(42);
var poisson = new PoissonDistribution(mean: 5.0);

// Sample event counts
var counts = new int[1000];
for (int i = 0; i < counts.Length; i++)
    counts[i] = poisson.Sample(rng3);

Binomial Distribution

The binomial distribution models the number of successes in a fixed number of independent trials:

C#
var rng4 = RandomSources.Create(42);
var binomial = new BinomialDistribution(numberOfTrials: 20, probabilityOfSuccess: 0.3);

// Sample number of successes
var successes = new int[1000];
for (int i = 0; i < successes.Length; i++)
    successes[i] = binomial.Sample(rng4);

Bulk Sampling for Performance

For large-scale simulations, use bulk sampling to generate many values at once. This is significantly faster than calling Sample() in a loop:

C#
var rng5 = RandomSources.Create(42);
var dist = new NormalDistribution(0.0, 1.0);

// Efficient: Use span-based bulk sampling
var buffer = new double[10000];
dist.Sample(rng5, buffer);

// Avoid: Loop over scalar Sample() calls (much slower)
// for (int i = 0; i < buffer.Length; i++)
//     buffer[i] = dist.Sample(rng5);

Bulk sampling reduces per-call overhead and enables internal optimizations like vectorization. Always prefer span-based methods when generating many samples.

Multivariate Distributions

Numerics.NET also supports sampling from multivariate distributions, which produce vectors of correlated values:

C#
var rng6 = RandomSources.Create(42);

// Create a 3D multivariate normal distribution
var mean = Vector.Create(0.0, 0.0, 0.0);
var covariance = Matrix.CopyFromDiagonal(Vector.Create(1.0, 2.0, 0.5));
var mvNormal = new MultivariateNormalDistribution(mean, covariance);

// Sample correlated vectors
var sample1 = mvNormal.Sample(rng6);
var sample2 = mvNormal.Sample(rng6);

Multivariate distributions are particularly useful in Monte Carlo simulations where you need to model correlated random variables.

Performance Guidance

Follow these guidelines for optimal performance when generating random data:

Prefer Bulk Operations

Always prefer bulk fill operations over loops that call scalar methods:

C#
var rng = new Pcg64(42);

// GOOD: Use bulk fill
var values = new double[1000];
rng.Fill(values);

// AVOID: Loop over scalar calls (much slower)
var values2 = new double[1000];
for (int i = 0; i < values2.Length; i++)
    values2[i] = rng.NextDouble();

Bulk operations are significantly faster because they:

  • Reduce per-call overhead

  • Enable SIMD optimizations

  • Minimize allocations

Reuse Destination Buffers

When generating random values repeatedly, reuse the same destination buffer to avoid allocations:

C#
var rng1 = new Pcg64(42);

// GOOD: Reuse buffer across iterations
var buffer = new double[100];
for (int trial = 0; trial < 1000; trial++)
{
    rng1.Fill(buffer);
    ProcessData(buffer);
}

// AVOID: Allocates new array each iteration
for (int trial = 0; trial < 1000; trial++)
{
    var buffer2 = new double[100];
    rng1.Fill(buffer2);
    ProcessData(buffer2);
}

Pass Explicit RNG to Hot Paths

When generating many values in performance-critical code, explicitly pass a concrete RNG type rather than using the shared source:

C#
// GOOD: Explicit RNG passed to hot path
var rng2 = new Pcg64(42);
var results = RunMonteCarlo(rng2, iterations: 1000000);

// LESS OPTIMAL: Using Shared adds small overhead
var results2 = RunMonteCarlo(RandomSources.Shared, iterations: 1000000);

Distribution Sampling Tips

When sampling from probability distributions:

  • Create distributions once: Distribution objects cache precomputed values. Create them once and reuse them for all samples.

  • Use bulk sampling: Generate many values at once using span-based methods rather than loops.

  • Choose the right algorithm: Some distributions support multiple sampling algorithms. Use the default unless you have specific needs.

Determinism and Reproducibility

The number of values you draw from a random source affects reproducibility. If you create an RNG with the same seed and draw the same number of values in the same order, you'll get identical results:

C#
// First run: generate 5 values, then 3 more
var rng1a = RandomSources.Create(42);
rng1a.Fill(new double[5]);  // Consume 5 values
var buffer1 = new double[3];
rng1a.Fill(buffer1);

// Second run: generate all 8 values at once
var rng2a = RandomSources.Create(42);
var buffer2 = new double[8];
rng2a.Fill(buffer2);

// The last 3 values from buffer2 match buffer1
// buffer2[5], buffer2[6], buffer2[7] == buffer1[0], buffer1[1], buffer1[2]

This means that if you change how many values you generate in one part of your code, all subsequent random numbers will be different. For strict reproducibility, maintain consistent draw counts across runs.

Distribution sampling preserves the reproducibility guarantees of the underlying random source. If you create a distribution with the same parameters and use a random source with the same seed, you'll get identical samples:

C#
// First run
var rng1a = RandomSources.Create(42);
var dist1 = new NormalDistribution(100.0, 15.0);
double value1 = dist1.Sample(rng1a);

// Second run with same seed
var rng2a = RandomSources.Create(42);
var dist2 = new NormalDistribution(100.0, 15.0);
double value2 = dist2.Sample(rng2a);

// value1 == value2 (exactly)

For more details on reproducibility guarantees and best practices, see Compatibility and Interop.

Next Steps

Now that you understand how to generate random data:

See Also