The first 90% of the code accounts for the first 90% of the development time. The remaining 10% of the code accounts for the other 90% of the development time. -- Tom Cargill

C# RandomProvider Class

A C# class to provide random numbers for a variety of data types and statistical distributions

Summary

The .NET Framework provides System.Random for generating random numbers which provides uniform random numbers of type System.Double in the range [0,1). However, in practice one almost always needs random numbers in a different range and often of a different type. I created the RandomProvider class as a more useful wrapper for the Random class and I use it all the time.

Example: How to use the RandomProvider Class

public static void TestRandomProvider()
{
   // Random double [0,1)
   Console.WriteLine("Random Double [0,1) = " + RandomProvider.Next());

   // Random Int64 [50,100)
   long ran = RandomProvider.Next(50,100);
   Console.WriteLine("Random Int64 [50,100) = " + ran);

   // Random Boolean
   Console.WriteLine("Random Boolean = " + RandomProvider.NextBoolean());

   // Random DateTime [1/1/1998, 6/1/1998)
   DateTime minDT = DateTime.Parse("1/1/1998");
   DateTime maxDT = DateTime.Parse("6/1/1998");
   DateTime ranDT = RandomProvider.Next(minDT, maxDT);
   Console.WriteLine("Random DateTime [1/1/1998, 6/1/1998) = " + ranDT);

   // Random TimeSpan [3 hours, 7 hours)
   TimeSpan minTs = new TimeSpan(3, 0, 0);
   TimeSpan maxTs = new TimeSpan(7, 0, 0);
   TimeSpan ranTs = RandomProvider.Next(minTs, maxTs);
   Console.WriteLine("Random TimeSpan [3 hrs, 7 hrs) = " + ranTs);

   // Random Normal Deviate
   Console.WriteLine("Normal deviate = " 
      + RandomProvider.NextNormal());

   // Random Exponential Deviate
   Console.WriteLine("Exponential deviate = " 
      + RandomProvider.NextExponential());

}

If you run the previous method you'll get something similar to the following output:

Example: Output

Random Double [0,1) = 0.147433493820687
Random Int64 [50,100) = 69
Random Boolean = False
Random DateTime [1/1/1998, 6/1/1998) = 2/4/1998 4:09:50 AM
Random TimeSpan [3 hrs, 7 hrs) = 03:58:55.0844951
Normal deviate = 0.277308108809515
Exponential deviate = 1.37058190966813

Here's the class:

The C# RandomProvider Class:

using System;

namespace Cambia.CoreLib
{

   /// <summary>
   /// RandomProvider.  Provides random numbers of all data types
   /// in specified ranges.  It also contains a couple of methods
   /// from Normally (Gaussian) distributed random numbers and 
   /// Exponentially distributed random numbers.
   /// </summary>
   public  class RandomProvider
   {
      private static Random m_RNG1;
      private static double m_StoredUniformDeviate;
      private static bool m_StoredUniformDeviateIsGood = false;

      #region -- Construction/Initialization --

      static RandomProvider()
      {
         Reset();
      }
      public static void Reset()
      {
         m_RNG1 = new Random(Environment.TickCount);
      }

      #endregion

      #region -- Uniform Deviates --

      /// <summary>
      /// Returns double in the range [0, 1)
      /// </summary>
      public static double Next()
      {
         return m_RNG1.NextDouble();
      }

      /// <summary>
      /// Returns true or false randomly.
      /// </summary>
      public static bool NextBoolean()
      {
         if (m_RNG1.Next(0,2) == 0)
            return false;
         else
            return true;
      }

      /// <summary>
      /// Returns double in the range [0, 1)
      /// </summary>
      public static double NextDouble()
      {
         double rn = m_RNG1.NextDouble();
         return rn;
      }

      /// <summary>
      /// Returns Int16 in the range [min, max)
      /// </summary>
      public static Int16 Next(Int16 min, Int16 max)
      {
         if (max <= min) 
         {
            string message = "Max must be greater than min.";
            throw new ArgumentException(message);
         }
         double rn = (max*1.0 - min*1.0)*m_RNG1.NextDouble() + min*1.0;
         return Convert.ToInt16(rn);
      }

      /// <summary>
      /// Returns Int32 in the range [min, max)
      /// </summary>
      public static int Next(int min, int max)
      {
         return m_RNG1.Next(min, max);
      }

