When we think about programming, we usually think about well-intentioned systematic instructions executing when and how we intended. In a digital world of One’s and Zero’s, the idea of randomness is madness. In the real world, randomness is a necessary part of our lives.

When programming, you may be required to generate random values to mimic the real-world system you are trying to emulate. Before we get to code, it is essential to define what we mean by randomness because it can have variations that can lead to consequences we never intended.

What Is Randomness?

The idea of randomness may seem intuitive, but there are two kinds of randomness you should consider when looking to add it to your applications:

  1. Predictability
  2. Uniformity

If you look at Merriam-Webster’s definition of random, you’ll get an explanation mentioning both concepts. The first being about predictability.

lacking a definite plan, purpose, or patternMerriam-Webster

In this case, given our previous set of values, can I predict with high confidence what the next value will be? If I can not, then I have a random process.

Uniformity may be more critical when wanting randomness.

relating to, having, or being elements or events with [a] definite probability of occurrence Merriam-Webster

Uniformity doesn’t look at any single value, but the complete set of our random values. If our collection has a general distribution, then we know our randomness is even and has no biases that could potentially be hurting us.

The following post will look at several ways to generate random values in .NET Core from the perspective of predictability and uniformity. You will also see samples in C# and .NET Core that you can use for your particular use cases.

Random Class

The .NET Framework has shipped with the Random class since the initial releases. The Random class is still here in .NET Core and performs very similarly.

When generating values, you have the option between several outputs:

1.Integers (int) 1.Bytes (byte) 1.Doubles (double)

Accessing values occurs through methods Next, NextBytes, and NextDouble appropriately.

Generate Using Random (Vanilla)

Let’s generate a basic random integer with a C# sample.

var random = new Random();
var value = random.Next();
return value;

With the following output

 Random Using Next: 303739907

This value is random, so your number will likely be different.

The Random class can also take a seed parameter that can impact the number generated by our class.

var random = new Random(seed:1);
var value = random.Next();
return value;

But what is the seed, and what is it doing to our random values?

The Microsoft Documentation explicitly states: “A number used to calculate a starting value for the pseudo-random number sequence.”

If you read that definition carefully, you may notice some words that indicate the presence of predictability, one of our randomness concepts.

The Problem With Random (the class)

While not generally an issue, the class is predictable. Given similar circumstances, we could generate the same random numbers every time with the Random class.

On the Microsoft Documentation site, they show an example where the random class generates the same values when using the same seed

Random numbers from a Random object with seed = 456: 2044805024 1323311594 1087799997 1907260840 179380355 120870348 0.21988117 0.21026556 0.39236514 0.42420498 0.24102703 0.47310170

Random numbers from a Random object with seed = 456: 2044805024 1323311594 1087799997 1907260840 179380355 120870348 0.21988117 0.21026556 0.39236514 0.42420498 0.24102703 0.47310170

While the general distribution may look random (uniformity), this randomness is predictable.

Providing an identical seed value to different Random objects causes each instance to produce identical sequences of random numbers. Microsoft Documentation

Well, what if we want unpredictable randomness?

RandomNumberGenerator & Cryptographic Randomness

.NET Core ships with a RandomNumberGenerator. The specific implementation of the random number generator is RNGCryptoServiceProvider. This class generates cryptographically secure values. In other words, the values it produces are not predictable by you or anyone. They are truly random, but that comes at a cost, as you’ll see.

The most significant difference is that the RandomNumberGenerator and Random is that the former can only generate random bytes. Luckily, bytes are the building blocks of all C# types. Let’s look at an example where we create a random integer that is cryptographically secure.

var random = RandomNumberGenerator.Create();
var bytes = new byte[sizeof(int)]; // 4 bytes
random.GetNonZeroBytes(bytes);
var result = BitConverter.ToInt32(bytes);

The example above uses the sizeof operator to get the size of our target type int, which is 4 bytes. Next, we fill a byte[] with 4 bytes. Finally, we use the BitConverter class to convert our bytes to our target type of int. Try this technique to any kind that BitConverter supports.

I’ve also written a simple static method that gives you Next functionality similar to the one in the Random class.

static int Next(this RandomNumberGenerator generator, int min, int max)
{
    // match Next of Random
    // where max is exclusive
    max = max - 1;

    var bytes = new byte[sizeof(int)]; // 4 bytes
    generator.GetNonZeroBytes(bytes);
    var val = BitConverter.ToInt32(bytes);
    // constrain our values to between our min and max
    // https://stackoverflow.com/a/3057867/86411
    var result = ((val - min) % (max - min + 1) + (max - min + 1)) % (max - min + 1) + min;
    return result;
}

Update In .NET Core 3.0

