Skip to content

Commit

Permalink
New XoRoRNG based on xoroshiro128+ algo
Browse files Browse the repository at this point in the history
David Blackman and Sebastiano Vigna recently debuted a new RNG algorithm
that is currently the fastest in Vigna's Xorshift shootout (
http://xoroshiro.di.unimi.it/ ), so naturally SquidLib needs to
implement it. It is **tied for the fastest** RandomnessSource with
LightRNG, but also has a larger period before it starts repeating
values. It does not implement StatefulRandomness, unfortunately, but
otherwise could be the preferred RNG in SquidLib for many purposes.
Being able to efficiently get the state of LightRNG is more useful from
the library perspective (finding which states cause defective procedural
generation, for instance), so the default will remain with LightRNG but
you can create your own RNG with a XoRoRNG easily.
  • Loading branch information
tommyettinger committed Apr 30, 2016
1 parent b92c07f commit 0a4a383
Show file tree
Hide file tree
Showing 2 changed files with 232 additions and 4 deletions.
Expand Up @@ -196,7 +196,7 @@ public long doThreadLocalRandom()
return seed;
}

@Benchmark
//@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public void measureThreadLocalRandom() throws InterruptedException {
Expand All @@ -215,7 +215,7 @@ public long doThreadLocalRandomInt()
return iseed;
}

@Benchmark
//@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public void measureThreadLocalRandomInt() throws InterruptedException {
Expand Down Expand Up @@ -404,7 +404,7 @@ public long doChaosR()
return seed;
}

@Benchmark
//@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public void measureChaosR() throws InterruptedException {
Expand All @@ -423,13 +423,84 @@ public long doChaosRInt()
return iseed;
}

@Benchmark
//@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public void measureChaosRInt() throws InterruptedException {
iseed = 9000;
doChaosRInt();
}




public long doXoRo()
{
XoRoRNG rng = new XoRoRNG(seed);
for (int i = 0; i < 1000000000; i++) {
seed += rng.nextLong();
}
return seed;
}

@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public void measureXoRo() throws InterruptedException {
seed = 9000;
doXoRo();
}

