Random Number Generator

Hello JGO Community

While browsing around these forums, lately i’ve encountered alot of posts about Random numbers and random number generators. I’m here today to share a piece of code that we use very successful in our engine for fast and efficient random number generation. The source code is mainly thanks to Matthew Jackson who is collaborating with me in making the engine into a reality. Without further ado, here is the source, it is a 63 bit random number generator.

  • Basic Feature List

~ ability to get and set the current seed
~ ability to get random numbers between min and max (long)
~ ability to get random numbers between min and max (float)

  • Source


/**
 * @author Matthew Jackson - Original Author
 * @author David Arayan (DrHalfway) - Modifications to code
 * @version 1.0 - Used as part of HalfWay Game Engine Math Library
 */
public class Random {
	private long seed;
	
	/**
	 * Default Constructor which generates the seed based on system time.
	 */
	public Random(){
			seed = System.nanoTime();
	}
	
	/**
	 * 
	 * @param seed The value to start the random number generator. 
	 * A seed value of 0 generates a random seed based on system time 
	 * which is the same as the default constructor.
	 *
	 */
	public Random(long seed){
		if (seed == 0){
			seed = System.nanoTime();
		}
		this.seed = seed;
	}
	
	/**
	 * Uses an XORShift to produce a 63 bit random number.
	 * Java's variables are all signed so the 64th bit is lost.
         * Period is 2^64 - 1 (thanks Roquen)
	 * @return A pseudo-random 63 bit number
	 */
	private long rand() {
		  seed ^= (seed << 13);
		  seed ^= (seed >>> 7);
		  seed ^= (seed << 17);
		  //return Math.abs(seed - 1);
                  return seed;
	}
	
	/**
	 * Allows the user to assign a min and max value for the numbers
	 * they want returned.
	 * @param min Minimum value to be returned (Can be negative)
	 * @param max Maximum value to be returned (Can be negative)
	 * @return A long random value between min and max
	 */
	public long rand(long min, long max ){
		if(min > max){
			return rand(max,min);
		}
		if (min == max) {
			return min;
		}
		return (rand() % (max + 1 - min)) + min;
	}
	
	/**
	 * A floating point random number generator.
	 * 
	 * @param min The minimum floating point value
	 * @param max The maximum floating point value
	 * @param dev The number of decimal places you want returned.
	 * dev = 10 will return 1 decimal place.
	 * dev = 100 will return 2 decimal places.
	 * @return returns random floating point value between min and max.
	 * 
	 */
	public float randf(float min, float max, int dev) {
		if (min == max) {
			return min;
		}
		return ((float)rand((int)(min * dev), (int)(max * dev))/dev);
	}
	
	/**
	 * Returns the current seed for the random number generator
	 */
	public long getSeed() {
		return seed;
	}
	
	/**
	 * Sets the current seed to seed
	 */
	public void setSeed(final long seed) {
		this.seed = seed;
	}
}


Below is a simple test program



public class test {
	public static void main(String[] args) {
		
		Random rand = new Random();
		final int guess = 42;
		
		while (true) {			
			long guessed = rand.rand(1, 100);
			
			System.out.println(guessed);
			
			if (guessed == guess) {
				break;
			}
		}
	}
}


EDIT : Below I’ve included a small benchmark program for bench marking the run-times of the above Random generator Vs Java’s Random generator.



public class test {
	public static void main(String[] args) {
		
		Random xRandom = new Random();
		java.util.Random jRandom = new java.util.Random();
		
		final int runs = 1000;
		long startTime = 0, stopTime = 0;
		float xRun = 0.0f;
		float jRun = 0.0f;
		
		/*
		 * RUN THE JAVA RANDOM BENCHMARK
		 */
		
		startTime = System.nanoTime();
		
		for (int i = 0; i < runs; i++) {
			jRandom.nextInt(100);
		}
		
		stopTime = System.nanoTime();
		
		jRun += ((stopTime - startTime) * 1e-6);
		
		/*
		 * RUN THE XOR RANDOM BENCHMARK
		 */
		
		startTime = System.nanoTime();
		
		for (int i = 0; i < runs; i++) {
			xRandom.rand(0, 100);
		}
		
		stopTime = System.nanoTime();
		
		xRun += ((stopTime - startTime) * 1e-6);
		
		System.out.println("RunTime for JRandom: " + jRun + "ms For: " + runs + " runs");
		System.out.println("RunTime for XRandom: " + xRun + "ms For: " + runs + " runs");
	}
}


