6.7. Case Study: Random Number
Generation
We now take a
brief and hopefully entertaining diversion into a popular programming
application, namely simulation and game playing. In this and the next section,
we develop a game-playing program that includes multiple functions. The program
uses many of the control statements and concepts discussed to this point.
The element of chance can be
introduced into computer applications by using the C++ Standard Library function
rand.
Consider the following statement:
The function rand
generates an unsigned integer between 0 and RAND_MAX (a symbolic constant defined in the
<cstdlib> header file). The value of
RAND_MAX must be at least 32767—the maximum
positive value for a two-byte (16-bit) integer. For GNU C++, the value of
RAND_MAX is 2147483647; for Visual Studio, the value of
RAND_MAX is 32767. If rand truly produces
integers at random, every number between 0 and RAND_MAX has an equal
chance (or probability) of being chosen
each time rand is called.
The range of values produced directly by the function
rand often is different than what a specific
application requires. For example, a program that simulates coin tossing might
require only 0 for "heads" and 1 for "tails." A program that simulates rolling a
six-sided die would require random integers in the range 1 to 6. A program that
randomly predicts the next type of spaceship (out of four possibilities) that
will fly across the horizon in a video game might require random integers in the
range 1 through 4.
Rolling a Six-Sided Die
To demonstrate rand, let us develop a program (Fig. 6.7) to
simulate 20 rolls of a six-sided die and print the value of each roll. The
function prototype for the rand function is in
<cstdlib>. To produce integers in the range
0 to 5, we use the modulus operator (%) with rand as
follows:
This is called
scaling. The number 6 is called the scaling factor. We then shift the range of numbers produced by adding 1
to our previous result. Figure 6.7 confirms that the results
are in the range 1 to 6.
Fig. 6.7. Shifted, scaled integers produced by 1 +
rand() % 6.
1 // Fig. 6.7: fig06_07.cpp
2 // Shifted and scaled random integers.
3 #include <iostream>
4 using std::cout;
5 using std::endl;
6
7 #include <iomanip>
8 using std::setw;
9
10 #include <cstdlib> // contains function prototype for rand
11 using std::rand;
12
13 int main()
14 {
15 // loop 20 times
16 for ( int counter = 1; counter <= 20 ; counter++ )
17 {
18 // pick random number from 1 to 6 and output it
19 cout << setw( 10 ) << ( 1 + rand() % 6 );
20
21 // if counter is divisible by 5, start a new line of output
22 if ( counter % 5 == 0 )
23 cout << endl;
24 } // end for
25
26 return 0; // indicates successful termination
27 } // end main
|
6 6 5 5 6
5 1 1 5 3
6 6 2 4 2
6 2 3 4 1
|
Rolling a Six-Sided Die 6,000,000
Times
To show that the numbers produced by function rand
occur with approximately equal likelihood, Fig. 6.8
simulates 6,000,000 rolls of a die. Each integer in the range 1 to 6 should
appear approximately 1,000,000 times. This is confirmed by the output window at
the end of Fig.
6.8.
Fig. 6.8. Rolling a six-sided die 6,000,000
times.
1 // Fig. 6.8: fig06_08.cpp
2 // Roll a six-sided die 6,000,000 times.
3 #include <iostream>
4 using std::cout;
5 using std::endl;
6
7 #include <iomanip>
8 using std::setw;
9
10 #include <cstdlib> // contains function prototype for rand
11 using std::rand;
12
13 int main()
14 {
15 int frequency1 = 0; // count of 1s rolled
16 int frequency2 = 0; // count of 2s rolled
17 int frequency3 = 0; // count of 3s rolled
18 int frequency4 = 0; // count of 4s rolled
19 int frequency5 = 0; // count of 5s rolled
20 int frequency6 = 0; // count of 6s rolled
21
22 int face; // stores most recently rolled value
23
24 // summarize results of 6,000,000 rolls of a die
25 for ( int roll = 1; roll <= 6000000; roll++ )
26 {
27 face = 1 + rand() % 6; // random number from 1 to 6
28
29 // determine roll value 1-6 and increment appropriate counter
30 switch ( face )
31 {
32 case 1:
33 ++frequency1; // increment the 1s counter
34 break;
35 case 2:
36 ++frequency2; // increment the 2s counter
37 break;
38 case 3:
39 ++frequency3; // increment the 3s counter
40 break;
41 case 4:
42 ++frequency4; // increment the 4s counter
43 break;
44 case 5:
45 ++frequency5; // increment the 5s counter
46 break;
47 case 6:
48 ++frequency6; // increment the 6s counter
49 break;
50 default: // invalid value
51 cout << "Program should never get here!";
52 } // end switch
53 } // end for
54
55 cout << "Face" << setw( 13 ) << "Frequency" << endl; // output headers
56 cout << " 1" << setw( 13 ) << frequency1
57 << "\n 2" << setw( 13 ) << frequency2
58 << "\n 3" << setw( 13 ) << frequency3
59 << "\n 4" << setw( 13 ) << frequency4
60 << "\n 5" << setw( 13 ) << frequency5
61 << "\n 6" << setw( 13 ) << frequency6 << endl;
62 return 0; // indicates successful termination
63 } // end main
|
Face Frequency
1 999702
2 1000823
3 999378
4 998898
5 1000777
6 1000422
|
As the program output shows, we can
simulate the rolling of a six-sided die by scaling and shifting the values
produced by rand. Note that the program
should never get to the default case (lines 50–51) provided in the
switch structure, because the switch's controlling expression
(face) always has values in the range 1–6; however, we provide the
default case as a matter of good practice. After we
study arrays in Chapter
7, we show how to replace the entire
switch structure in Fig. 6.8 elegantly with a single-line
statement.
Error-Prevention Tip 6.2
|
Provide a default case in a
switch to catch errors even if you are
absolutely, positively certain that you have no
bugs! |
Randomizing the Random Number
Generator
Executing the program of Fig. 6.7 again produces the following output:
6 6 5 5 6
5 1 1 5 3
6 6 2 4 2
6 2 3 4 1
|
Notice that the program prints
exactly the same sequence of values shown in Fig. 6.7. How can
these be random numbers? Ironically, this repeatability is an important
characteristic of function rand. When
debugging a simulation program, this repeatability is essential for proving that
corrections to the program work properly.
Function rand actually generates pseudorandom numbers. Repeatedly calling
rand produces a sequence of numbers that
appears to be random. However, the sequence repeats itself each time the program
executes. Once a program has been thoroughly debugged, it can be conditioned to
produce a different sequence of random numbers for each execution. This is
called randomizing and
is accomplished with the C++ Standard Library function srand. Function
srand takes an unsigned integer argument and seeds the rand
function to produce a different sequence of random numbers for each execution of
the program.
Figure 6.9 demonstrates function
srand. The program uses the data type
unsigned, which is short for unsigned
int. An int is stored in at least two bytes
of memory (typically four bytes of memory on today's popular 32-bit systems) and
can have positive and negative values. A variable of type unsigned int
is also stored in at least two bytes of memory. A two-byte unsigned int
can have only nonnegative values in the range 0–65535. A four-byte unsigned
int can have only nonnegative values in the range 0–4294967295. Function
srand takes an unsigned int value as an argument. The function
prototype for the srand function is in header file
<cstdlib>.
Fig. 6.9. Randomizing the die-rolling
program.
1 // Fig. 6.9: fig06_09.cpp
2 // Randomizing die-rolling program.
3 #include <iostream>
4 using std::cout;
5 using std::cin;
6 using std::endl;
7
8 #include <iomanip>
9 using std::setw;
10
11 #include <cstdlib> // contains prototypes for functions srand and rand
12 using std::rand;
13 using std::srand;
14
15 int main()
16 {
17 unsigned seed; // stores the seed entered by the user
18
19 cout << "Enter seed: ";
20 cin >> seed;
21 srand( seed ); // seed random number generator
22
23 // loop 10 times
24 for ( int counter = 1; counter <= 10; counter++ )
25 {
26 // pick random number from 1 to 6 and output it
27 cout << setw( 10 ) << ( 1 + rand() % 6 );
28
29 // if counter is divisible by 5, start a new line of output
30 if ( counter % 5 == 0 )
31 cout << endl;
32 } // end for
33
34 return 0; // indicates successful termination
35 } // end main
|
Enter seed: 67
6 1 4 6 2
1 6 1 6 4
|
| |
Enter seed: 432
4 6 3 1 6
3 1 5 4 2
|
| |
Enter seed: 67
6 1 4 6 2
1 6 1 6 4
|
Let's run the program several times
and observe the results. Notice that the program produces a different sequence of
random numbers each time it executes, provided that the user enters a different seed. We used the same seed in the
first and third sample outputs, so the same series of 10 numbers is displayed in
each of those outputs.
To randomize without having to enter a
seed each time, we may use a statement like
This causes the computer to read its
clock to obtain the value for the seed. Function time (with the
argument 0 as written in the preceding
statement) returns the current time as the number of seconds since January 1,
1970, at midnight Greenwich Mean Time (GMT). This value is converted to an
unsigned integer and used as the seed to the
random number generator. The function prototype for time is in
<ctime>.
Common Programming Error 6.7
|
Calling function srand more than once in a program restarts the pseudorandom number
sequence and can affect the randomness of the numbers produced by
rand. |
Generalized Scaling and Shifting of
Random Numbers
Previously, we demonstrated how to
write a single statement to simulate the rolling of a six-sided die with the
statement
which always assigns an integer (at random) to variable
face in the range 1
face
6.
Note that the width of this range (i.e., the number of consecutive integers in
the range) is 6 and the starting number in the range is 1. Referring to the
preceding statement, we see that the width of the range is determined by the
number used to scale rand with the modulus
operator (i.e., 6), and the starting number of the range is equal to the number
(i.e., 1) that is added to the expression rand % 6. We can generalize
this result as
number = shiftingValue + rand() % scalingFactor;
where shiftingValue is equal to the first number in the desired range of
consecutive integers and scalingFactor is equal to the width of the desired range of consecutive
integers.
Common Programming Error 6.8
|
Using srand in place of
rand to attempt to generate random
numbers is a compilation error—function srand
does not return a
value. |