      /// <summary>
      /// Returns Int64 in the range [min, max)
      /// </summary>
      public static Int64 Next(Int64 min, Int64 max)
      {
         if (max <= min) 
         {
            string message = "Max must be greater than min.";
            throw new ArgumentException(message);
         }

         double rn = (max*1.0 - min*1.0)*m_RNG1.NextDouble() + min*1.0;
         return Convert.ToInt64(rn);
      }

      /// <summary>
      /// Returns float (Single) in the range [min, max)
      /// </summary>
      public static Single Next(Single min, Single max)
      {
         if (max <= min) 
         {
            string message = "Max must be greater than min.";
            throw new ArgumentException(message);
         }

         double rn = (max*1.0 - min*1.0)*m_RNG1.NextDouble() + min*1.0;
         return Convert.ToSingle(rn);
      }

      /// <summary>
      /// Returns double in the range [min, max)
      /// </summary>
      public static double Next(double min, double max)
      {
         if (max <= min) 
         {
            string message = "Max must be greater than min.";
            throw new ArgumentException(message);
         }

         double rn = (max - min)*m_RNG1.NextDouble() + min;
         return rn;
      }

      /// <summary>
      /// Returns DateTime in the range [min, max)
      /// </summary>
      public static DateTime Next(DateTime min, DateTime max)
      {
         if (max <= min) 
         {
            string message = "Max must be greater than min.";
            throw new ArgumentException(message);
         }
         long minTicks = min.Ticks;
         long maxTicks = max.Ticks;
         double rn = (Convert.ToDouble(maxTicks) 
            - Convert.ToDouble(minTicks))*m_RNG1.NextDouble() 
            + Convert.ToDouble(minTicks);
         return new DateTime(Convert.ToInt64(rn));
      }

      /// <summary>
      /// Returns TimeSpan in the range [min, max)
      /// </summary>
      public static TimeSpan Next(TimeSpan min, TimeSpan max)
      {
         if (max <= min) 
         {
            string message = "Max must be greater than min.";
            throw new ArgumentException(message);
         }

         long minTicks = min.Ticks;
         long maxTicks = max.Ticks;
         double rn = (Convert.ToDouble(maxTicks) 
            - Convert.ToDouble(minTicks))*m_RNG1.NextDouble() 
            + Convert.ToDouble(minTicks);
         return new TimeSpan(Convert.ToInt64(rn));
      }

      /// <summary>
      /// Returns double in the range [min, max)
      /// </summary>
      public static double NextUniform()
      {
         return Next();
      }

      /// <summary>
      /// Returns a uniformly random integer representing one of the values 
      /// in the enum.
      /// </summary>
      public static int NextEnum(Type enumType)
      {
         int[] values = (int[])Enum.GetValues(enumType);
         int randomIndex = Next(0, values.Length);
         return values[randomIndex];
      }

      #endregion

      #region -- Exponential Deviates --

      /// <summary>
      /// Returns an exponentially distributed, positive, random deviate 
      /// of unit mean.
      /// </summary>
      public static double NextExponential()
      {
         double dum = 0.0;
         while (dum == 0.0)
            dum=NextUniform();
         return -1.0*System.Math.Log(dum, System.Math.E);
      }

      #endregion

      #region -- Normal Deviates --

      /// <summary>
      /// Returns a normally distributed deviate with zero mean and unit 
      /// variance.
      /// </summary>
      public static double NextNormal()
      {
         // based on algorithm from Numerical Recipes
         if (m_StoredUniformDeviateIsGood)
         {
            m_StoredUniformDeviateIsGood = false;
            return m_StoredUniformDeviate;
         }
         else
         {
            double rsq = 0.0;
            double v1 = 0.0, v2 = 0.0, fac = 0.0;
            while (rsq >=1.0 || rsq == 0.0)
            {
               v1 = 2.0*Next() - 1.0;
               v2 = 2.0*Next() - 1.0;
               rsq = v1*v1 + v2*v2;
            }
            fac = System.Math.Sqrt(-2.0
               *System.Math.Log(rsq, System.Math.E)/rsq);
            m_StoredUniformDeviate = v1*fac;
            m_StoredUniformDeviateIsGood = true;
            return v2*fac;
         }
      }

      #endregion

   }
}