It isn’t very often that you need a pseudo random number generator (PRNG) that can go forwards or backwards in time, or skip to specific points in the future or the past. However, if you are ever writing a game like Braid and do end up needing one, here’s one way to do it.

At the core of how this is going to work, we are going to keep track of an index, and have some way to convert that index into a random number. if we want to move forward in time, we will just increment the index. If we want to move backwards in time, we will just decrement the index. It’s super simple from a high level, but how are we going to convert an index into a random number?

There are lots of pseudo random number generators out there that we could leverage for this purpose, the most famous being C++’s built in “rand()” function, and another one famous in the game dev world is the Mersenne Twister.

I’m going to do something a little differently though as it leads well into the next post I want to write, and may be a little bit different than some people are used to seeing; I want to use a hash function.

## Murmur Hash 2

Good hash functions have the property that small changes in input give large changes in output. This means that if we hash the number 1 and then hash the number 2, that they ought not to be similar output, they ought to be wildly different numbers in the usual case. Sometimes, just like real random numbers, we might get 2 of the same numbers in a row, but that is the desired behavior to have the output act like real random sequences.

There are varying levels of quality of hash functions, ranging from a simple string “hash” function of using the first character of a string (super low quality hash function, but super fast) all the way up to cryptographic quality hash functions like MD5 and SHA-1 which are a lot higher quality but also take a lot longer to generate.

In our usage case, I’m going to assume this random number generator is going to be used for game use, where if the player can discover the pattern in the random numbers, they won’t be able to gain anything meaningful or useful from that, other than at most be able to cheat at their own single player game. However, I really do want the numbers to be fairly random to the casual eye. I don’t want visible patterns to be noticeable since that would decrease the quality of the gameplay. I would also like my hash to run as quickly as possible to keep game performance up.

Because of that level of quality I’m aiming for, I opted to go with a fast, non cryptographic hash function called Murmur Hash 2. It runs pretty quick and it gives pretty decent quality results too – in fact the official Murmur Hash Website claims that it passes the Chi Squared Test for “practically all keysets & bucket sizes”.

If you need a higher quality set of random numbers, you can easily drop in a higher quality hash in place of Murmur Hash. Or, if you need to go the other way and have faster code at the expensive of random number quality, you can do that too.

## Speed Comparison

How fast is it? Here’s some sample code to compare it vs C++’s built in rand() function, as well as an implementation of the Mersenne Twister I found online that seems to preform pretty well.

#include #include #include #include #include "tinymt32.h" // from http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/TINYMT/index.html // how many numbers to generate #define NUMBERCOUNT 10000000 // Generate 10 million random numbers // profiling macros #define PROFILE_BEGIN { LARGE_INTEGER freq; LARGE_INTEGER start; QueryPerformanceFrequency(&freq); QueryPerformanceCounter(&start); #define PROFILE_END(label) LARGE_INTEGER end; QueryPerformanceCounter(&end); printf(label " - %f msrn", ((double)(end.QuadPart - start.QuadPart)) * 1000.0 / freq.QuadPart); } // MurmurHash code was taken from https://sites.google.com/site/murmurhash/ //----------------------------------------------------------------------------- // MurmurHash2, by Austin Appleby // Note - This code makes a few assumptions about how your machine behaves - // 1. We can read a 4-byte value from any address without crashing // 2. sizeof(int) == 4 // And it has a few limitations - // 1. It will not work incrementally. // 2. It will not produce the same results on little-endian and big-endian // machines. unsigned int MurmurHash2 ( const void * key, int len, unsigned int seed ) { // 'm' and 'r' are mixing constants generated offline. // They're not really 'magic', they just happen to work well. const unsigned int m = 0x5bd1e995; const int r = 24; // Initialize the hash to a 'random' value unsigned int h = seed ^ len; // Mix 4 bytes at a time into the hash const unsigned char * data = (const unsigned char *)key; while(len >= 4) { unsigned int k = *(unsigned int *)data; k *= m; k ^= k >> r; k *= m; h *= m; h ^= k; data += 4; len -= 4; } // Handle the last few bytes of the input array switch(len) { case 3: h ^= data[2] << 16; case 2: h ^= data[1] <> 13; h *= m; h ^= h >> 15; return h; } void RandTest() { for(int index = 0; index < NUMBERCOUNT; ++index) int i = rand(); } unsigned int MurmurTest() { unsigned int key = 0; for(int index = 0; index < NUMBERCOUNT; ++index) key = MurmurHash2(&key,sizeof(key),0); return key; } // g_twister is global and inited in main so it doesnt count towards timing tinymt32_t g_twister; unsigned int TwisterTest() { unsigned int ret = 0; for(int index = 0; index < NUMBERCOUNT; ++index) ret = tinymt32_generate_uint32(&g_twister); return ret; } int main(int argc, char**argv) { // rand() test PROFILE_BEGIN; RandTest(); PROFILE_END("rand()"); // hash test unsigned int murmurhash; PROFILE_BEGIN; murmurhash = MurmurTest(); PROFILE_END("Murmur Hash 2"); // twister test g_twister.mat1 = 0; g_twister.mat2 = 0; tinymt32_init(&g_twister, 0); unsigned int twister; PROFILE_BEGIN; twister = TwisterTest(); PROFILE_END("Mersenne Twister"); // show the results system("pause"); // this is here so that the murmur and twister code doesn't get optimized away printf("%u %urn", murmurhash, twister); return 0; }

