package squidpony.squidmath; import squidpony.annotation.GwtIncompatible; import java.security.SecureRandom; import java.util.Arrays; import java.util.concurrent.locks.ReentrantLock; /** * RandomnessSource using Mersenne Twister algorithm (not recommended). * <br> * Uses the Mersenne Twister algorithm to provide results with a longer period. * Mersenne Twister has known statistical vulnerabilities, however, and this * implementation is incredibly slow, which is why it is deprecated. You should * use {@link LongPeriodRNG} for most of the cases that MersenneTwister would be * good at in theory, or {@link IsaacRNG} for cases that need an extremely large * period and cryptographic-like properties. * <br> * * @author Daniel Dyer (Java Port) * @author Makoto Matsumoto and Takuji Nishimura (original C version) * @author Eben Howard - http://squidpony.com - howard@squidpony.com * @author Lewis Potter * @deprecated */ @GwtIncompatible /* Because of SecureRandom */ @Deprecated /* This code is really, really slow due to threading behavior, and should be avoided. */ public class MersenneTwister implements RandomnessSource { // The actual seed size isn't that important, but it should be a multiple of 4. private static final int SEED_SIZE_BYTES = 16; // Magic numbers from original C version. private static final int N = 624; private static final int M = 397; private static final int[] MAG01 = {0, 0x9908b0df}; private static final int UPPER_MASK = 0x80000000; private static final int LOWER_MASK = 0x7fffffff; private static final int BOOTSTRAP_SEED = 19650218; private static final int BOOTSTRAP_FACTOR = 1812433253; private static final int SEED_FACTOR1 = 1664525; private static final int SEED_FACTOR2 = 1566083941; private static final int GENERATE_MASK1 = 0x9d2c5680; private static final int GENERATE_MASK2 = 0xefc60000; private final byte[] seed; // Lock to prevent concurrent modification of the RNG's internal state. private final ReentrantLock lock = new ReentrantLock(); private final int[] mt = new int[N]; // State vector. private int mtIndex = 0; // Index into state vector. private static final int BITWISE_BYTE_TO_INT = 0x000000FF; private static final long serialVersionUID = 217351968847857679L; /** * Creates a new RNG and seeds it using the default seeding strategy. */ public MersenneTwister() { this(new SecureRandom().generateSeed(SEED_SIZE_BYTES)); } /** * Creates an RNG and seeds it with the specified seed data. * * @param seed The seed data used to initialize the RNG. */ public MersenneTwister(byte[] seed) { if (seed == null || seed.length != SEED_SIZE_BYTES) { throw new IllegalArgumentException("Mersenne Twister RNG requires a 128-bit (16-byte) seed."); } this.seed = Arrays.copyOf(seed, seed.length); int[] seedInts = convertBytesToInts(this.seed); // This section is translated from the init_genrand code in the C version. mt[0] = BOOTSTRAP_SEED; for (mtIndex = 1; mtIndex < N; mtIndex++) { mt[mtIndex] = BOOTSTRAP_FACTOR * (mt[mtIndex - 1] ^ (mt[mtIndex - 1] >>> 30)) + mtIndex; } // This section is translated from the init_by_array code in the C version. int i = 1; int j = 0; for (int k = Math.max(N, seedInts.length); k > 0; k--) { mt[i] = (mt[i] ^ ((mt[i - 1] ^ (mt[i - 1] >>> 30)) * SEED_FACTOR1)) + seedInts[j] + j; i++; j++; if (i >= N) { mt[0] = mt[N - 1]; i = 1; } if (j >= seedInts.length) { j = 0; } } for (int k = N - 1; k > 0; k--) { mt[i] = (mt[i] ^ ((mt[i - 1] ^ (mt[i - 1] >>> 30)) * SEED_FACTOR2)) - i; i++; if (i >= N) { mt[0] = mt[N - 1]; i = 1; } } mt[0] = UPPER_MASK; // Most significant bit is 1 - guarantees non-zero initial array. } /** * Take four bytes from the specified position in the specified block and * convert them into a 32-bit int, using the big-endian convention. * * @param bytes The data to read from. * @param offset The position to start reading the 4-byte int from. * @return The 32-bit integer represented by the four bytes. */ public static int convertBytesToInt(byte[] bytes, int offset) { return (BITWISE_BYTE_TO_INT & bytes[offset + 3]) | ((BITWISE_BYTE_TO_INT & bytes[offset + 2]) << 8) | ((BITWISE_BYTE_TO_INT & bytes[offset + 1]) << 16) | ((BITWISE_BYTE_TO_INT & bytes[offset]) << 24); } /** * Convert an array of bytes into an array of ints. 4 bytes from the input * data map to a single int in the output data. * * @param bytes The data to read from. * @return An array of 32-bit integers constructed from the data. * @since 1.1 */ public static int[] convertBytesToInts(byte[] bytes) { if (bytes.length % 4 != 0) { throw new IllegalArgumentException("Number of input bytes must be a multiple of 4."); } int[] ints = new int[bytes.length / 4]; for (int i = 0; i < ints.length; i++) { ints[i] = convertBytesToInt(bytes, i * 4); } return ints; } public byte[] getSeed() { return Arrays.copyOf(seed, seed.length); } @Override public final int next(int bits) { int y; try { lock.lock(); if (mtIndex >= N) // Generate N ints at a time. { int kk; for (kk = 0; kk < N - M; kk++) { y = (mt[kk] & UPPER_MASK) | (mt[kk + 1] & LOWER_MASK); mt[kk] = mt[kk + M] ^ (y >>> 1) ^ MAG01[y & 0x1]; } for (; kk < N - 1; kk++) { y = (mt[kk] & UPPER_MASK) | (mt[kk + 1] & LOWER_MASK); mt[kk] = mt[kk + (M - N)] ^ (y >>> 1) ^ MAG01[y & 0x1]; } y = (mt[N - 1] & UPPER_MASK) | (mt[0] & LOWER_MASK); mt[N - 1] = mt[M - 1] ^ (y >>> 1) ^ MAG01[y & 0x1]; mtIndex = 0; } y = mt[mtIndex++]; } finally { lock.unlock(); } // Tempering y ^= y >>> 11; y ^= (y << 7) & GENERATE_MASK1; y ^= (y << 15) & GENERATE_MASK2; y ^= y >>> 18; return y >>> (32 - bits); } @Override public final long nextLong() { return ((next(32) & 0xffffffffL) << 32) | (next(32) & 0xffffffffL); } /** * Produces a copy of this RandomnessSource that, if next() and/or nextLong() are called on this object and the * copy, both will generate the same sequence of random numbers from the point copy() was called. This just needs to * copy the state so it isn't shared, usually, and produce a new value with the same exact state. * * @return a copy of this RandomnessSource */ @Override public RandomnessSource copy() { MersenneTwister next = new MersenneTwister(seed); System.arraycopy(mt, 0, next.mt, 0, mt.length); next.mtIndex = mtIndex; return next; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; MersenneTwister that = (MersenneTwister) o; if (mtIndex != that.mtIndex) return false; if (!Arrays.equals(seed, that.seed)) return false; return Arrays.equals(mt, that.mt); } @Override public int hashCode() { int result = CrossHash.Lightning.hash(seed); result = 31 * result + CrossHash.Lightning.hash(mt); result = 31 * result + mtIndex; return result; } @Override public String toString() { return "MersenneTwister with hidden state (id is " + System.identityHashCode(this) + ')'; } }