Friday, January 18, 2013

Random Numbers Using Rand ( )

Having the ability to generate random numbers in a program can be very useful, whether it's to make a simple number guessing game or to see how a program accepts a variety of possible inputs. For our purposes, a random number is a value which was not obvious before creation. And, any number in the availible range should be just as likely as any other, known as a uniform distribution.

There are at least two ways to accomplish this feat. For exploring the two examples below, I will use the following, The Base Main. The function body of RandomNumber( ) will be the only difference between the examples. We use the rand( ) function which returns a number which is offset by a random seed. The seed is set by passing the srand( ) function with a number, generally the time.

The Base Main
main.cpp
//===================================INCLUDES==================================
#include <iostream>    //Included for text output, cout.
#include <time.h>      //Included to get current time.

//Assume namespace ::std.

using namespace std;


//=============================FUNCTION PROTOTYPES=============================
int RandomNumber( int min, int max );


//==================================MAIN ENTRY=================================
int main( void )
{
    //Initialize Rand( ) function's seed with current time.
    srand( static_cast<unsigned int>( time(NULL) ) );

    //Loop for 100 times.
    for ( int i = 0; i < 100; i++ )
    {
        //Output a number between 1 and 100.
        cout << RandomNumber( 1, 100 ) << endl;
    }

    //End the program, returning zero.
    return ( 0 );
}

//=============================FUNCTION DEFINITIONS============================

The above code seeds the randomization with the system time. When a function definition for RandomNumber( ) is appended, 100 numbers are generated to the screen.

The Modulus Method

Currently the popular implementation for generating pseudo-random numbers in C++, The Modulus Method, is so widely used that not many people consider the process. Even though we can't employ actual randomness since most computers behave predictably, there's no reason we can't explore some different implimentations.

The Modulus Method
main.cpp, RandomNumber( )
//=============================FUNCTION DEFINITIONS============================
//-----------------------------------------------------------------------------
// Function:    RandomNumber
// Description: Returns a random number between min value and max value,
//              including min and max. [min, ..., max]
//-----------------------------------------------------------------------------

int RandomNumber( int min, int max )
{
    //Return a random number between min and max, inclusive.
    // Adds minimum value to the remainder of division of the number returned
    // from Rand() function and the maximum number, plus one, less the minimum.

    return ( min + rand( ) % ( (max + 1) - min ) );
}

The random number is seeded by passing the time, an unsigned integer, into the srand( ) function. This ensures that the numbers returned from calls to rand ( ) are potentially different each time the program runs.

The rand( ) function returns a random integer which is between 0 and the RAND_MAX, the constant specified by the library, which is guarenteed to at least be 32,767.

To get this number within a desired range, other than 0 to 32,767, the modulus operator, %, is used to calculate the remainder of division between the number returned and the maximum value. A random number between 0 and 9,  would use rand( ) % 10. Any number returned by rand ( ) is less than 10. For example, if rand( ) returns 8000, the modulus operation would divide 8000 / 10, which evenly divides, so there would be a remainder of 0, which would be our random number result for that run.

In the RandomNumber function the following is used to specify the minimum and maximum range for the resulting random number:
( min + rand( ) % ( (max + 1) - min ) );

More simply, whatever the random number is, is then acted on by the modulus to clamp its upper bounds and then has the minimum value added to it to clamp its lower bounds. There's some repetition, albeit sporatic, to the remainder of numbers early on in the number system which isn't so in later numbers, which could sometimes makes the randomness less uniform than desired.

Modulus Step-by-step
To show how this works, consider if the minimum of 1 and maximum of 10 used.
RandomNumber(1, 10);

The maximum desired number is increased by one to allow for the maximum number itself to possibly result.
( 1 + rand( ) % ( (10 + 1) - 1 ) );

The result, 11, is minused from the minimum desired number, in this case 1. This makes the number to the right of the modulus the equivalent of the desired range from zero.
( 1 + rand( ) % ( 11 - 1 ) );

Rand ( ) is ultimately a number, it is the driving force in getting a different result each time, so let's say that rand ( ) returns 4892, just a random number between 0 and RAND_MAX.
( 1 + rand( ) % 10 );

The result of 4892 % 10 is 2.
( 1 + 4892 % 10 );

The minimum desired value, 1, is then added to the resulting remainder, 2, to ensure it is at least the minimum value. This guarentees that the number will never be the same or higher than the number divided by. This should make sense, if you divide by a number you'll never get a remainder of that number.
( 12 );

The number returned by calling RandomNumber(1, 10) in this case is 3.


The Do-While Method

Rather than using the somewhat limited remainder of division and then clamping down on whatever number results, we could instead call random until a number within our desired range comes about.


The Do-While Method
main.cpp
//=============================FUNCTION DEFINITIONS============================
//-----------------------------------------------------------------------------
// Function:    RandomNumber
// Description: Returns a random number between min value and max value,
//              including min and max. [min, ..., max]
//-----------------------------------------------------------------------------

int RandomNumber ( int min, int max )
{
    int randomNumber;

    do
    {
        //Set randomNumber equal to a random number.
        randomNumber = rand ( );
    }
    //Generate a new random number while randomNumber
    // is less than the minimum or more than maximum.

    while ( randomNumber < min || randomNumber > max );

    //Return the random number between, and including, min and max.
    return randomNumber;
}