And Below are bench-marking results for different runs on my machine.

RunTime for JRandom: 0.283136ms For: 1000 runs
RunTime for XRandom: 0.236113ms For: 1000 runs

RunTime for JRandom: 3.022447ms For: 10,000 runs
RunTime for XRandom: 2.67428ms For: 10,000 runs

RunTime for JRandom: 11.892359ms For: 100,000 runs
RunTime for XRandom: 5.694059ms For: 100,000 runs

RunTime for JRandom: 23.15375ms For: 1,000,000 runs
RunTime for XRandom: 9.104691ms For: 1,000,000 runs

RunTime for JRandom: 120.58705ms For: 10,000,000 runs
RunTime for XRandom: 33.68179ms For: 10,000,000 runs

RunTime for JRandom: 1094.5873ms For: 100,000,000 runs
RunTime for XRandom: 289.4332ms For: 100,000,000 runs

Hope it helps you all for any game creation needs. All comments/contributions to the source are welcome!

EDIT2 - I’ve generated and added a bitmap image for comparison of “randomness” of this versus java’s random generator. Basically a pixel value is picked randomly in the range of 0 to 255. The images are 512 X 512 in size. Left is the above Generator, Right is Java’s Generator.

EDIT3 - Changed the rand() function as suggested by Roquen, cheers!

~DrHalfway

How does it fare on a randomness test suite? Not that I expect j.u.Random to show flying colors there either, but it’d still be a good benchmark for comparison other than raw speed.

http://www.phy.duke.edu/~rgb/General/dieharder.php

How much randomness do you get out of such a system? It does not look like it would work very well. Is it based on an existing rng scheme?

You should also add something to the no argument constructor to prevent two instances created around the same time from sharing the same seed. (Just in case?)

   private long seed;
+   private static long thing = System.nanoTime() ^ 0x1234567812345678L;
   
   /**
    * Default Constructor which generates the seed based on system time.
    */
   public Random(){
+        seed = (thing++) + System.nanoTime();
   }

I have updated the above with images of a randomness test on two grey scale images of size 512 x 512, comparing Java’s random generator Vs Halfway Generator.

Also, as far as nano times are concerned, it is not a problem to do this.



public static void main(String[] args) {		
	Random rand = new Random();
	Random rand2 = new Random();
		
	System.out.println("First Nano Time: " + rand.getSeed());
	System.out.println("Second Nano Time: " + rand2.getSeed());
}

// results

/*
First Nano Time: 12199246486318
Second Nano Time: 12199246488319
*/


This is more than enough for varying “randomness” between two generators. If you’d like, you can make the seed “static”, the random pattern will not be repeating itself for a very-very-VERY long time ;D

Seems legit. I like the fact it runs fast. Usually I even use Math.random() ::slight_smile:

It’s an XORShift RNG (A subset of a Linear Feedback Shift Register [LFSR]) . The system is extremely fast at generating pseudo-random numbers and has been in use for a long time. The numbers that are being used for the shifts aren’t just off the top of your head. They are picked to create a maximal 64 bits shift register (63 bits are caused from the absolute function, effectively doubling up the numbers that can be returned therefore halving the actual variation in numbers returned).

With LFSRs the sequence of numbers will eventually repeat. To be maximal means every single combination of bits (apart from the all zeroes) will appear before the sequence repeats. This means there are a total 9,223,372,036,854,775,808 numbers in a 64-bit LFSR that will appear before the sequence repeats itself when using a maximal sequence.

This system can be “cracked” with a given sample size so you wouldn’t use it alone in a security system but there are security algorithms based off it.

However for the sake of games programming, 99 times out of 100 your random number generator doesn’t need to be secure.

If you want more information there is plenty of information on the topic all over Google.

I hope this enlightens some of you who haven’t seen this type of RNG before.

Matt.

XORSHIFT and some other notes I made here: http://www.java-gaming.org/topics/math-random/18426/msg/168661/view.html#msg168661

The version above is very broken. You can’t just change ops around willy-nilly. As written it has an unknown period…and that’s very very bad.


   /** XORSHIFT base generator (George Marsaglia "Xorshift RNGs", Row 1 Column 3)*/
   private long rand() {
        seed ^= (seed <<  1);
        seed ^= (seed >>> 3); 
        seed ^= (seed << 45);
        return seed;
   }

