Introduction
The Mersenne Twister(MT) is a pseudorandom number generator (PRNG) developed by Makoto Matsumoto
and Takuji Nishimura[1][2] during 1996-1997. MT has the following merits:
- It is designed with consideration on the flaws of various existing generators.
- Far longer period and far higher order of equidistribution than any other implemented generators.
(It has been proven that the period is 2^19937-1, and 623-dimensional equidistribution property is assured.)
- Fast generation. (Although it depends on the system, it is reported that MT is sometimes faster
than the standard ANSI-C library in a system with pipeline and cache memory.)
- Efficient use of the memory.
Credit where it's due
I present a C++ class (
CRandomMT
) that encapsulates the work of the creators. I do not take credit
for their work, I am merely presenting an object oriented (OO) version of their code that you can simply drop
into your game or application.
CRandomMT
Inspiration
In 2000 I was working on a random dungeon generator for a Roguelike and I asked the folks on
rec.games.roguelike.development what they
used for PRNG. One response (or maybe two) suggested that I give the MT a try. After reading
about its merits (see above) I decided to download it and drop it in my Roguelike. I was
surprised that I could not find an object oriented (OO) version of the algorithm and immediately
sat down to
wrap the code. I mean, that is what us C++ guys do... right?
Perspiration
Wrapping the code up wasn't all that difficult and there really wasn't any perspiration, which
made me wonder even more why the algorithm had not been encapsulated into a C++ class and made publicly
available. Nevertheless, I went about the business of pulling this function here and that one there
and this one should be private, you know the drill, until I had a completely encapsulated version
ready for use.
Roguelikes tend to need a good deal of random numbers so, my next task was to add a few methods
that would make my Roguelike easier to code and the intentions of the code easy to read. The
standard nomenclature for representing dice rolls is:
<number of dice>d<size of die>
so, to roll 2 six sided dice you would write 2d6. Thinking about this for a bit I determined that
the standard nomenclature would be difficult to translate into methods for my class but if I reverse the
nomenclature to read:
d<size of die><number of dice> I could easily create methods
that described the intentions of the code by creating methods such as CRandomMT::D6(int _DiceCount)
and CRandomMT::D25(int _DiceCount)
. Ironically, these were merely wrappers around the method:
CRandomMT::RollDice(int _DieSize, int _DiceCount)
.
Completion
Ahhh, completion... that really is the hardest part. I did complete the class that I present to you and I even
completed this article. But, I failed to complete the Roguelike. Like everything that I have burned to CD, I
suppose that I will get around to completing that work someday. I mean, that is what us C++ guys do... right?
Class Description
I should first point out that many of the methods are unneccessary for general use and that they are all inline.
Because this was a game and I was going to need a ton of random numbers I opted for speed over size.
CRandomMT::CRandomMT()
This is the default CTOR.
CRandomMT::CRandomMT(ULONG seed)
A constructor that you provide the seed value.
CRandomMT::~CRandomMT()
The DTOR.
CRandomMT::SeedMT()
Used to seed or re-seed the random number generator.
ULONG CRandomMT::RandomMT()
Returns a unsigned long random number.
int CRandomMT::RandomRange(int hi, int lo)
Returns an int random number falling in the range specified.
int RollDice(int face, int number_of_dice)
Returns an int random number for the number of dice specified and the face of the die.
int CRandomMT::D#(int die_count)
Returns a simulated roll of the number of dice specified for the die (determined by #). These
are just wrappers around RollDice()
int CRandomMT::HeadsOrTails()
Returns 0 or 1, used to simulate a coin flip.
Example Usage
#include "Random.h"
.
.
.
CRandomMT MTrand(GetTickCount());
int rand_3d6 = MTrand.D6(3);
.
.
.
About the demo application
The demo application is a fairly lame example but does demonstrate the equidistribution provided from the MT
algorithm as well as the speed increase. Although I didn't percieve better distribution from the algorithm,
the speed increase is significant witht the random routine being placed inline. Here we simulate flipping
100,000 coins and then visualy display the results with a CProgressCtrl
and a couple static controls
for the textual display.
NuMega TrueTime Statistics *
CRandomMT::RandomMT() not inlined
Function | % in function | Called | Avgerage |
---|
|
rand() | 23.88 | 100,000 | 0.73 |
FlipRand() | 13.76 | 1 | 41,864.76 |
CRandomMT::RandomMT() | 19.83 | 100,000 | 0.60 |
FlipMersenne() | 13.60 | 1 | 41,378.81 |
NuMega TrueTime Statistics *
CRandomMT::RandomMT() inlined
Function | % in function | Called | Avgerage |
---|
|
rand() | 31.76 | 100,000 | 0.72 |
FlipRand() | 18.22 | 1 | 41,507.23 |
CRandomMT::RandomMT() | INLINED |
FlipMersenne() | 10.88 | 1 | 26,168.42 |
*
- % in function: Time spent on this line and the functions it called as a percentage of the time spent in the entire function.
- Called: Number of times the function was called.
- Average: Average execution time of the function during the session.
Faster?
In the speed category it is clear that the MT algorithm is much faster.
The difference is that FlipMersenne()
in the inlined version averaged 26,168.42 -vs- the
41,507.23 of FlipRand()
that is around a 45% increase in speed. This can be important
in a game that needs to serve up tons of random numbers. Even the smaller increase in speed found in
the non-inlined version results in big processor savings for applications that need to generate billions
of random numbers.
Equidistribution?
The probability of a coin flip being heads or tails is expressed using the the mathmatical formula
1/n. Where n is the number of times the coin is tossed. In the demo application, we simulated
flipping the coin 100,000 times. To my surprise rand()
provided very good results based on a
50/50 assumption.
I started the application and brought up Excel, next I simulated flipping 100,000 coins 10 times and
then I averaged the results from each of those iterations. Here are the results:
Mersenne Twister
FLIP(s) | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | AVG % |
HEADS | 50006 | 49910 | 49883 | 49673 | 49962 | 50073 | 49965 | 49768 | 50105 | 50116 | 49.946 |
TAILS | 49994 | 50090 | 50117 | 50327 | 50038 | 49927 | 50035 | 50232 | 49895 | 49884 | 50.054 |
rand()
FLIP(s) | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | AVG % |
HEADS | 49998 | 50091 | 49907 | 50106 | 50144 | 50097 | 49919 | 49888 | 50073 | 49847 | 50.007 |
TAILS | 50002 | 49909 | 50093 | 49894 | 49856 | 49903 | 50081 | 50112 | 49927 | 50153 | 49.993 |
The above results for rand()
appear to come closer to the desired 50/50 distribution of a coin
flip than that of MT. When one adds even a small error percentage to this statistical sample, it becomes
clear that this test is more a wash than anything else. This leaves us with only speed as the determining
factor in the decision to use the MT algorithim.
Conclussion
The pursuit of the perfect PRNG is an ongoing effort that eludes computer scientist and mathematicians alike.
The Mersenne Twister is generally considered to be fast, small and provides equal distribution. I like the
fact that I can generate 1,000,000,000 random numbers in about 0.45 seconds on a PIII 900mhz machine (with an urolled loop).
Thanks
Thanks go out to Makoto Matsumoto and Takuji Nishimura for creating the algorithm. I'd also like to thank my friend and business partner Derek Licciardi for his input and expertise in statistical analysis.
Links
References
- [1] "Mersenne Twister: A 623-Dimensionally Equidistributed Uniform Pseudo-Random Number Generator", M. Matsumoto and T. Nishimura, ACM Transactions on Modeling and Computer Simulation, Vol. 8, No. 1, January 1998, pp 3--30.
- [2] Mersenne Twister: A random number generator, www.math.keio.ac.jp/~matumoto/emt.html
History
- 19 Feb 2003 - updated source download
Dave has been programming for the past 20+ years first on a variety of platforms and operating systems using various languages. As a hobbyist Dave cut his teeth on the Commodore Pet and the 64 coding in basic and then moving to 6502 ASM. Dave moved to the Amiga using 68000 ASM and then C. His knowledge of the C language offered the stepping stone for him to make his hobby his profession taking a position coding C on an AIX Unix platform. Since then he has worked on many flavors of Unix, QNX, Windows (3.11 – present), and has been coding games for his Pocket PC in his spare time.
Dave lives in Indiana with his two teenage daughters and two cats.