Page 1 of 1

Generating Random Numbers - The C++ Way Rate Topic: -----

#1 Anarion  Icon User is offline

  • The Persian Coder
  • member icon

Reputation: 381
  • View blog
  • Posts: 1,655
  • Joined: 16-May 09

Posted 25 July 2015 - 03:36 PM

Introduction


Anyone familiar with C++ knows at least one way of generating pseudo-random numbers and that is srand( time(0) ) coupled with one or more consecutive calls to rand() % someValue. In this tutorial, however, you will see a newer way to deal with pseudo-random numbers in C++11.

Things You Need To Know Before Starting


  • What pseudo-random numbers are.
  • General familiarity with STL.
  • C++11 uniform initialization.

Also, don't forget to add the C++11 flag for compiler.

The Old Way


To generate a sequence of random numbers, the one method that everybody knows about is to use srand(unsigned int seed) and then rand(). srand is used to initialize the pseudo-random number generator with a seed value. After that, each call to rand() returns the next pseudo-random number. Lets try it out:
#include <iostream>
#include <vector>

#include <cstdlib> //for rand() and srand()

using namespace std;


int main()
{
    cout<<"*** Testing Random Number Generation - The Old Way ***"<<endl<<endl;

    cout<<"Seeding the number generator... ";
    srand(5); //seed the random number generator with 5
    cout<<"Done!"<<endl;

    cout<<"Generating 10 random integers in the range [0, 10)... ";
    vector<int> randVector; //to store the random numbers
    for(int i=0; i<10; ++i) {
        randVector.push_back( rand()%10 );
    }
    cout<<"Done!"<<endl;

    cout<<"Here are the numbers: ";
    for(const int& number: randVector) {
        cout<<" "<<number;
    }
    cout<<endl;

    return 0;
}

It's a pretty expressive program. Compile and run it to see what the output is.
The important part of the code is:
srand(5); //seed the random number generator with 5
randVector.push_back( rand()%10 );

srand(5) initializes the pseudo-random number generator's formula with a seed value, and rand() retrieves the next random number in the sequence. The generated number by rand() is between 0 and RAND_MAX which is implementation-dependent but guaranteed to be at least 32767. (reference).

Try to re-run the program a few times. Don't get surprised by the results! No matter how many times you run the program, the generated numbers are going to be the same sequence. Not much random, right?
The reason behind such behavior is in the call to srand and the argument you pass to it as seed. Because the number I have passed to it is constant, the generated sequence of numbers is going to be same every time the program is run.

In order to fix this issue, the seed passed to srand should be different each time the program is run. The seed value can be generated based on different conditions. One of the easiest ways to generate a seed is to use the current time:
srand( time(nullptr) ); //requires the <ctime> header
randVector.push_back( rand()%10 );

Basically, time(nullptr) returns the number of seconds passed since 00:00 hours, Jan 1, 1970 UTC (i.e., the current unix timestamp).
After this modification, each time the program is run, the seed value changes and therefore, the generated random number sequence is going to be different. Although, problems would arise if you call your program from another executable to generate some output and each run only takes a fraction of a second. In such case, at least a few runs of the program could generate the same sequence due to the same seed value. For such rare cases, you could use milliseconds instead of seconds.
For a reference on the time function, check here.
One drawback when using the remainder operator is that it doesn't generate a uniform distribution due to lower numbers being more likely to be generated.

The Newer Way


Some people argue that the srand and rand approach is evil. I believe the result can be good enough if you are not using it for cryptography or other cases that require a top quality pseudo-random number generation. However, with the new tools available in the <random> header, there is no good reason for using srand and rand anymore.

In order to generate pseudo-random numbers using the facilities in the <random> header, you need to use the following tools:
  • Seed generator - to generate seeds for feeding the generator engine. std::random_device is provided which is actually an integer random number generator itself but it is generally used to seed a generator and should not be used on it's own for generating numbers. For more information, check here.
  • Random number generator - generates pseudo-random numbers. There are different engines and generators available such as std::mt19937, std::mt19937_64 (both of which use the mersenne_twister_engine with different parameters). There is also a std::default_random_engine which is implementation defined.
  • Random number distribution - processes the generator's output to fit different statistical probability functions. If you are not into statistical distributions much, chances are that you will be using std::uniform_int_distribution<> and std::uniform_real_distribution<> most of the time.
    For more information on the available engines or distributions, check here.


Don't get confused by all the new classes and different distributions. Let's see them in action:
#include <iostream>
#include <vector>
#include <random>

using namespace std;


int main()
{
    cout<<"*** Testing Random Number Generation - The New Way ***"<<endl<<endl;

    cout<<"Generating 10 random integers in the range [0, 10)... ";
    vector<int> randVector; //to store the random numbers

    random_device rd; //seed generator
    mt19937_64 generator{rd()}; //generator initialized with seed from rd
    uniform_int_distribution<> dist{0, 9}; //the range is inclusive, so this produces numbers in range [0, 10), same as before

    for(int i=0; i<10; ++i) {
        randVector.push_back( dist(generator) );
    }
    cout<<"Done!"<<endl;

    cout<<"Here are the numbers: ";
    for(const int& number: randVector) {
        cout<<" "<<number;
    }
    cout<<endl;

    return 0;
}

Instead of calling rand(), dist(generator) is called and by doing so, the distribution object calls generator.operator() to generate the next number according to the distribution.

Conclusion


The addition of different generators and distributions to the standard library is a big improvement. If you consider the quality of generated pseudo-random numbers in a small test application, you may not see a significant difference between the two approaches. The problem with rand() and srand( time(nullptr) )(aside from the popular use of %) is not that they are evil. The thing is, when (better) new tools are available, there is no reason to use deprecated alternatives.

Is This A Good Question/Topic? 4
  • +

Page 1 of 1