This will have a period of (264-1), but I have know idea about the statistical properties about this specific choice.

The ranged result methods need rethinking.

FOLLOW UP! In a moment of work avoidance I ran the corrected version above through SmallCrush and it performs worse than a good LCG. Specifically it fails 8 out of 15 tests. Didn’t bother with BigCrush. Results here: http://pastebin.java-gaming.org/c9c0d928f22

Changing the values to the one mentioned in the paper as being on of his favorites [13, 17, 5], drops the failure rate to 3. Summary (SmallCrush only):
1 BirthdaySpacings eps 6 MaxOft eps 8 MatrixRank eps

I use a m sequence with a plain counter. I then add these before using them. This makes it non linear but adds a bias in the last bit. Not much though. Also the period becomes 2^128-1. Not that you need that kind of period.

On XORSHIFT: I screwed up when I glanced at the paper and grabbed a 32-bit constant set (oops). A proper 64-bit set is [13,7,17], which only fails one SmallCrush (MatrixRank).

@delt0r: Most of the time I just use a 32-bit LCG and don’t worry about it. If I need something better, then WELL1024.

I took over because I wrote the RNG and to be fair the OP shouldn’t have posted it because he knew none of the theory behind it so I came in to answer and clarify things for some of the posters.

As for the period as you said it is 2^64 - 1 (Maximal) which is why I chose these numbers. As for the statistical properties, I didn’t realise that different choices in numbers could effect it. I will have to read up and better understand all these properties. As for now, I will change the values to your last suggested ones and have the OP correct it in the code.

Edit: I’m not having a go at the OP there he is trying to do the right thing by the community and provide you guys with some working code =)

Updated the rand() function as suggested by Matt and Roquen, cheers guys. Oh and Matt, welcome to the community.

That second line has a typo: seed >> 7 must be seed >>> 7. The signed instead of unsigned shift breaks everything. I’d strongly recommend losing the Math.abs(seed - 1) and just return seed. It’s a significant performance hit that doesn’t buy you anything.

On the whole I’d really suggest using a 32-bit xorshift instead of 64-bit. You just don’t need the long period (or rather is massively unlikely…and if you do you should know why…and you should probably rethink what you’re doing if you need upward to ~3 billion random number to make a decision in a video game). On 64-bit VMs the performance will be similar, but not so for 32-bit…which is a sad reality. Android? I’ll let people that have a clue respond here.

Err…I’ll just toss together an example and let people critic me!

Cheers again Roquen, updated the code accordingly. As far as Android goes, haven’t really had a bottleneck with using the above version, then again, we only get something like 80FPS on Android compared to 4000FPS PC (The whole engine). Might look into a 32 bit version instead.

This may not be true but isn’t Math.abs() call extremely fast? I know it is on C++ (normally translates to a single instruction on modern hardware) but have’t really bothered to benchmark java, always assumed its about the same.

Speed is always relative. In this case you have a dependency chain of length 6 which is being extended to 9 (negate, conditional move, subtract)…so it’s 50% longer. Luckily there is no reason to prefer low-bits over high bits, so you could replace the return statement with: return (seed >>>1); if you want to stick with unsigned 63 bit results (which only pushes the chain length up to 7, or ~17% longer).

My opinion is to return 64-bit results and let call-site do any fix-up they need locally. Personally the vast majority of my random number generation is for some fixed number of bits. But having said that, when inlined the two shift sequence would be folded into one given the proposed change above.

I see you took out the return Math.abs(seed - 1); That made the RNG scheme seem dubious. There is no reason why you could not get 64 bits out of this scheme in Java if it works for a 64 bit unsigned int. (And it sounds like there is no need to throw out any bits.) I look into my crystal ball and see someone adding that line to get results in the domain [0, 2^63 - 1] instead of any 64 bit signed number other than zero. If that’s the case you’re unnecessarily limiting yourself to one fewer bit of randomness per call. You also changed a comment to read "Period is 2^64 - 1 " instead of “Max period is 2^64 - 1”, which is good. I was going to say the minimum period is more important, but you’ve already corrected the comment and the old code that broke it. There are still a few things you should change.

