Random Numbers in C#

.NET Musings

Random Numbers in C#

When is a Random number not truly Random? When it's generated through the System.Random class. This class takes an optional seed value, but returns essentially a predefined order of seemingly random numbers.

The (non)Random Class

To prove this case, run the following code which uses the default constructor of the Random class:

  1: while (true)
  2: {
  3:   Random r = new Random();
  4:   Console.Write(r.Next() + ":");
  5:   Console.WriteLine(r.Next());
  6:   string test = Console.ReadLine();
  7:   if (test=="q") { return; }
  8: }
  9: 
The generated results look will like something like this:
1161903818:2056898737

1963702974:413464101

1466546674:234618278

Now, change the code to include a seed value in the constructor for the Random class:


  1: while (true)
  2: {
  3:   Random r = new Random(1234);
  4:   Console.Write(r.Next() + ":");
  5:   Console.WriteLine(r.Next());
  6:   string test = Console.ReadLine();
  7:   if (test=="q") { return; }
  8: }
  9: 

You will see that the Randomness isn't there anymore. The results from this run are far from Random!
857109877:1923929452

857109877:1923929452

857109877:1923929452

This tells us that the parameter-less constructor is using some internal generation algorithm to seed to class. In fact, if we fire of a series of New/Next commands within the loop, we will see a set number of the generations will repeat themselves before rotating to a new set. The seed is obviously generated from CPU ticks or the system clock or some other factor of time.


The nice part of this class is that you can pass in a Min and a Max to the Next method. If you don't need a cryptographically strong random number, this should work fine.


Using RNGCryptoServiceProvider for True(r) Randomness


The .Net Framework comes with one concrete implementation of System.Security.Cryptography.RandomNumberGenerator, and that is the RNGCryptoServiceProvider. While I don't want to get into a debate on how truly random the RNG Provider is, it is generally accepted as a cryptographically strong Random Number Generator. Please check with your security group prior to generating any code for security measures to make sure you are within the security teams guidelines.


There is a bit more code involved in using this class, but it is well worth the extra effort. The code looks like this:


  1: while (true)
  2: {
  3:   int max = 10000;
  4:   int min = 1;
  5:   RNGCryptoServiceProvider c = new RNGCryptoServiceProvider();
  6:   for (int x = 0; x < 20; x++)
  7:   {
  8:     // Create a byte array to hold the random values.
  9:     byte[] randomNumber = new byte[4];
 10:     // Fill the array with a random value.
 11:     c.GetBytes(randomNumber);
 12:     //Convert to a number
 13:     int result = Math.Abs(BitConverter.ToInt32(randomNumber, 0));
 14:     Console.WriteLine(result % max + min);
 15:   }
 16:   string test = Console.ReadLine();
 17:   if (test == "q") { return; }
 18: }
 19: 

The byte array length determines "how many cryptographically strong random bytes are produced" (VS2008 Documentation). To create a Random Number within a range of values, look at Line 15 of the code sample, where it is converting the full random number generated to a number between 1 and 10,000.


Hopefully this clears up the difference between the Random class and the RNGCryptoServiceProvider class, and the degree of randomness.


Happy Coding!




Comments (3) -

  • Anonymous

    8/22/2009 3:43:08 AM |

    The effect would be small with a small number like 10, since 10 divides into 2^31 214,748,364 times with a remainder of 8. That means there are 214,748,365 chances to get 1 through 8 and only 214,748,364 chances to get 9 or 10. Not very significant, but could easily become so if you were generating random numbers in the millions range, or a ton of random numbers, or needed high quality randomness.

  • Philip Japikse, MCSD, MCDBA, CSM

    8/22/2009 8:43:08 AM |

    I'm not a mathemetician, so I tested my code the only way I see fit, with a unit test.  I ran the generator 100,000 times to generate a number between 1 and 10.  I then increment a counter for each result.  The results I got the first run is this:
    1:9914
    2:9893
    3:9831
    4:10142
    5:10016
    6:10041
    7:10073
    8:10132
    9:10078
    10:9880

    The second time is this:
    1:10072
    2:9870
    3:10041
    4:10032
    5:10017
    6:9971
    7:9905
    8:10115
    9:9968
    10:10009

    I guess I just don't understand your argument, because these don't look skewed towards one end or the other.

    I'm not sure of the use case you are trying to apply this to, but for line of business applications (where I earn my living), this is random enough (note that I said true"r" random numbers).  If you need absolute true random numbers, it's going to be difficult to achieve with a computer.

  • Thomas S. Trias

    8/22/2009 1:43:08 PM |

    If you want numbers between min and max inclusive, the calculation is ((result % (max - min + 1)) + min).  This can be factored using Euler's theorems into multiple modulus operations, but since (max - min + 1) can be pre-calculated, this is the easiest method.

    Unfortunately, if max + min (in your case - max - min + 1 in mine) doesn't evenly divide 2^31 (since you effectively zero out the sign bit), you are going to get some skew in your distribution towards the numbers near the beginning of the sequence.  Also, taking the absolute value makes 0 / min less likely, since there is only one representation of zero.

    Does anyone know of a better way to use the RNGCryptoServiceProvider to get a uniform distribution distribution of numbers besides throwing away some results?  In the above case, the best I can come up with is to generate 2 bytes of random data, zero out bits 15 and 14 (since 16384 evenly divides 65536, and since the cryptographically secure random number should satisfy the next bit test, we still have a uniform distribution at that point), and then discard any results between 10001 and 16383 inclusive.

    I thought there might be a way to reuse the leftover bits from the previous random calculation, but that still doesn't resolve the issue of what to do with the numbers between 10001 and 16383.  If we generate 2560256 bytes worth of data, is there a good way to evenly distribute the results over 0 to 10000?

Comments are closed
Managed Windows Shared Hosting by OrcsWeb