C++

C++ Random Number Between 0 and 1

A random number is generated within a range, from a minimum number to a maximum number. Assume that these minimum and maximum numbers are greater than 1. Let the number generated within the range be num. Let the minimum number be min, and let the maximum number be max. With these, in order to convert the number to between 0 and 1, use the formula:

    random_number = (num – min)/(max – min)

random_number should now be between 0 and 1.
The next questions are how to generate random numbers and how to decide min and max. In fact, random numbers, as described by C++20 specification, are actually pseudo-random numbers. The C++20 specification gives a guide to producing truly random numbers (non-deterministic random numbers). The problem with this truly random number generator is that the compiler’s responsibility, or the programmer, is to provide the algorithm to what is considered non-deterministic random number generation. This article does not address nondeterministic random numbers.

Pseudo-random numbers are generated in a sequence (an order) of numbers, which look like random numbers. The generation of a random number needs what is called a seed. The seed is some starting value. This article explains the basics of random number generation in C++20. If the resulting number is greater than 1, it is brought down to between 0 and 1, using the above formula. The C++ <random>library has to be included in the program to have a random or random number sequence.

Article Content

Distributions
Uniform Distribution

A uniform distribution is one where the probability of a number is one out of the total number of numbers in the sequence. Consider the following sequence:

    0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100

If these eleven numbers are a sequence of random numbers, each number has appeared once out of eleven occurrences. This means it is a uniform distribution. In practice, not all may appear once. One or two or three may appear more than once, and they would not appear in regular order.

If the random number returned is 40, then the program has to convert the random number to between 0 and 1 using

    random_number = (400)/(1000)

                               = 4/10 = 0.4

Here, num is 40; min is 0, and max is 100.

Binomial Distribution

The binomial distribution is not a uniform distribution. “Bi”, the prefix of Binomial, means two. The number of values in the binomial distribution is represented by t in C++. If the bi numbers concerned for the distribution are 2 and 3, and if t is 1, then the sequence is:

    2, 3

If t is 2 for the same bi numbers (2 and 3), then the sequence becomes,

    4, 12, 9

If t is 3 for the same bi numbers (2 and 3), then the sequence becomes,

    8, 36, 54, 27

If t is 4 for the same bi numbers (2 and 3), then the sequence becomes,

    16, 96, 216, 216, 81

t is a positive integer that can be more than 4. For each value of t, there are t+1 elements in the sequence. A sequence depends on the bi numbers chosen and the value of t. The bi numbers can be any pair, e.g., 13 and 17. The sum of the bi numbers is also important. A sequence is developed from what is known as the Binomial Theorem.

There are other distributions in the random library in C++.

linear_congruential_engine

There are a number of random number engines in C++. linear_congruential_engine is one of them. This engine takes a seed, multiplies it with a multiplier, and adds a constant number c to the product to have the first random number. The first random number becomes the new seed. This new seed is multiplied by the same ‘a’, the product of which is added to the same c, to have the second random number. This second random number becomes the new seed for the next random number. This procedure is repeated for as many random numbers as required by the programmer.

The seed here has the role of an index. The default seed is 1.

A syntax for the linear_congruential_engine is:

    linear_congruential_engine<class UIntType, UIntType a, UIntType c, UIntType m>lce

lce is the name of the programmer’s choice. This syntax uses the default seed of 1. The first template parameter here should be specialized with “unsigned int”. The second and third should have the actual values of ‘a’ and c. The fourth should have the actual value of the maximum random number expected, plus 1.

Assuming that a seed of the value 2, is required, then the syntax would be:

    linear_congruential_engine<class UIntType, UIntType a, UIntType c, UIntType m>lce(2)

Note the seed in parentheses just after lce.

The following program, illustrates the use of linear_congruential_engine, with the default seed of 1:

    #include <iostream>

    #include <random>

    using namespace std;


    int main()

    {

        linear_congruential_engine<unsigned int, 3, 1, 500>lce;


        cout <<lce() <<endl;

        cout <<lce() <<endl;

        cout <<lce() <<endl;

        cout <<lce() <<endl;

        cout <<lce() <<endl;

        cout <<endl;


        cout <<lce.min() <<endl;

        cout <<lce.max() <<endl;


        return 0;

    }