public long doXoRoInt()
{
XoRoRNG rng = new XoRoRNG(iseed);
for (int i = 0; i < 1000000000; i++) {
iseed += rng.nextInt();
}
return iseed;
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public void measureXoRoInt() throws InterruptedException {
iseed = 9000;
doXoRoInt();
}

public long doXoRoR()
{
RNG rng = new RNG(new XoRoRNG(seed));
for (int i = 0; i < 1000000000; i++) {
seed += rng.nextLong();
}
return seed;
}

@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public void measureXoRoR() throws InterruptedException {
seed = 9000;
doXoRoR();
}

public long doXoRoIntR()
{
RNG rng = new RNG(new XoRoRNG(iseed));
for (int i = 0; i < 1000000000; i++) {
iseed += rng.nextInt();
}
return iseed;
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public void measureXoRoIntR() throws InterruptedException {
iseed = 9000;
doXoRoIntR();
}


/*
public long doSecureRandom()
{
Expand Down
157 changes: 157 additions & 0 deletions squidlib-util/src/main/java/squidpony/squidmath/XoRoRNG.java
@@ -0,0 +1,157 @@
/* Written in 2016 by David Blackman and Sebastiano Vigna (vigna@acm.org)
To the extent possible under law, the author has dedicated all copyright
and related and neighboring rights to this software to the public domain
worldwide. This software is distributed without any warranty.
See <http://creativecommons.org/publicdomain/zero/1.0/>. */
package squidpony.squidmath;

import squidpony.StringKit;

/**
* A port of Blackman and Vigna's xoroshiro 128+ generator; should be very fast and produce high-quality output.
* Testing shows it is within 5% the speed of LightRNG, sometimes faster and sometimes slower, and has a larger period.
* It's called XoRo because it involves Xor as well as Rotate operations on the 128-bit pseudo-random state.
* Original version at http://xoroshiro.di.unimi.it/xoroshiro128plus.c
* Written in 2016 by David Blackman and Sebastiano Vigna (vigna@acm.org)
* @author Sebastiano Vigna
* @author David Blackman
* @author Tommy Ettinger
*/
public class XoRoRNG implements RandomnessSource {

private static final long DOUBLE_MASK = (1L << 53) - 1;
private static final double NORM_53 = 1. / (1L << 53);
private static final long FLOAT_MASK = (1L << 24) - 1;
private static final double NORM_24 = 1. / (1L << 24);

private static final long serialVersionUID = 1018744536171610261L;

private long state0, state1;

/**
* Creates a new generator seeded using Math.random.
*/
public XoRoRNG() {
this((long) (Math.random() * Long.MAX_VALUE));
}

public XoRoRNG(final long seed) {
setSeed(seed);
}

@Override
public int next(int bits) {
return (int) (nextLong() & (1L << bits) - 1);
}

@Override
public long nextLong() {
final long s0 = state0;
long s1 = state1;
final long result = s0 + s1;

s1 ^= s0;
state0 = Long.rotateLeft(s0, 55) ^ s1 ^ (s1 << 14); // a, b
state1 = Long.rotateLeft(s1, 36); // c

return result;
}


/**
* Can return any int, positive or negative, of any size permissible in a 32-bit signed integer.
* @return any int, all 32 bits are random
*/
public int nextInt() {
return (int)nextLong();
}

/**
* Exclusive on the upper bound. The lower bound is 0.
* @param bound the upper bound; should be positive
* @return a random int less than n and at least equal to 0
*/
public int nextInt( final int bound ) {
if ( bound <= 0 ) return 0;
int threshold = (0x7fffffff - bound + 1) % bound;
for (;;) {
int bits = (int)(nextLong() & 0x7fffffff);
if (bits >= threshold)
return bits % bound;
}
}
/**
* Inclusive lower, exclusive upper.
* @param lower the lower bound, inclusive, can be positive or negative
* @param upper the upper bound, exclusive, should be positive, must be greater than lower
* @return a random int at least equal to lower and less than upper
*/
public int nextInt( final int lower, final int upper ) {
if ( upper - lower <= 0 ) throw new IllegalArgumentException("Upper bound must be greater than lower bound");
return lower + nextInt(upper - lower);
}

/**
* Exclusive on the upper bound. The lower bound is 0.
* @param bound the upper bound; should be positive
* @return a random long less than n
*/
public long nextLong( final long bound ) {
if ( bound <= 0 ) return 0;
long threshold = (0x7fffffffffffffffL - bound + 1) % bound;
for (;;) {
long bits = nextLong() & 0x7fffffffffffffffL;
if (bits >= threshold)
return bits % bound;
}
}

public double nextDouble() {
return (nextLong() & DOUBLE_MASK) * NORM_53;
}

public float nextFloat() {
return (float) ((nextLong() & FLOAT_MASK) * NORM_24);
}

public boolean nextBoolean() {
return (nextLong() & 1) != 0L;
}

public void nextBytes(final byte[] bytes) {
int i = bytes.length, n = 0;
while (i != 0) {
n = Math.min(i, 8);
for (long bits = nextLong(); n-- != 0; bits >>= 8) {
bytes[--i] = (byte) bits;
}
}
}

/**
* Sets the seed of this generator. Passing this 0 will just set it to -1
* instead.
*
* @param seed the number to use as the seed
*/
public void setSeed(final long seed) {

long state = seed + 0x9E3779B97F4A7C15L,
z = state;
z = (z ^ (z >>> 30)) * 0xBF58476D1CE4E5B9L;
z = (z ^ (z >>> 27)) * 0x94D049BB133111EBL;
state0 = z ^ (z >>> 31);
state = seed + 0x9E3779B97F4A7C15L;
z = state;
z = (z ^ (z >>> 30)) * 0xBF58476D1CE4E5B9L;
z = (z ^ (z >>> 27)) * 0x94D049BB133111EBL;
state1 = z ^ (z >>> 31);
}

@Override
public String toString() {
return "XoRoRNG with state hash 0x" + StringKit.hexHash(state0, state1) + 'L';
}
}

0 comments on commit 0a4a383

Please sign in to comment.