Press "Enter" to skip to content

Create better random numbers in C#

Last updated on June 29, 2019

I was digging around in an old program I wrote and saw a method I wrote for random number generation. It reminded me that some programmers may not realize the problem with the normal random number code in .Net – and how the numbers are actually predictable.

If you use the System.Random class, and create new instance of it with every use, you’ll get the same numbers, in the same order.

To see the problem, run a program that uses a System.Random object to generate 10 random numbers. Write those numbers down, and then stop the program. Now, run the program again. You’ll see that it gives you the same 10 numbers, in the exact same order.

This happens because the random number generator initializes itself with a seed value. But it’s the same seed value each time you run the program. So, you get the same results. This is often called “pseudo-random” numbers.

A better method is to use the random number generator in the cryptography library of the .Net framework. It may not be a 100%, true random number (if you want to get into high-level mathematics), but it’s good enough for almost anything most of us will ever need.

Here’s the code I used to create a random number between two numbers (inclusive):

RandomNumber.cs class

using System;
using System.Security.Cryptography;
namespace FDL.Library.Numeric
{
    public static class RandomNumber
    {
        private static readonly RNGCryptoServiceProvider _generator = new RNGCryptoServiceProvider();
        public static int Between(int minimumValue, int maximumValue)
        {
            byte[] randomNumber = new byte[1];
            _generator.GetBytes(randomNumber);
            double asciiValueOfRandomCharacter = Convert.ToDouble(randomNumber[0]);
            // We are using Math.Max, and substracting 0.00000000001, 
            // to ensure "multiplier" will always be between 0.0 and .99999999999
            // Otherwise, it's possible for it to be "1", which causes problems in our rounding.
            double multiplier = Math.Max(0, (asciiValueOfRandomCharacter / 255d) - 0.00000000001d);
            // We need to add one to the range, to allow for the rounding done with Math.Floor
            int range = maximumValue - minimumValue + 1;
            double randomValueInRange = Math.Floor(multiplier * range);
            return (int)(minimumValue + randomValueInRange);
        }
    }
}

The reason this method has minimumValue and maximumValue parameters was because I needed to be able to generate a random number between 5 and 10 (for example).

So, if you’re creating a .Net game, or any other .Net program where you want to have something randomized, use a method like this to give you better random results.

EDIT: 28 Apr 2014

I moved the generator variable to a static class-level variable, so we don’t keep creating new instances of this object – or need to worry about disposing them.

13 Comments

  1. radia
    radia February 19, 2015

    thank you, really helping me 🙂

  2. Allan Becker
    Allan Becker June 16, 2015

    Hi,

    Thanks for this process.  I need to use a RNG to access existing array elements so my question is this: Is “0” (zero) a valid low range value?

  3. Allan
    Allan June 17, 2015

    I wrote a little C# app to test this with zero and found out for myself that it does indeed work.  Thanks for the code, it will make my language learning flashcard selection a little less predictable!  Good work.

    • Scott Lilly
      Scott Lilly June 18, 2015

      Hi,

      I’m sorry I couldn’t answer. I didn’t have Internet until last night. But you figured out the best way to see if it works. 🙂 Good luck with your language learning program!

  4. infocydetech
    infocydetech December 10, 2015

    Thanks, this is a simpler approach than what I was using and so far it is working well for me.  Thanks for taking the time to post your code.

  5. iQuori
    iQuori October 16, 2016

    Nice.  Also, if you know that you need to generate say 10,000 random number in advance (so they are available instantly), it is very easy to change the above code to return an array of 10,000 or whatever random numbers all at once.  This routine returns ‘amount’ of random numbers.  It is similar except I changed the maximumValue to be exclusive… so (zero to maximumValue – 1):

    public static int[] Multiple(int maximumValue, int amount)
    {
        var randomNumbers = new int[amount];
        byte[] randomNumber = new byte[amount];
        _generator.GetBytes(randomNumber);
        for (int i = 0; i < amount; i++)
        {
            double asciiValueOfRandomCharacter = Convert.ToDouble(randomNumber[i]);
            double multiplier = Math.Max(0, (asciiValueOfRandomCharacter / 255d) - 0.00000000001d);
            double randomValueInRange = Math.Floor(multiplier * maximumValue);
            randomNumbers[i] = (int)(randomValueInRange);
        }
        return randomNumbers;
    }

     

    • Scott Lilly
      Scott Lilly October 17, 2016

      That would be nice to run, to verify the random number distribution is really random. That’s always a good idea, when using a random number generation function. 🙂

  6. Magnus
    Magnus July 26, 2018

    The method seems to generate biased numbers.

    If I try to generate numbers between 1 and 10, the chance of getting 5 is 9.77% and the chance of getting 6 is 10.17% (10 million samples)

    • Scott Lilly
      Scott Lilly July 30, 2018

      That is interesting. I tried it and saw the same results as you. However, when I first wrote this, I know I did a similar test and saw more-balanced results. I’m going to try some things with it this weekend, to see if I can track down what is happening.

  7. Puterdo Borato
    Puterdo Borato March 14, 2019

    Could you tell me if you see anything wrong with this code? On tens of millions of runs I can see that numbers distribute not absolutely evenly, but deviations are within 1%. Do we expect any better from software pseudo-random generator?

        public static class RandomNumber
        {
            private static readonly RNGCryptoServiceProvider provider = new RNGCryptoServiceProvider();
            public static int Next(int min, int max)
            {
                // get delta that will be used for modulo operation
                var d = max - min + 1;
                // get "random" bytes into buffer
                var randomNumber = new byte[4];
                provider.GetBytes(randomNumber);
                // convert bytes into number 
                // then get modulo by delta from first step
                // and add lower limit (min) to make the number fit within desired range
                int v = (Math.Abs(BitConverter.ToInt32(randomNumber, 0)) % d) + min;
                return v;
            }
        }
    

     

Comments are closed.