The output is:

    4

    13

    40

    121

    364


    0

    499

Note the way the lce object for the engine was instantiated. Here, ‘a’ is 3, c is 1, and the maximum, hoped to reach number, m is 500. m is actually a modulus – see later. lce(), as used here, is not a constructor. It is an operator that returns the next random number required for the engine in the output sequence. min for this scheme is 0, and max is 499, and these can be used to convert a number returned to between 0 and 1 – see below.

The first random number returned is 4. It is equal to 1 X 3 + 1 = 4. 4 becomes the new seed. The next random number is 13, which is equal to 4 X 3 + 1 = 13. 13 becomes the new seed. The next random number is 40, which is equal to 13 X 3 + 1 = 40. In this way, the succeeding random numbers are 121 and 364.

The following code, illustrates the use of linear_congruential_engine, with a seed of 2:

        linear_congruential_engine<unsigned int, 3, 1, 1000>lce(2);


        cout <<lce() <<endl;

        cout <<lce() <<endl;

        cout <<lce() <<endl;

        cout <<lce() <<endl;

        cout <<lce() <<endl;

        cout <<endl;


        cout <<lce.min() <<endl;

        cout <<lce.max() <<endl;

The output is:

    7

    22

    67

    202

    607


    0

    999

The maximum random number hoped for here is 1000. min for this scheme is still 0, and max is now 999, and these can be used to convert a number returned to between 0 and 1 – see below

The first random number returned is 7. It is equal to 2 X 3 + 1 = 7. 7 becomes the new seed. The next random number is 22, which is equal to 7 X 3 + 1 = 22. 22 becomes the new seed. The next random number is 67, which is equal to 22 X 3 + 1 = 67. In this way, the succeeding random numbers are 202 and 607.

The following code uses the above formula to produce a random number between 0 and 1, for this engine:

        linear_congruential_engine<unsigned int, 3, 1, 1000>lce(2);


        unsigned int num = lce();  // normal random number

        unsigned int min = lce.min();

        unsigned int max = lce.max();


        float random_number = ((float)(num - min))/((float)(max - min));


        cout <<random_number <<endl;

The output is:

    0.00700701

Here, num is 7, and so

    random_number = (70)/(9990) = 7/999 = 0.00700701 rounded to 8 decimal places.

linear_congruential_engine is not the only specialized engine in the random library; there are others.

default_random_engine

This is like a general-purpose engine. It produces random numbers. The sequence order is not guaranteed to be un-determined. However, the order is likely not known by the programmer. The following two lines show how this engine can be used:

        random_device rd;

        default_random_engine eng(rd());

random_device is a class from which rd has been instantiated. Note the parentheses for rd in the argument lists of the engine. A distributor needs this engine for its operation – see below.

Random Number Distribution Classes
uniform_int_distribution

uniform_int_distribution
The probability that any number will occur is 1 divided by the total number of numbers for this class. For example, if there are ten possible output numbers, the probability of each number being displayed is 1/10. The following code illustrates this:

        random_device rd;

        default_random_engine eng(rd());

        uniform_int_distribution<int>dist(3, 12);


        cout <<dist(eng) <<' '  <<dist(eng) <<' ' <<dist(eng) <<' ' <<dist(eng) <<' ' <<dist(eng) <<' ' <<endl;

        cout <<dist(eng) <<' '  <<dist(eng) <<' ' <<dist(eng) <<' ' <<dist(eng) <<' ' <<dist(eng) <<' ' <<endl;

The output from the author’s computer is:

    9 8 3 5 12

    7 4 11 7 6

Unfortunately, 7 has appeared two times at the expense of 10. The arguments of dist are the numbers 3 and 13 inclusive (ten consecutive integers). dist(eng) is an operator that returns the next number. It uses the engine. Note the use of the int template specialization.