As Kevin Jones pointed out in a Twitter conversation. The implementation I have above has a slight bias due to the modulus operator. The fourth value generated seems to trend lower. This can be an issue when talking about predictability.

Luckily, Kevin is a cryptographic genius and has submitted an implementation into .NET Core 3.0 that removes the bias. Read his post about the RandomNumberGenerator.

As he says in hist post. You should be able to generate random integers with no bias in .Net Core 3.0 as follows. Note, the method of GetInt32 is a static method off of RandomNumberGenerator and not an instance method.

var min = 1;
var max = 1_000;
var randomNumber = RandomNumberGenerator.GetInt32(min, max);

With an implementation as follows:

public static int GetInt32(int fromInclusive, int toExclusive)
{
    if (fromInclusive >= toExclusive)
        throw new ArgumentException(SR.Argument_InvalidRandomRange);

    // The total possible range is [0, 4,294,967,295).
    // Subtract one to account for zero being an actual possibility.
    uint range = (uint)toExclusive - (uint)fromInclusive - 1;

    // If there is only one possible choice, nothing random will actually happen, so return
    // the only possibility.
    if (range == 0)
    {
        return fromInclusive;
    }

    // Create a mask for the bits that we care about for the range. The other bits will be
    // masked away.
    uint mask = range;
    mask |= mask >> 1;
    mask |= mask >> 2;
    mask |= mask >> 4;
    mask |= mask >> 8;
    mask |= mask >> 16;

    Span<uint> resultSpan = stackalloc uint[1];
    uint result;

    do
    {
        RandomNumberGeneratorImplementation.FillSpan(MemoryMarshal.AsBytes(resultSpan));
        result = mask & resultSpan[0];
    }
    while (result > range);

    return (int)result + fromInclusive;
}

All credit goes to Kevin Jones, for pointing this out and improving the .NET Ecosystem. Check out his full pull request to the .NET Core 3.0 code base.

The Problem With RandomNumberGenerator

Performance is the high cost of gaining cryptographic randomness. When generating 10,000 random values, the difference is magnitudes more expensive.

var random = new Random();
using var crypto = RandomNumberGenerator.Create();
var min = 1;
var max = 10000;

var watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 10000; i++)
{
    random.Next(min, max);
}
watch.Stop();

Console.WriteLine($"Random took {watch.Elapsed.TotalMilliseconds} ms");

watch.Restart();
for (int i = 0; i < 10000; i++)
{
    crypto.Next(min, max);
}
watch.Stop();

Console.WriteLine($"RandomNumberGenerator took {watch.Elapsed.TotalMilliseconds} ms");

The results speak for themselves, showing RandomNumberGenerator trailing Random.

Random RandomNumberGenerator
0.4052 ms 90.7277 ms

The performance issue may not be a deal-breaker for you, and you should evaluate the cost of using RandomNumberGenerator based on your specific needs.

What About Uniformity

Well, this is where things get interesting. I hypothesized that a cryptographic implementation would produce more uniform values over time. Given a range of possible values, they would all have an equal probability if being generated.

private static void GenerateComparisonDistributions()
{
    var random = new Random();
    using var crypto = RandomNumberGenerator.Create();
    var min = 1;
    var max = 10000;

    const string filename = "random_distribution.csv";

    if (File.Exists(filename))
    {
        File.Delete(filename);
    }

    using var file = File.OpenWrite(filename);
    var encoding = new UTF8Encoding(true);

    file.Write(encoding.GetBytes("random,cryptography\n"));
    for(var i =1; i < 10000; i++)
    {
        var row = encoding.GetBytes($"{random.Next(min, max)},{crypto.Next(min, max)}\n");
        file.Write(row);
    }

    file.Flush();
}

The experiment I ran generated 10,000 random values between 1 and 10,000. If the series averages 5,000, then we know we have a uniform distribution. So what was the outcome?

Random RandomNumberGenerator
4994.29863 4973.205721

Remember, the distribution of both generator values are random, but both averages are converging on 5,000 with an insignificant difference.

Random has the exact random uniformity as RandomNumberGenerator.

If you are not concerned about predictability, you should use Random. It is significantly more performance, with no compromise in uniformity.

Conclusion

Randomness in a non-random domain like computer programming is a difficult problem to solve. Luckily, we have the fine folks working on the .NET Framework giving us tools to make it easier.

Remember to consider both predictability and uniformity when choosing your random implementation, as it can make a big difference in the areas of security and performance.

If you need to generate a cryptographically secure (unpredictable) value, remember that the combination of RandomNumberGenerator and BitConverter can go a long way to help you. Ultimately, both Random and RandomNumberGenerator will give you the same uniform output.