C++/random function
Expert: Ralph McArdell - 11/8/2006
QuestionHelp!!!... I need to make a random function for a dice which I will be using in a game I am programming. Numbers from 1 to 6. thanks!
AnswerI am an expert in C++ not in the mathematics of random number generation. Taking that into account means I am going to explain the available random number generation facilities in C++. If you wish to know how to create your own random number generation functions (in the mathematical sense of function), please ask a mathematics expert.
First I should note that all such functions are pseudo random number generators. To obtain real randomness you need to use some random physical quantity. For information on pseudo random numbers see the Wikipedia article at
http://en.wikipedia.org/wiki/Pseudorandom_number_generator and its links for starters. If you wish to know more about true randomness try the Wikipedia article at
http://en.wikipedia.org/wiki/Random_number_generator and its links for starters, also the site at
http://www.random.org/ and their introduction at
http://www.random.org/essay.html which appears not to be referenced from the Wikipedia article, looks to be of interest as well.
OK so that is the background. What about actually doing it using C++?
Well the current C++ standard inherits its pseudo random number functionality from C in the form of the two library functions rand and srand and the macro constant RAND_MAX which are declared in the C++ header cstdlib (stdlib.h for C).
The main function here is rand:
int rand(void);
The rand function returns a value between 0 and RAND_MAX. In your case you will therefore need to scale this value to the range you require: 1 to 6, for example:
dice_roll = rand()%6 + 1;
Taking the remainder yields a value in the range 0 to 5; adding 1 will yield a value in the range 1 to 6. This is just and example, in production code you should not be using magic numbers but use named constant values for 6 and 1. I should note that the above is just one way to scale the raw pseudo random number to the range you require.
So that would seem to fix your problem. Until you try it and notice that each time you run the program you get the same sequence of dice roll values. This is by design so programs with (pseudo) random behaviour can be tested.
In order to generate different pseudo random number sequences the pseudo random number generator needs to be seeded with a different value. This is what the srand function is used for:
void srand(unsigned int seed);
If rand is called before srand is called then it behaves as if srand had been called with a value of 1. You can think of rand as producing many sequences of pseudo random numbers, each sequence having a number. Which sequence is used is determined by the value passed to srand and is sequence number 1 by default. Hence if srand is never called you always end up with pseudo random number sequence 1.
So to have rand produce different sequences of pseudo random numbers you call srand and pass it a sequence number. So where does this number come from as it also needs to be a pseudo random number? If it is not you will just have your program always have rand produce the same sequence, just a different one from the default!
The usual trick is to use values based on something that changes from run to run of an application, such as time. The obvious function to do this is also inherited by C++ from C, the time function, declared in ctime (time.h for C):
time_t time(time_t * timer);
This returns the system's best approximation of the current calendar time. The format of time_t is not defined by the standard but is usually some sort of integer value that specifies the time in seconds from some point in the past, sometimes called the Epoch. Both the implementation supplied with Visual C++ 8 (2005) and the implementation for my SuSE Linux system use this approach and an Epoch value of midnight (00:00:00 UTC), January 1st 1970.
With such implementations you can just pass the time_t value to srand. However if the size of the time_t integer is larger than that of the unsigned int taken by srand your compiler may issue a warning. The conversion from time_t to unsigned int should keep the lowest numbered bits of the time_t, which are the bits that change most frequently and therefore most useful as a seed value for the pseudo random number generator.
You have to pass the time function the address of a time_t object. The time function also returns the time value, unless an error occurred, when -1 converted to a time_t is returned:
time_t seconds;
if ( time_t(-1)==time(seconds) )
{
// Handle error
}
srand(seconds);
Here is an example program that puts all this together:
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <iomanip>
namespace
{
// Helper struct template to allow compiler to compute the number
// of digits required to represent unsigned integer values.
//
// If your compiler does not support unsigned long long
// try using unsigned long instead.
template <unsigned long long UnsignedInteger>
struct NumDigits
{
enum { value = 1 + NumDigits<UnsignedInteger/10>::value };
};
// Recursive template instantiaion terminating specialisation
// Recursion stops and unwinds when UnsignedInteger/10 == 0
template<>
struct NumDigits<0>
{
enum { value=0 };
};
}
// Main program entry point
int main()
{
// Show size of RAND_MAX and the number of digits required to
// display it for the system in question:
std::cout << "RAND_MAX=" << RAND_MAX
<< "\n. Number of digits required to represent RAND_MAX="
<< NumDigits<RAND_MAX>::value << '\n';
// Get the current time in seconds
time_t seconds;
if ( time_t(-1)==time(&seconds) )
{
return 1; // error from time
}
// Declare alias for ungiend int that can be used
// in conversions to unsigned int
typedef unsigned int uint;
// Show raw time value and time value after converting to unsigned int
std::cout << "Raw seconds: " << seconds << '\n';
std::cout << "Raw seconds as uint: " << uint(seconds) << '\n';
// Use time in seconds to seed pseudo random number generator
srand( uint(seconds) );
// Generate some random numbers
size_t const SamplesSize(10);
int samples[SamplesSize];
for ( int i(0); i < SamplesSize; ++i )
{
samples[i] = rand();
std::cout << std::setw(NumDigits<RAND_MAX>::value)
<< samples[i] << ' ';
}
std::cout << '\n';
// Convert generated pseudo random number values to dice roll values
for ( int i(0); i < SamplesSize; ++i )
{
std::cout << std::setw(NumDigits<RAND_MAX>::value)
<< samples[i]%6+1 << ' ';
}
std::cout << std::endl;
// system("pause");
}
The first thing to say is not to be too worried about the intricacies of the NumDigits helper template. I added this so that I can get the compiler to compute the number of digits required to display RAND_MAX so that I could use this value when setting the field width to display values. This was done as I built and ran this system using two compilers and two operating systems:
- Built using MS Visual C++ 8 32-bit compiler, run on Windows XP x64
- Build using g++ 4.0.2 64-bit compiler, run on SuSE Linux 64-bit
They used different values of RAND_MAX, requiring different field widths:
- +32767 for the 32-bit VC++ build
- +2147483647 for the 64-bit g++ build
If you cannot get the NumDigits template to work with your compiler just remove both parts of the definition of NumDigits in the anonymous namespace and replace all instances NumDigits<RAND_MAX>::value with a simple constant value.
Ok so what does the program demonstrate?
First it displays the value of RAND_MAX and the number of digits required to display a value of this size.
Next the time in seconds is obtained from time and the value displayed both as a raw time_t value and the time_t converted to an unsigned int as required by srand.
The seconds value is then passed to srand after being converted to an unsigned int to seed the pseudo random number generator.
Finally some pseudo random numbers are generated, stored and displayed. After a certain number have been generated (set to 10) the stored values are converted into your dice roll range of 1 to 6 and displayed.
I point out that this code is for demonstration purposes only and is not production quality.
Hopefully the documentation supplied with your compiler for its libraries will contain detailed information on the particular implementation details of the library functions and macros I have mentioned. I know that for VC++ they are documented in the MSDN library, which is supplied as the documentation for this product and is also online at
http://msdn.microsoft.com/library/ For UNIX and Linux systems you can consult the system man pages. Note that rand and srand are in section 3 and time is in section 2.
Now since the publication of the ISO C++ standard in 1998 there have been some updates. The first, TC1 in 2003, was more of a bug-fix update. However more recently there has been a new update, TR1, that is an optional set of library facilities to those provided in the ISO standard (optional here means that not providing them does not make a C++ implementation non-standard). See
http://www.open-std.org/jtc1/sc22/wg21/ for information on C++ standards work. The draft document for the TR1 library extensions can be found at
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1836.pdf The document is ISO/IEC DTR 19768, having the title "Draft Technical Report on C++ Library Extensions".
The reason I mention this TR is that in section 5, Numerical Facilities, there is a whole sub-section on random number generation, section 5.1 Random Number Generation. This section describes a whole framework for random number generation and a set of standard generator engines and random distribution templates.
I currently know of no implementations of the TR1 library extensions, but I am sure they will be along in due course so you might like to have a peek to see what might be available in the future.
Hope this is of use to you.