There is no need to look for num, min, and max for this case and then use the above formula to obtain a number between 0 and 1. This is because there is a float equivalent of this class that uses float specialization. The output will not be the same for each run.

uniform_real_distribution

uniform_real_distribution is similar to uniform_int_distribution. With it, in order to obtain a number between 0 and 1, just use 0 and 1 as the arguments. The following code illustrates this:

        random_device rd;

        default_random_engine eng(rd());

        uniform_real_distribution<float>dist(0, 1);


        cout <<dist(eng) << ' '  <<dist(eng) <<' ' <<dist(eng) <<' ' <<dist(eng) <<' ' <<dist(eng) <<' ' <<endl;

        cout <<dist(eng) <<' '  <<dist(eng) <<' ' <<dist(eng) <<' ' <<dist(eng) <<' ' <<dist(eng) <<' ' <<endl;

The output from the author’s computer is:

    0.384051 0.745187 0.364855 0.122008 0.580874

    0.745765 0.0737481 0.48356 0.184848 0.745821

Note the use of the float template specialization. The output will not be the same for each run.

binomial_distribution

With this distribution, the probability for each output number is not the same. binomial_distribution has been illustrated above. The following code shows how to use the binomial_distribution to produce 10 random numbers:

        random_device rd;

        default_random_engine eng(rd());

        binomial_distribution<int>dist(10);


        cout <<dist(eng) <<' '  <<dist(eng) <<' ' <<dist(eng) <<' ' <<dist(eng) <<' ' <<dist(eng) <<' ' <<endl;

        cout <<dist(eng) <<' '  <<dist(eng) <<' ' << dist(eng) <<' ' <<dist(eng) <<' ' <<dist(eng) << ' ' <<endl;

The output from the author’s computer is:

    5 3 5 5 7

    6 6 5 8 3

The output will not be the same for each run. The template specialization used here is int.

The following code uses the above formula to produce a random number between 0 and 1, for this distribution:

        random_device rd;

        default_random_engine eng(rd());

        binomial_distribution<int>dist(10);


        unsigned int num = dist(eng);  // normal random number

        unsigned int min = dist.min();

        unsigned int max = dist.max();

        cout <<min <<endl;

        cout <<max <<endl;

        cout <<endl;


        cout <<num <<endl;

        float random_number = ((float)(num - min))/((float)(max - min));


        cout <<random_number <<endl;

The output from the author’s computer is:

    0

    10


    7

    0.7

Better Random Number

The number of seconds since UNIX Epoch can be used as the seed. It becomes difficult for the hacker to know the seed. The following program illustrates this with the linear_congruential_engine:

    #include <iostream>

    #include <random>

    #include <chrono>

    using namespace std;


    int main()

    {

        const auto p1 = chrono::system_clock::now();

        unsigned int seed = chrono::duration_cast<std::chrono::seconds>(p1.time_since_epoch()).count();

 
        linear_congruential_engine<unsigned int, 3, 1, 1000>lce(seed);


        cout <<lce() <<' ' <<lce() <<' ' <<lce() <<' ' <<lce() <<' ' <<lce() <<' ' <<endl;

        cout <<endl;


        cout <<lce.min() <<endl;

        cout <<lce.max() <<endl;


        return 0;

    }

The output from the author’s computer is:

    91 274 823 470 411


    0

    999

Note that the chrono library has been included. The output is different for each run.

Conclusion

The easiest way to have a random number between 0 and 1 is to use the random_device, the default_random_engine, and the uniform_real_distribution (with arguments 0 and 1). Any other engine or distribution used may need the formula, random_number = (num – min)/(max – min).

About the author

Chrysanthus Forcha

Discoverer of mathematics Integration from First Principles and related series. Master’s Degree in Technical Education, specializing in Electronics and Computer Software. BSc Electronics. I also have knowledge and experience at the Master’s level in Computing and Telecommunications. Out of 20,000 writers, I was the 37th best writer at devarticles.com. I have been working in these fields for more than 10 years.