In this code we create a local variable, int randomNumber, to hold the different random numbers produced by rand( ), which is some amount between 0 and RAND_MAX. Should the randomNumber generated by rand( ) be outside the range, randomNumber < min || randomNumber > max, we do it again until it is within our desired range. Essentially, it simply generates a random number until it gets one within the range between min and max.

The distribution is more uniform than the modulus method above, but the smaller the range the more time it takes to return a number. This is something to consider if the program is very processor intensive where millions of time-sensitive operations occur every second.

Random Number Generation Frequency

You might wonder if the distribution pattern actually skewed. In some cases, it is possible that certain numbers appear more frequently, though it's not apocalyptic, and users wont be able to predict with certainty the next number to be generated. However, to give you an idea of the distribution pattern for both methods, I'll use the code below to do a side-by-side comparison:

Generating a Frequency Report
main.cpp
//===================================INCLUDES==================================
#include <iostream>    //Included for text output, cout.
#include <iomanip>     //Included for output formatting manipulation.
#include <time.h>      //Included to get current time.

//Assuming namespace ::std.
using namespace std;


//=============================FUNCTION PROTOTYPES=============================
int RandomNumber_Modulus( int min, int max );
int RandomNumber_DoWhile( int min, int max );

//==================================MAIN ENTRY=================================
int main( void )
{
    const int RANGE_MINIMUM = 0;
    const int RANGE_MAXIMUM = 50;
    const int SAMPLE_SIZE = 9000;

    //Declare frequency variables to zero.
    intmodulusFrequency[RANGE_MAXIMUM - RANGE_MINIMUM] = {0};
    int dowhileFrequency[RANGE_MAXIMUM - RANGE_MINIMUM] = {0};

    int modulusResult = 0;
    int dowhileResult = 0;

    //Initialize Rand( ) function's seed with current time.
    srand( static_cast<unsigned int>( time (NULL) ) );

    //Generate SAMPLE_SIZE count of random numbers per generation method.
    for ( int i = 0; i < SAMPLE_SIZE; i++ )
    {
        //Set result variable to the random number from respective function.
        modulusResult = RandomNumber_Modulus( RANGE_MINIMUM, RANGE_MAXIMUM );
        dowhileResult = RandomNumber_DoWhile( RANGE_MINIMUM, RANGE_MAXIMUM );

        //Increment frequency count for the number returned.
        modulusFrequency[modulusResult]+ +;
        dowhileFrequency [dowhileResult]++;
    }

    //Output a table header
    cout << left
         << setw(16) << "RANGE_MINIMUM :" << setw(15) << RANGE_MINIMUM
         << setw(16) << "SAMPLE_SIZE :" << setw(15) << SAMPLE_SIZE << endl
         << setw(16) << "RANGE_MAXIMUM :" << setw(15) << RANGE_MAXIMUM
         << setw(16) << "RANGE COUNT :" << setw(15) << RANGE_MAXIMUM - RANGE_MINIMUM
         << endl << endl
         << setw(18) << "THE MODULUS METHOD" << setw(13) << ""
         << setw(19) << "THE DO-WHILE METHOD" << endl;

   //Output the frequency results.
    for ( int i = 0; i < (RANGE_MAXIMUM - RANGE_MINIMUM); i++ )
    {
        //Output the modulus results.
        cout << setw(3) << i << setw(9) << "occurred"
             << setw(4) << modulusFrequency[i] << setw(7) << "times.";

        cout << setw(8) << "";

        //Output the do-while results.
        cout << setw(3) << i << setw(9) << "occurred"
             << setw(4) << dowhileFrequency[i] << setw(7) << "times.";

        cout << endl;
    }

    //End the program, returning zero.
    return ( 0 );
}


//=============================FUNCTION DEFINITIONS============================
//-----------------------------------------------------------------------------
// Function:    RandomNumber_Modulus
// Description: Returns a random number between min value and max value,
//              including min and max. [min, ..., max]
//-----------------------------------------------------------------------------

int RandomNumber_Modulus( int min, int max )
{
    //Return a random number between min and max, inclusive.
    // Adds minimum value to the remainder of division of the number returned
    // from Rand() function and the maximum number, plus one, less the minimum.

    return ( min + rand( ) % ( (max + 1) - min ) );
}


//-----------------------------------------------------------------------------
// Function:    RandomNumber_DoWhile
// Description: Returns a random number between min value and max value,
//              including min and max. [min, ..., max]
//-----------------------------------------------------------------------------

int RandomNumber_DoWhile( int min, int max )
{
    int randomNumber;

    do
    {
        //Set randomNumber equal to a random number.
        randomNumber = rand ( );
    }
    //Generate a new random number while randomNumber
    // is less than the minimum or more than maximum.

    while ( randomNumber < min || randomNumber > max );

    //Return a random number between min and max, inclusive.
    return randomNumber;
}


This code creates random SAMPLE_SIZE count of numbers between RANGE_MINIMUM and RANGE_MAXIMUM using the modulus method, RandomNumber_Modulus( ), and the do-while method, RandomNumber_DoWhile( ). The modulus random number result, modulusResult, is added to the element of the modulus frequency array, modulusFrequency. The do-while random number, dowhileResult, is added to the element dowhileFrequency.

Then the program outputs to the screen, the count of each number that occurred for each method. This is a small range, but the purpose of a uniform number generator is to have all numbers within the range equally possible; there shouldn't be any one number occuring too often compared to the others.

Here's a sample output:

No comments:

Post a Comment