The engines produce unsigned
numbers, and each number in the engine’s range has the same likelihood of being generated. Applications often need numbers of different types or distributions. The library handles both these needs by defining different distributions that, when used with an engine, produce the desired results. Table 17.16 (overleaf) lists the operations supported by the distribution types.
Programs often need a source of random floating-point values. In particular, programs frequently need random numbers between zero and one.
The most common, but incorrect, way to obtain a random floating-point from rand
is to divide the result of rand()
by RAND_MAX
, which is a system-defined upper limit that is the largest random number that rand
can return. This technique is incorrect because random integers usually have less precision than floating-point numbers, in which case there are some floating-point values that will never be produced as output.
With the new library facilities, we can easily obtain a floating-point random number. We define an object of type uniform_real_distribution
and let the library handle mapping random integers to random floating-point numbers. As we did for uniform_int_distribution
, we specify the minimum and maximum values when we define the object:
default_random_engine e; // generates unsigned random integers
// uniformly distributed from 0 to 1 inclusive
uniform_real_distribution<double> u(0,1);
for (size_t i = 0; i < 10; ++i)
cout << u(e) << " ";
This code is nearly identical to the previous program that generated unsigned
values. However, because we used a different distribution type, this version generates different results:
0.131538 0.45865 0.218959 0.678865 0.934693 0.519416 ...
With one exception, which we’ll cover in § 17.4.2 (p. 752), the distribution types are templates that have a single template type parameter that represents the type of the numbers that the distribution generates. These types always generate either a floating-point type or an integral type.
Each distribution template has a default template argument (§ 16.1.3, p. 670). The distribution types that generate floating-point values generate double
by default. Distributions that generate integral results use int
as their default. Because the distribution types have only one template parameter, when we want to use the default we must remember to follow the template’s name with empty angle brackets to signify that we want the default (§ 16.1.3, p. 671):
// empty <> signify we want to use the default result type
uniform_real_distribution<> u(0,1); // generates double by default
In addition to correctly generating numbers in a specified range, another advantage of the new library is that we can obtain numbers that are nonuniformly distributed. Indeed, the library defines 20 distribution types! These types are listed in § A.3 (p. 882).
As an example, we’ll generate a series of normally distributed values and plot the resulting distribution. Because normal_distribution
generates floating-point numbers, our program will use the lround
function from the cmath
header to round each result to its nearest integer. We’ll generate 200 numbers centered around a mean of 4 with a standard deviation of 1.5. Because we’re using a normal distribution, we can expect all but about 1 percent of the generated numbers to be in the range from 0 to 8, inclusive. Our program will count how many values appear that map to the integers in this range:
default_random_engine e; // generates random integers
normal_distribution<> n(4,1.5); // mean 4, standard deviation 1.5
vector<unsigned> vals(9); // nine elements each 0
for (size_t i = 0; i != 200; ++i) {
unsigned v = lround(n(e)); // round to the nearest integer
if (v < vals.size()) // if this result is in range
++vals[v]; // count how often each number appears
}
for (size_t j = 0; j != vals.size(); ++j)
cout << j << ": " << string(vals[j], '*') << endl;
We start by defining our random generator objects and a vector
named vals
. We’ll use vals
to count how often each number in the range 0 . . . 9 occurs. Unlike most of our programs that use vector
, we allocate vals
at its desired size. By doing so, we start out with each element initialized to 0.
Inside the for
loop, we call lround(n(e))
to round the value returned by n(e)
to the nearest integer. Having obtained the integer that corresponds to our floating-point random number, we use that number to index our vector
of counters. Because n(e)
can produce a number outside the range 0 to 9, we check that the number we got is in range before using it to index vals
. If the number is in range, we increment the associated counter.
When the loop completes, we print the contents of vals
, which will generate output such as
0: ***
1: ********
2: ********************
3: **************************************
4: **********************************************************
5: ******************************************
6: ***********************
7: *******
8: *
Here we print a string
with as many asterisks as the count of the times the current value was returned by our random-number generator. Note that this figure is not perfectly symmetrical. If it were, that symmetry should give us reason to suspect the quality of our random-number generator.
bernoulli_distribution
ClassWe noted that there was one distribution that does not take a template parameter. That distribution is the bernoulli_distribution
, which is an ordinary class, not a template. This distribution always returns a bool
value. It returns true
with a given probability. By default that probability is .5.
As an example of this kind of distribution, we might have a program that plays a game with a user. To play the game, one of the players—either the user or the program—has to go first. We could use a uniform_int_distribution
object with a range of 0 to 1 to select the first player. Alternatively, we can use a Bernoulli distribution to make this choice. Assuming that we have a function named play
that plays the game, we might have a loop such as the following to interact with the user:
string resp;
default_random_engine e; // e has state, so it must be outside the loop!
bernoulli_distribution b; // 50/50 odds by default
do {
bool first = b(e); // if true, the program will go first
cout << (first ? "We go first"
: "You get to go first") << endl;
// play the game passing the indicator of who goes first
cout << ((play(first)) ? "sorry, you lost"
: "congrats, you won") << endl;
cout << "play again? Enter 'yes' or 'no'" << endl;
} while (cin >> resp && resp[0] == 'y'),
We use a do while
(§ 5.4.4, p. 189) to repeatedly prompt the user to play.
Because engines return the same sequence of numbers (§ 17.4.1, p. 747), it is essential that we declare engines outside of loops. Otherwise, we’d create a new engine on each iteration and generate the same values on each iteration. Similarly, distributions may retain state and should also be defined outside loops.
One reason to use a bernoulli_distribution
in this program is that doing so lets us give the program a better chance of going first:
bernoulli_distribution b(.55); // give the house a slight edge
If we use this definition for b
, then the program has 55/45 odds of going first.