public class Random {
   private long seed;
   
   public Random(){
+        long s = System.nanoTime(); // See note
+        setSeed(s != 0 ? s : 1);
   }
   
   public Random(long seed){
+        setSeed(seed);
   }
   
+  public long rand() {
        seed ^= (seed << 13);
        seed ^= (seed >>> 7);
        seed ^= (seed << 17);
        return seed;
   }

+  public long randNonNegative() {
+      return rand() >>> 1;
+  }

+  public long rand(int bits) {
+      return rand() >>> (64 - bits);
+  }
   
   public long rand(long min, long max ){
      if(min > max){ // See other note
+        long m = max;
+        max = min; 
+        min = m;
      }
      if (min == max) {
         return min;
      }
      return (rand() % (max + 1 - min)) + min;
   }
   
   public float randf(float min, float max, int dev) {
      if (min == max) {
         return min;
      }
+     return ((float)rand((long)(min * dev), (long)(max * dev))/dev); // Probably should be long and not int.
   }
   
   public long getSeed() {
      return seed;
   }
   
   public void setSeed(long seed) {
+     if(seed == 0)
+       throw new IllegalArgumentException("Seed cannot be zero."); // See third note
      this.seed = seed;
   }
}

Note 1: Although I can’t get two calls in a row to return the same nano time on my computer, that might not be the case on all platforms. Return values for System.nanoTime() can be as impercise as currentTimeMillis() which may not even have millisecond precision. It’s probably not likely that a computer is fast enough to create two objects but doesn’t have a timer accurate enough to return different values in that time span, but maybe there’s a small chance. I fixed something else that’s far less likely. nanoTime() could return zero, so that could create a bad Random instance.

Note 2: Is this the behavior you want for bad min/max values?

Note 3: Changed it to throw an exception to prevent zero values. I changed the constructor behavior, too, but maybe you wanted zero to be a special value. You can choose one method or the other (or allow zero seeds), but setSeed should probably be consistent with the constructor.

Shouldn’t it throw an exception in that case? o_O

Throwing an exception for setting a seed to zero is a bad idea IHMO. This problem should be addressed as follows:


/**
 * (blah, blah)
 * <p>
 * If seed is zero, then (we do this)
 */
public void setSeed(long seed) { ... }

Throwing an exception is exposing an implementation detail. As long as the code does (we do this), then it’s perfectly to contract. One common thing to do, say in procedural generation, is to use a pretty poor hashing function to seed a PRNG so zero is a pretty reasonable seeding value. The procedural content generator really should care about any specifics of whatever PRNG it ends up using.

@UltimateSin: I forgot to mention that the various shifting constants (and their permutations) only insure full period and not any statistical properties.

I agree in this case it exposes implementation details and have no interest in arguing one way or another, but I disagree that throwing certain exceptions exposes implementation details. Though in this case it does. But I don’t think you can avoid exposing implementation details if that were your only change. This class is not a generic stream of random bits and already abuses that problem. For example, there is no differentiation between seed and state. The fact that getState() getSeed() returns a long instead of an instance of RngState also carries that problem.

I mainly used the exception to convey an idea (there were no doc comments and people would have just ignored a “don’t use zero” statement.) It’s probably …

Sorry, I walked away from the computer, forgot my train of thought for this post, and remembered my original train of thought for my previous post. :stuck_out_tongue: My original thoughts were that the contract of setSeed should be that only values returnable by getSeed should be considered valid. A user of this class would only choose arbitrary seeds for the instantiation of the object (in which case the contract could reasonably be implementation specific because it is a constructor). All calls to setSeed should assume that the user knows they have a valid seed value, whether that key was obtained from getSeed() or obtained using a static utility function or hardcoded by the programmer. Under that contract, no implementation details are exposed in ordinary methods.

(The setSeed/getSeed name bugs me a lot more now. Seed should be changed to state.)

My verbage was unclear: I wasn’t talking about exceptions in general, only in these kinds of situations. WRT: seed vs. state. Two distinctly different notions…they only get blurred in short-period generators were state is representable by (say) a 32 or 64 bit integer. If you want to be nit-picky, then you can’t really get a seed, only a state…it’s simply a pragmatic choice to name the methods so…and it would be pretty unclear to people that don’t understand the “guts” of generators to have “setSeed” & “getState” for these short period generators.