2 The C# Built-In Random Number Generator
Tải bản đầy đủ - 0trang
Random Numbers
285
on referred to simply as random numbers, are chosen with equal probability from
a finite set of allowed int32 values. The current implementation of the Random()
class is based on Knuth’s subtractive random number generator algorithm [50] which
has an alleged period of about 232 . This period is usually considered good enough
for most practical applications but is definitely not recommended for cryptography
or other applications that require a substantial degree of security such as password
generation or encrypted telecommunications.
The random number generator provided by the .NET Framework starts from a seed
value and can be overloaded in two different ways. By default, the parameterless constructor of the Random() class uses the computer’s internal system clock to generate
its own seed value whereas the parameterized constructor can accept specific seed
values from within the allowed range of Int32 integers. Because the system clock is
continuously changing in value, using a parameterless constructor will therefore initiate a different randomized numerical sequence every time it is called up. However,
since the clock has finite resolution, using the parameterless constructor to create
different Random() objects in close succession can create random number generators that produce identical randomized numerical sequences. This problem can be
avoided by creating one Random() object to generate many random numbers over
time, instead of repeatedly creating several new Random() objects to generate one
random number at a time. However, some of these features of the random number
generator can sometimes be useful. For example, if you want to repeat the same
sequence of numbers, you can set the seed state during instantiation by passing the
same integer value to the constructor.
Once this Random() object has been created we can call the Next() method to
produce a random number. The Next() method can be overloaded in three different
ways. Without any input parameters, the Next() method returns a random number
anywhere from 0 to the maximum value of int32.Max which is 2, 147, 483, 647. The
Next(int32) method returns a nonnegative random number from 0 to the Int32
integer value specified by the single input parameter. Lastly, the Next(Int32,Int32
) method returns a random number from anywhere within the allowed Int32 integer
range specified by the two input parameters.
The Random.NextBytes() method fills the elements of a specified array of bytes
with random bytes selected anywhere from 0 to 255. Therefore, the Random() class
can also be used to populate a byte array with random bytes.
Finally, the Random.NextDouble() method is not overloaded and only returns a
double-precision floating point from the interval [0, 1]. However, by properly adjusting the call to the Random.NextDouble() method this way
a + (b - a)*rnd.NextDouble();
where rnd is an object of the Random() class, we can obtain random numbers from
anywhere within the numerical interval specified by [a, b). Some actual examples
are listed below in order to illustrate the implementation of all these ideas.
© 2010 by Taylor and Francis Group, LLC
286
Numerical Methods, Algorithms and Tools in C#
static void TestRNG()
{
int seed = 234;
int min = -55;
int max = 25;
Console.WriteLine("Testing 6 random int32 from 0 to int32.MAX");
Random randObj1 = new Random(seed);
for (int j = 0; j < 6; j++)
Console.Write("{0,11} ", randObj1.Next());
Console.WriteLine("Testing 6 random int32 from 0 to 25");
Random randObj2 = new Random(seed);
for (int j = 0; j < 6; j++)
Console.Write("{0,11} ", randObj2.Next(max));
Console.WriteLine("Testing 6 random int32 from -55 to 25");
Random randObj3 = new Random(seed);
for (int j = 0; j < 6; j++)
Console.Write("{0,11} ", randObj3.Next(min, max));
Console.WriteLine("Testing 5 random bytes: 0 to 255");
Random randObj4 = new Random();
Byte[] b = new Byte[10];
randObj4.NextBytes(b);
Console.WriteLine("The Random bytes are: ");
for (int i = 0; i < 10; i++)
{
Console.Write(i);
Console.Write(":");
Console.WriteLine(b[i]);
}
Console.WriteLine("Testing 6 random doubles from 0 to 1");
Random randObj5 = new Random(seed);
for (int j = 0; j < 6; j++)
Console.WriteLine("{0,11} ",randObj5.NextDouble());
Console.WriteLine("Testing 6 random doubles from -55 to 25");
Random randObj6 = new Random(seed);
for (int j = 0; j < 6; j++)
Console.WriteLine("{0,11}",(min+(max-min)*randObj6.NextDouble()));
}
A useful application of the random number generator class is a utility that pertains
to generating passwords that can contain random letters or characters in addition to
random numbers. Since random numbers are generated by default, we must create
a new method that will contain this functionality. The implementation of a random
password generator is given below.
static string CreateRandomPassword(int passwordLength)
{
string allowedChars =
"abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ0123456789";
char[] chars = new char[passwordLength];
Random rd = new Random();
© 2010 by Taylor and Francis Group, LLC
Random Numbers
287
for (int i = 0; i < passwordLength; i++)
{
chars[i] = allowedChars[rd.Next(0,allowedChars.Length)];
}
return new string(chars);
}
static void TestRandomPassword()
{
Console.WriteLine("Testing for generation of random passwords");
for (int i = 0; i < 6; i++)
{
Console.WriteLine("Password {0}={1}",i,CreateRandomPassword(10));
Thread.Sleep(2000);
}
Console.ReadLine();
}
Microsoft also provides an encryption class, called RNGCryptoServiceProvider, for
use in developing secure applications, including allegedly secure random number
generators. However, at the time of this writing there was considerable debate over
the Internet as to the level of security that this class actually is capable of providing.
Since technology advances so rapidly, you might want to double check the current
reports on this topic when you read this to see what the current security level status
of this is really like. In any event, below are two additional examples illustrating the
use of this Microsoft encryption class.
public static void BetterRandomString()
{
// create a stronger hash code using RNGCryptoServiceProvider
byte[] random = new byte[64];
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
// populate with random bytes
rng.GetBytes(random);
// convert random bytes to string
string randomBase64 = Convert.ToBase64String(random);
// display
Console.WriteLine("Random string: {0}\r\n ",randomBase64);
}
public static string CreateRandomEncryptedPassword(int PasswordLen)
{
String allowedChars =
"abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ0123456789";
Byte[] randomBytes = new Byte[PasswordLen];
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
rng.GetBytes(randomBytes);
char[] chars = new char[PasswordLen];
int allowedCharCount = allowedChars.Length;
for (int i = 0; i < PasswordLen; i++)
{
chars[i] = allowedChars[(int)randomBytes[i]%allowedCharCount];
}
return new string(chars);
}
© 2010 by Taylor and Francis Group, LLC
288
Numerical Methods, Algorithms and Tools in C#
static void TestRandomEncryptedPassword()
{
for (int i = 0; i < 6; i++)
{
Console.WriteLine("Encrypted Password {0} = {1}",
i, CreateRandomEncryptedPassword(10));
Thread.Sleep(2000);
}
Console.ReadLine();
}
By default, the random number methods within the Random() class generate numbers with replacement. This means that a particular random number may get generated repeatedly. If you do not want a set of randomly generated numbers to contain
any duplication of elements, you need to code the process of generating random
numbers accordingly. There are a number of ways to obtain a list of unique random
numbers containing no repetition. For pedagogical reasons, I thought it would be
instructive to present at least three different methods to generate random numbers
having no duplicate values.
In the first and perhaps simplest method, you can just put the values you want
randomized (numbers, strings, objects,. . .) in an array and then shuffle the array by
randomly exchanging the array elements. This procedure, using the Knuth-FisherYates shuffle algorithm, will produce a randomized list of unique numerical values
as illustrated in the example below.
static int[] UniqueRandom1(int max)
{
Random rand = new Random();
int[] array = new int[max];
// Initialize the array to integers from 0 to max
for (int i = 0; i < array.Length; i++)
{ array[i] = i; }
for (int i = array.Length - 1; i > 0; i--)
{
int randomPosition = rand.Next(i+1);
int temp = array[i];
array[i] = array[randomPosition];
array[randomPosition] = temp;
}
return array;
}
static void TestUniqueRandom1()
{
int size = 15;
int[] myData = UniqueRandom1(size);
Console.WriteLine("Testing unique random numbers version 1\n");
for (int i = 0; i < myData.Length; i++)
{ Console.WriteLine("myData[{0}] = {1}", i, myData[i]); }
}
© 2010 by Taylor and Francis Group, LLC
Random Numbers
289
As an alternate method for obtaining a list of unique random numbers you can also
start by first creating an ArrayList object to hold the numbers. Next, generate a random number within range of the desired values and add this number to the list. Then
loop repeatedly generating another random numbers and checking whether each of
them is already contained in the list. If the number is not found in the list, then add
that number to the list. Otherwise, if the number is found in the list then ignore it
and get another random number. Repeat this process until the list has been completely filled. The random numbers left in the list should all be unique without any
repetition. The code below implements this idea.
static ArrayList UniqueRandom2(int max)
{
// Create an ArrayList object that will hold the numbers
ArrayList lstNumbers = new ArrayList();
// Create an object from the Random() class that
// will be used to generate random numbers
Random rndNumber = new Random();
// Get a random number between 0 and the max requested value
int number = rndNumber.Next(0, max);
// Add this first random number to the list
lstNumbers.Add(number);
// Setup a number counter to keep track of the
// numbers being added to the list
int count = 0;
do // Repeatedly...
{
// generate a random number between 0 and the max
number = rndNumber.Next(0, max);
// If the newly generated number in not yet in the list...
if (!lstNumbers.Contains(number))
{
lstNumbers.Add(number); // ... add it
count++;
// and increase the counter
}
} while (count <= max);
// Once the list is built, return it
return lstNumbers;
}
static void TestUniqueRandom2()
{
const int Total = 15;
ArrayList lstNumbers = UniqueRandom2(Total);
for (int i = 0; i < lstNumbers.Count; i++)
Console.WriteLine("x[{0}] = {1}", i, lstNumbers[i]);
Console.ReadLine();
}
As a final, but perhaps much subtler, example of a method for obtaining a list of
unique random numbers consider the following algorithm and its subsequent implementation shown below. First, a generic list is created and loaded with the values
© 2010 by Taylor and Francis Group, LLC
290
Numerical Methods, Algorithms and Tools in C#
you want randomized (numbers, strings, objects,. . .). Individual elements from this
list are then randomly selected and added to another list. After an element has been
selected and added to the second list, it is removed from the original list so that it
cannot be selected again. This process is repeated until there are no more elements
to select from in the original list. An implementation of this algorithm in C# is given
below.
public static List
GetUniqueRandomList(int size)
{
List list = new List();
Random randomGenerator = new Random();
List range = new List();
for (int i = 0; i < size; i++)
range.Add(i);
while (range.Count > 0)
{
int item = range[randomGenerator.Next(range.Count)];
list.Add(item);
range.Remove(item);
}
return list;
}
static void TestUniqueRandom3()
{
List iList = new List();
iList = GetUniqueRandomList(15);
foreach (int i in iList)
{
Console.WriteLine(i);
}
Console.ReadLine();
}
10.3 Other Random Number Generators
There are many different types of random number generators [51] but the Linear
Congruential Generator (LCG) represents one of the oldest and best-known pseudorandom number generator algorithms [50]. The linear congruential generator is
defined by the recurrence relation:
Xn+1 = (aXn + c) mod m
where Xn is the sequence of pseudorandom values, m > 0 is called the modulus, a
where 0 < a < m is called the multiplier, c where 0 ≤ c < m is called the increment,
© 2010 by Taylor and Francis Group, LLC
Random Numbers
291
and X0 where 0 ≤ X0 < m is called the seed or start value. Together these values
specify the LCG. The period of the LCG is at most m, and largely depends on the
value chosen for the other variables. The LCG will have a full period if and only if
c and m are relatively prime, a − 1 is divisible by all prime factors of m and a − 1
is a multiple of 4 if m is a multiple of 4. While capable of producing decent pseudorandom numbers, LCGs are extremely sensitive to the choice of the coefficients c,
m, and a. The most efficient linear congruent generators, including the one built into
Visual C#, reportedly have an m equal to a power of 2, most often around m = 232 .
Perhaps the most famous and controversial one of all the random number generators is the Mersenne Twister published in 1998 by Matsumoto and Nishimura [52,
53], which has a reportedly proven period of 219937 − 1. In practice, however, there is
little reason to use anything larger as most applications do not require 219937 unique
combinations (219937 ≈ 4.3 × 106001). Nevertheless, the Mersenne Twister algorithm
has received some criticism in the computer science field. Critics claim that while
the Mersenne Twister is good at generating random numbers, it is not very elegant
and is overly complex to implement. As an alternative to the Mersenne Twister, critics have proposed a simpler complementary multiply-with-carry generator [54] with
an alleged period of 1033000 which claims to be significantly faster and maintains
better or equal randomness. In any event, a C# implementation of the Mersenne
Twister random number generator algorithm is given below along with sample code
to demonstrate how to use it. Those readers wishing to learn more about how this
algorithm works are referred to the original paper [52]. Those readers looking for an
alternative to either the Visual C# or the Mersenne Twister random number generators are referred to another website [55] that maintains a very good list of random
number generators including code in C/C++ and Fortran that can be downloaded
directly to your computer.
public class MersenneTwister
{
// Class MersenneTwister generates random numbers
// from a uniform distribution using the Mersenne
// Twister algorithm.
private const int N = 624;
private const int M = 397;
private const uint MATRIX_A = 0x9908b0dfU;
private const uint UPPER_MASK = 0x80000000U;
private const uint LOWER_MASK = 0x7fffffffU;
private const int MAX_RAND_INT = 0x7fffffff;
private uint[] mag01 = {0x0U, MATRIX_A};
private uint[] mt = new uint[N];
private int
mti = N+1;
public MersenneTwister()
{ init_genrand( (uint)DateTime.Now.Millisecond); }
public MersenneTwister( int seed )
{
init_genrand( (uint)seed );
}
© 2010 by Taylor and Francis Group, LLC
292
Numerical Methods, Algorithms and Tools in C#
public MersenneTwister( int[] init )
{
uint[] initArray = new uint[init.Length];
for ( int i = 0; i < init.Length; ++i )
initArray[i] = (uint)init[i];
init_by_array( initArray, (uint)initArray.Length);
}
public static int MaxRandomInt
{ get { return 0x7fffffff; } }
public int Next()
{ return genrand_int31(); }
public int Next( int maxValue )
{ return Next( 0, maxValue ); }
public int Next( int minValue, int maxValue )
{
if ( minValue > maxValue )
{
int tmp = maxValue;
maxValue = minValue;
minValue = tmp;
}
return (int)(Math.Floor((maxValue-minValue+1)*genrand_real1()+
minValue));
}
public float NextFloat()
{ return (float) genrand_real2(); }
public float NextFloat( bool includeOne )
{
if ( includeOne )
{
return (float) genrand_real1();
}
return (float) genrand_real2();
}
public float NextFloatPositive()
{ return (float) genrand_real3(); }
public double NextDouble()
{ return genrand_real2(); }
public double NextDouble( bool includeOne )
{
if ( includeOne )
{
return genrand_real1();
}
return genrand_real2();
}
© 2010 by Taylor and Francis Group, LLC
Random Numbers
293
public double NextDoublePositive()
{ return genrand_real3(); }
public double Next53BitRes()
{ return genrand_res53(); }
public void Initialize()
{ init_genrand((uint)DateTime.Now.Millisecond); }
public void Initialize( int seed )
{ init_genrand( (uint)seed ); }
public void Initialize( int[] init )
{
uint[] initArray = new uint[init.Length];
for ( int i = 0; i < init.Length; ++i )
initArray[i] = (uint)init[i];
init_by_array( initArray, (uint)initArray.Length );
}
private void init_genrand( uint s)
{
mt[0]= s & 0xffffffffU;
for (mti=1; mti
{
mt[mti] =
(uint)(1812433253U*(mt[mti-1]ˆ(mt[mti-1]>>30))+mti);
mt[mti] &= 0xffffffffU;
}
}
private void init_by_array(uint[] init_key, uint key_length)
{
int i, j, k;
init_genrand(19650218U);
i=1; j=0;
k = (int)(N>key_length ? N : key_length);
for (; k>0; k--)
{
mt[i] = (uint)((uint)(mt[i]ˆ((mt[i-1]ˆ(mt[i-1]>>30))*1664525U
))+init_key[j]+j);
mt[i] &= 0xffffffffU;
i++; j++;
if (i>=N) { mt[0] = mt[N-1]; i=1; }
if (j>=key_length) j=0;
}
for (k=N-1; k>0; k--)
{
mt[i] = (uint)((uint)(mt[i] ˆ ((mt[i-1] ˆ (mt[i-1] >> 30)) *
1566083941U))- i);
mt[i] &= 0xffffffffU;
i++;
if (i>=N) { mt[0] = mt[N-1]; i=1; }
}
mt[0] = 0x80000000U;
}
© 2010 by Taylor and Francis Group, LLC
294
Numerical Methods, Algorithms and Tools in C#
uint genrand_int32()
{
uint y;
if (mti >= N)
{
int kk;
if (mti == N+1)
init_genrand(5489U);
for (kk=0;kk
{
y = (mt[kk]&UPPER_MASK)|(mt[kk+1]&LOWER_MASK);
mt[kk] = mt[kk+M] ˆ (y >> 1) ˆ mag01[y & 0x1U];
}
for (;kk
{
y = (mt[kk]&UPPER_MASK)|(mt[kk+1]&LOWER_MASK);
mt[kk] = mt[kk+(M-N)] ˆ (y >> 1) ˆ mag01[y & 0x1U];
}
y = (mt[N-1]&UPPER_MASK)|(mt[0]&LOWER_MASK);
mt[N-1] = mt[M-1] ˆ (y >> 1) ˆ mag01[y & 0x1U];
mti = 0;
}
y = mt[mti++];
y ˆ= (y >> 11);
y ˆ= (y << 7) & 0x9d2c5680U;
y ˆ= (y << 15) & 0xefc60000U;
y ˆ= (y >> 18);
return y;
}
private int genrand_int31()
{ return (int)(genrand_int32()>>1); }
double genrand_real1()
{ return genrand_int32()*(1.0/4294967295.0); }
double genrand_real2()
{ return genrand_int32()*(1.0/4294967296.0); }
double genrand_real3()
{return (((double)genrand_int32())+0.5)*(1.0/4294967296.0);}
double genrand_res53()
{
uint a=genrand_int32()>>5, b=genrand_int32()>>6;
return(a*67108864.0+b)*(1.0/9007199254740992.0);
}
}
static void TestMersenneTwister()
{
MersenneTwister randGen = new MersenneTwister();
Console.WriteLine( "100 uniform random integers in [0,{0}]:",
MersenneTwister.MaxRandomInt);
int i;
© 2010 by Taylor and Francis Group, LLC
Random Numbers
295
for (i = 0; i < 100; ++i)
{
Console.Write("{0} ",randGen.Next());
if ( i%5 == 4 ) Console.WriteLine("");
}
Console.WriteLine("100 uniform random doubles in [0,1]:");
for ( i = 0; i < 100; ++i )
{
Console.Write("{0} ",randGen.NextDouble().ToString("F8"));
if ( i%5 == 4 ) Console.WriteLine("");
}
Console.WriteLine("Press ENTER to quit");
Console.ReadLine();
}
10.4 True Random Number Generators
As we have seen, C# comes with its own built-in pseudo-random number generator
that has an alleged period of about 232 which is adequate for most practical but
non-critically secure applications. More recently, however, far more robust pseudorandom number generators have been developed. The most prominent among these
are perhaps the Mersienne Twister [52, 53] with an alleged period of 219937 − 1 and
the multiply-with-carry generator [54] with an reported period of about 1033000. With
such huge periods, these pseudo-random number generators have been very useful
in a wide range of advanced applications, such as encrypted telecommunications,
where security issues are a major concern.
True random numbers, however, can only be obtained from observing and recording naturally occurring random physical phenomena. As a result, a few websites
with links and interfaces to certain natural events have appeared on the Internet and
can therefore generate true random numbers for us. At the time of this writing, I have
compiled a list of some of these websites below and included a brief description of
the nature with which they can collect true random numbers from naturally occurring
physical events.
• Fourmilab is located in Switzerland [56] and offers free genuine random numbers by timing successive pairs of radioactive decays detected by a GeigerMăuller tube interfaced to a computer. Because of the radioactive nature of
these random numbers, they call them Hot Bits. Since the HotBits generation
hardware produces data at a modest rate of about 100 bytes per second, requests are filled from an inventory of pre-built HotBits. You order up your
serving of HotBits by filling out a request form on their webpage and specifying how many random bytes you want and in which format you would like
them delivered. Your request is relayed to the HotBits server, which flashes
© 2010 by Taylor and Francis Group, LLC