11

I need to generate random integers within a maximum. Since performance is critical, I decided to use a XORShift generator instead of Java's Random class.

long seed = System.nanoTime();
seed ^= (seed << 21);
seed ^= (seed >>> 35);
seed ^= (seed << 4);

This implementation (source) gives me a long integer, but what I really want is an integer between 0 and a maximum.

public int random(int max){ /*...*/}

What it is the most efficient way to implement this method?

eversor
  • 3,031
  • 2
  • 26
  • 46
  • Can't you just modulo the long by the maximum? – smk Nov 03 '12 at 20:56
  • @SajitKunnumkal This produces a (slight) bias for some integers (of course, may be negligible if max is sufficiently small). –  Nov 03 '12 at 21:03
  • If performance is critical, you should avoid `nanoTime()`. – finnw Nov 03 '12 at 21:25
  • If performance is critical, you should probably use a simple LCG instead. If max is constant and known ahead of time, you could even build an ad-hoc LCG that will directly generate numbers in the range [0, max) with no bias. – CAFxX Nov 23 '12 at 23:13
  • Performance shouldn't be a consideration when using XORShift; the fact that you're using it means that it's for a non-critical application which means System.nanoTime() is perfectly suitable. – Sandy Simonton May 25 '16 at 21:40

2 Answers2

8

I had some fun with your code and came up with this:

public class XORShiftRandom {

private long last;
private long inc;

public XORShiftRandom() {
    this(System.currentTimeMillis());
}

public XORShiftRandom(long seed) {
    this.last = seed | 1;
    inc = seed;
}

public int nextInt(int max) {
    last ^= (last << 21);
    last ^= (last >>> 35);
    last ^= (last << 4);
    inc += 123456789123456789L;
    int out = (int) ((last+inc) % max);     
    return (out < 0) ? -out : out;
}

}

I did a simple test and it is about Four times as fast as the java.util.Random

If you are intrested in how it works you can read this paper:

Disclamer:

The code above is designed to be used for research only, and not as a replacement to the stock Random or SecureRandom.

Frank
  • 16,476
  • 7
  • 38
  • 51
  • Any explanation for the shift values of 21, 35, 4? Namely, did you test with other values for speed and/or randomness? – cory.todd Mar 22 '16 at 01:37
  • These values come from the OP, i did not play with them. Maybe ask the Question to him. – Frank Apr 13 '16 at 07:49
  • A quick discussion of why those values are of use is found here: http://www.javamex.com/tutorials/random_numbers/xorshift.shtml#.V0Yb5nUrKkA – Sandy Simonton May 25 '16 at 21:41
  • 1
    Note that (unlike `java.util.Random`) your results are not exactly uniformly distributed. They can't be as `2**32` (the number of ints) is not divisible by `max`, except for powers of two. For really big `max` like `3e9`, the ratio is 1:2, for small values, it's much better. – maaartinus Aug 24 '17 at 08:30
  • @maaartinus, thats why i did also put the disclaimer... it can indeed be further improved, but that is for who ever decides to use it. – Frank Aug 25 '17 at 09:33
  • 1
    @maaartinus I think this is a very important point that shouldn't be underplayed. I interpreted the requirement as being to find a performance-efficient method to scale the result to a desired range without compromising the uniform-distribution property of the original algorithm. Taking a modulus does not fulfill this. – Terry Burton Dec 04 '17 at 12:11
  • I would suggest using the method described here: https://stackoverflow.com/a/4196060/2568535 – Terry Burton Dec 04 '17 at 12:17
  • @TerryBurton This may loop close to forever. For a simple fix, see my answer. – maaartinus Dec 04 '17 at 12:54
4

Seeding

There are many problems here. In case, you're using nanoTime more than once, you'd definitely doing it wrong as nanoTime is slow (hundreds of nanoseconds). Moreover, doing this probably leads to bad quality.

So let's assume, you seed your generator just once.

Uniformity

If care about uniformity, then there are at least two problems:

Xorshift

It never generates zero (unless you unlucky with seeding and then zero is all you ever get).

This is easily solvable by something as simple as

private long nextLong() {
    x ^= x << 21;
    x ^= x >>> 35;
    x ^= x << 4;
    y += 123456789123456789L;
    return x + y;
}

The used constant is pretty arbitrary, except for it must be odd. For best results, it should be big (so that all bits change often), it should have many bit transitions (occurrences of 10 and 01 in the binary representation) and it shouldn't be too regular (0x55...55 is bad).

However, with x!=0 and any odd constant, uniformity is guaranteed and the period of the generator is 2**64 * (2*64-1).

I'd suggest seeding like

seed = System.nanoTime();
x = seed | 1;
y = seed;

nextInt(int limit)

The accepted answer provides non-uniformly distributed values for the reason I mentioned in a comment. Doing it right is a bit complicated, you can copy the code from Random#nextInt or you can try something like this (untested):

public int nextInt(int limit) {
    checkArgument(limit > 0);
    int mask = -1 >>> Integer.numberOfLeadingZeros(limit);
    while (true) {
        int result = (int) nextLong() & mask;
        if (result < limit) return result;
    }
}

Above, the mask looks in binary like 0...01...1, where the highest one corresponds with the highest one of limit. Using it, a uniformly distributed number in the range 0..mask gets produced (unifomrnity is easy as mask+1 is a power of two). The conditional refuses numbers not below limit. As limit > mask/2, this happens with a probability below 50%, and therefore the expected number of iterations is below 2.

Recommendation

Fooling around with this is fun, but testing it is hard and I'd recommend using ThreadLocalRandom instead, unless you need reproducibility.

maaartinus
  • 44,714
  • 32
  • 161
  • 320