Here's the output of that code run in release on my machine, generating 10 million random numbers of each type. You can see that murmurhash takes about 1/3 as long as rand() but is not quite as fast as the Mersenne Twister. I ran this several times and got similar results, so all in all, Murmur Hash 2 is pretty fast!

## Final Code & Sample Output

Performance looks good but how about the time traveling part, and how about seeing some example output?

Here’s the finalized code:

#include #include #include // MurmurHash code was taken from https://sites.google.com/site/murmurhash/ //----------------------------------------------------------------------------- // MurmurHash2, by Austin Appleby // Note - This code makes a few assumptions about how your machine behaves - // 1. We can read a 4-byte value from any address without crashing // 2. sizeof(int) == 4 // And it has a few limitations - // 1. It will not work incrementally. // 2. It will not produce the same results on little-endian and big-endian // machines. unsigned int MurmurHash2 ( const void * key, int len, unsigned int seed ) { // 'm' and 'r' are mixing constants generated offline. // They're not really 'magic', they just happen to work well. const unsigned int m = 0x5bd1e995; const int r = 24; // Initialize the hash to a 'random' value unsigned int h = seed ^ len; // Mix 4 bytes at a time into the hash const unsigned char * data = (const unsigned char *)key; while(len >= 4) { unsigned int k = *(unsigned int *)data; k *= m; k ^= k >> r; k *= m; h *= m; h ^= k; data += 4; len -= 4; } // Handle the last few bytes of the input array switch(len) { case 3: h ^= data[2] << 16; case 2: h ^= data[1] <> 13; h *= m; h ^= h >> 15; return h; } class CReversablePRNG { public: CReversablePRNG() { m_index = 0; m_seed = 0; } unsigned int NextNumber() { unsigned int ret = MurmurHash2(&m_index, sizeof(m_index), m_seed); m_index++; return ret; } unsigned int LastNumber() { unsigned int lastIndex = m_index - 2; unsigned int ret = MurmurHash2(&lastIndex, sizeof(lastIndex), m_seed); m_index--; return ret; } // to be able to save / restore state for a save game or whatever else void GetState(unsigned int &index, unsigned int &seed) { index = m_index; seed = m_seed; } void SetState(unsigned int index, unsigned int seed) { m_index = index; m_seed = seed; } private: unsigned int m_index; unsigned int m_seed; }; int main(int argc, char**argv) { // create and seed our random number generator. If two similar numbers are hashed // they should give very different results usually, so for a seed, we can hash the // time in seconds, even though the number from run to run should be really similar CReversablePRNG prng; unsigned int currentTime = time(NULL); unsigned int seed = MurmurHash2(¤tTime, sizeof(currentTime), 0x1337beef); prng.SetState(0, seed); // display our seed and our table header printf("seed = %urn", seed); printf("index | raw number | mod 10rn"); printf("---------------------------rn"); // generate 10 numbers forward for (int index = 0; index < 10; ++index) { unsigned int nextNumber = prng.NextNumber(); printf("%2i | %10u | %urn", index, nextNumber, nextNumber % 10); } // generate 3 numbers back printf("rn"); for (int index = 0; index < 3; ++index) { unsigned int lastNumber = prng.LastNumber(); printf("%2i | %10u | %urn", 8 - index, lastNumber, lastNumber % 10); } // generate 5 numbers forward printf("rn"); for (int index = 0; index < 5; ++index) { unsigned int nextNumber = prng.NextNumber(); printf("%2i | %10u | %urn", 7 + index, nextNumber, nextNumber % 10); } system("pause"); return 0; }

## Next Up

Hopefully you enjoyed this post!

Next up I’m going to be applying this code to the problem of shuffling to continue on from the post where I tried to do that before: Fast & Lightweight Random “Shuffle” Functionality.

Pingback: Fast & Lightweight Random “Shuffle” Functionality – FIXED! « The blog at the bottom of the sea