diff --git a/src/main/java/gregtech/api/GTValues.java b/src/main/java/gregtech/api/GTValues.java index 8448650b3ac..1d101966941 100644 --- a/src/main/java/gregtech/api/GTValues.java +++ b/src/main/java/gregtech/api/GTValues.java @@ -1,6 +1,6 @@ package gregtech.api; -import gregtech.api.util.XSTR; +import gregtech.api.util.random.XoShiRo256PlusPlusRandom; import gregtech.common.ConfigHolder; import net.minecraftforge.fml.common.FMLCommonHandler; @@ -45,7 +45,7 @@ public class GTValues { */ public static final short W = OreDictionary.WILDCARD_VALUE; - public static final Random RNG = new XSTR(); + public static final Random RNG = new XoShiRo256PlusPlusRandom(); /** Current time on the Client. Will always be zero on the server. */ public static long CLIENT_TIME = 0; diff --git a/src/main/java/gregtech/api/util/XSTR.java b/src/main/java/gregtech/api/util/XSTR.java deleted file mode 100644 index cea25a4ced5..00000000000 --- a/src/main/java/gregtech/api/util/XSTR.java +++ /dev/null @@ -1,241 +0,0 @@ -package gregtech.api.util; - -/** - * A subclass of java.util.random that implements the Xorshift random number - * generator - *

- * - it is 30% faster than the generator from Java's library - it produces - * random sequences of higher quality than java.util.Random - this class also - * provides a clone() function - *

- * Usage: XSRandom rand = new XSRandom(); //Instantiation x = rand.nextInt(); - * //pull a random number - *

- * To use the class in legacy code, you may also instantiate an XSRandom object - * and assign it to a java.util.Random object: java.util.Random rand = new - * XSRandom(); - *

- * for an explanation of the algorithm, see - * http://demesos.blogspot.com/2011/09/pseudo-random-number-generators.html - * - * @author Wilfried Elmenreich University of Klagenfurt/Lakeside Labs - * http://www.elmenreich.tk - *

- * This code is released under the GNU Lesser General Public License Version 3 - * http://www.gnu.org/licenses/lgpl-3.0.txt - */ - -import java.util.Random; -import java.util.concurrent.atomic.AtomicLong; - -/** - * XSTR - Xorshift ThermiteRandom - * 03.06.2016 - * version 0.0.4 - */ -public class XSTR extends Random { - - private static final long serialVersionUID = 6208727693524452904L; - private long seed; - private static final double DOUBLE_UNIT = 0x1.0p-53; // 1.0 / (1L << 53) - private static final float FLOAT_UNIT = 0x1.0p-24f; // 1.0f / (1 << 24) - - /* - * MODIFIED BY: Robotia - * Modification: Implemented Random class seed generator - */ - - /** - * Creates a new pseudo random number generator. The seed is initialized to - * the current time, as if by - * setSeed(System.currentTimeMillis());. - */ - public XSTR() { - this(seedUniquifier() ^ System.nanoTime()); - } - - private static final AtomicLong seedUniquifier = new AtomicLong(8682522807148012L); - - private static long seedUniquifier() { - // L'Ecuyer, "Tables of Linear Congruential Generators of - // Different Sizes and Good Lattice Structure", 1999 - for (;;) { - long current = seedUniquifier.get(); - long next = current * 181783497276652981L; - if (seedUniquifier.compareAndSet(current, next)) { - return next; - } - } - } - - /** - * Creates a new pseudo random number generator, starting with the specified - * seed, using setSeed(seed);. - * - * @param seed the initial seed - */ - public XSTR(long seed) { - this.seed = seed; - } - - public boolean nextBoolean() { - return next(1) != 0; - } - - public double nextDouble() { - return (((long) (next(26)) << 27) + next(27)) * DOUBLE_UNIT; - } - - /** - * Returns the current state of the seed, can be used to clone the object - * - * @return the current seed - */ - public synchronized long getSeed() { - return seed; - } - - /** - * Sets the seed for this pseudo random number generator. As described - * above, two instances of the same random class, starting with the same - * seed, produce the same results, if the same methods are called. - * - * @param seed the new seed - */ - public synchronized void setSeed(long seed) { - this.seed = seed; - } - - /** - * @return Returns an XSRandom object with the same state as the original - */ - @Override - public XSTR clone() { - return new XSTR(getSeed()); - } - - /** - * Implementation of George Marsaglia's elegant Xorshift random generator - * 30% faster and better quality than the built-in java.util.random see also - * see http://www.javamex.com/tutorials/random_numbers/xorshift.shtml - */ - public int next(int nbits) { - long x = seed; - x ^= (x << 21); - x ^= (x >>> 35); - x ^= (x << 4); - seed = x; - x &= ((1L << nbits) - 1); - return (int) x; - } - - boolean haveNextNextGaussian = false; - double nextNextGaussian = 0; - - synchronized public double nextGaussian() { - // See Knuth, ACP, Section 3.4.1 Algorithm C. - if (haveNextNextGaussian) { - haveNextNextGaussian = false; - return nextNextGaussian; - } else { - double v1, v2, s; - do { - v1 = 2 * nextDouble() - 1; // between -1 and 1 - v2 = 2 * nextDouble() - 1; // between -1 and 1 - s = v1 * v1 + v2 * v2; - } while (s >= 1 || s == 0); - double multiplier = StrictMath.sqrt(-2 * StrictMath.log(s) / s); - nextNextGaussian = v2 * multiplier; - haveNextNextGaussian = true; - return v1 * multiplier; - } - } - - /** - * Returns a pseudorandom, uniformly distributed {@code int} value between 0 - * (inclusive) and the specified value (exclusive), drawn from this random - * number generator's sequence. The general contract of {@code nextInt} is - * that one {@code int} value in the specified range is pseudorandomly - * generated and returned. All {@code bound} possible {@code int} values are - * produced with (approximately) equal probability. The method - * {@code nextInt(int bound)} is implemented by class {@code Random} as if - * by: - * - *

-     *  {@code
-     * public int nextInt(int bound) {
-     *   if (bound <= 0)
-     *     throw new IllegalArgumentException("bound must be positive");
-     *
-     *   if ((bound & -bound) == bound)  // i.e., bound is a power of 2
-     *     return (int)((bound * (long)next(31)) >> 31);
-     *
-     *   int bits, val;
-     *   do {
-     *       bits = next(31);
-     *       val = bits % bound;
-     *   } while (bits - val + (bound-1) < 0);
-     *   return val;
-     * }}
-     * 
- * - *

- * The hedge "approx - * imately" is used in the foregoing description only because the next - * method is only approximately an unbiased source of independently chosen - * bits. If it were a perfect source of randomly chosen bits, then the - * algorithm shown would choose {@code int} values from the stated range - * with perfect uniformity. - *

- * The algorithm is slightly tricky. It rejects values that would result in - * an uneven distribution (due to the fact that 2^31 is not divisible by n). - * The probability of a value being rejected depends on n. The worst case is - * n=2^30+1, for which the probability of a reject is 1/2, and the expected - * number of iterations before the loop terminates is 2. - *

- * The algorithm treats the case where n is a power of two specially: it - * returns the correct number of high-order bits from the underlying - * pseudo-random number generator. In the absence of special treatment, the - * correct number of low-order bits would be returned. Linear - * congruential pseudo-random number generators such as the one implemented - * by this class are known to have short periods in the sequence of values - * of their low-order bits. Thus, this special case greatly increases the - * length of the sequence of values returned by successive calls to this - * method if n is a small power of two. - * - * @param bound the upper bound (exclusive). Must be positive. - * @return the next pseudorandom, uniformly distributed {@code int} value - * between zero (inclusive) and {@code bound} (exclusive) from this random - * number generator's sequence - * @throws IllegalArgumentException if bound is not positive - * @since 1.2 - */ - public int nextInt(int bound) { - long last = seed ^ (seed << 21); - last ^= (last >>> 35); - last ^= (last << 4); - seed = last; - int out = (int) last % bound; - return (out < 0) ? -out : out; - } - - public int nextInt() { - return next(32); - } - - public float nextFloat() { - return next(24) * FLOAT_UNIT; - } - - public long nextLong() { - // it's okay that the bottom word remains signed. - return ((long) (next(32)) << 32) + next(32); - } - - public void nextBytes(byte[] bytes_arr) { - for (int iba = 0, lenba = bytes_arr.length; iba < lenba;) - for (int rndba = nextInt(), - nba = Math.min(lenba - iba, Integer.SIZE / Byte.SIZE); nba-- > 0; rndba >>= Byte.SIZE) - bytes_arr[iba++] = (byte) rndba; - } -} diff --git a/src/main/java/gregtech/api/util/random/SplitMix64Random.java b/src/main/java/gregtech/api/util/random/SplitMix64Random.java new file mode 100644 index 00000000000..7d089290b5f --- /dev/null +++ b/src/main/java/gregtech/api/util/random/SplitMix64Random.java @@ -0,0 +1,162 @@ +/* + * DSI utilities + * + * Copyright (C) 2015-2023 Sebastiano Vigna + * + * This program and the accompanying materials are made available under the + * terms of the GNU Lesser General Public License v2.1 or later, + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html, + * or the Apache Software License 2.0, which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. + * + * SPDX-License-Identifier: LGPL-2.1-or-later OR Apache-2.0 + */ +package gregtech.api.util.random; + +import it.unimi.dsi.fastutil.HashCommon; + +import java.util.Random; +import java.util.SplittableRandom; + +/** + * A fast, high-quality, non-splittable version of the SplitMix + * pseudorandom number generator used by {@link SplittableRandom}. Due to + * the fixed increment constant and to different strategies in generating finite ranges, the methods of this generator + * are usually faster than those of {@link SplittableRandom}. + * + *

+ * Note that this generator has a relatively short period (264) so it should + * not be used to generate very long sequences (the rule of thumb to have a period + * greater than the square of the length of the sequence you want to generate). + * + * @see Random + */ +public class SplitMix64Random extends Random { + + private static final long serialVersionUID = 1L; + /** 264 · φ, φ = (√5 − 1)/2. */ + private static final long PHI = 0x9E3779B97F4A7C15L; + + /** The internal state of the algorithm (a Weyl generator using the {@link #PHI} as increment). */ + private long x; + + /** Creates a new generator seeded using {@link Util#randomSeed()}. */ + public SplitMix64Random() { + this(Util.randomSeed()); + } + + /** + * Creates a new generator using a given seed. + * + * @param seed a seed for the generator. + */ + public SplitMix64Random(final long seed) { + setSeed(seed); + } + + /* + * David Stafford's (http://zimbry.blogspot.com/2011/09/better-bit-mixing-improving-on.html) + * "Mix13" variant of the 64-bit finalizer in Austin Appleby's MurmurHash3 algorithm. + */ + private static long staffordMix13(long z) { + z = (z ^ (z >>> 30)) * 0xBF58476D1CE4E5B9L; + z = (z ^ (z >>> 27)) * 0x94D049BB133111EBL; + return z ^ (z >>> 31); + } + + /* + * David Stafford's (http://zimbry.blogspot.com/2011/09/better-bit-mixing-improving-on.html) + * "Mix4" variant of the 64-bit finalizer in Austin Appleby's MurmurHash3 algorithm. + */ + private static int staffordMix4Upper32(long z) { + z = (z ^ (z >>> 33)) * 0x62A9D9ED799705F5L; + return (int) (((z ^ (z >>> 28)) * 0xCB24D0A5C88C35B3L) >>> 32); + } + + @Override + public long nextLong() { + return staffordMix13(x += PHI); + } + + @Override + public int nextInt() { + return staffordMix4Upper32(x += PHI); + } + + @Override + public int nextInt(final int n) { + return (int) nextLong(n); + } + + /** + * Returns a pseudorandom uniformly distributed {@code long} value + * between 0 (inclusive) and the specified value (exclusive), drawn from + * this random number generator's sequence. The algorithm used to generate + * the value guarantees that the result is uniform, provided that the + * sequence of 64-bit values produced by this generator is. + * + * @param n the positive bound on the random number to be returned. + * @return the next pseudorandom {@code long} value between {@code 0} (inclusive) and {@code n} (exclusive). + */ + public long nextLong(final long n) { + if (n <= 0) throw new IllegalArgumentException("illegal bound " + n + " (must be positive)"); + long t = staffordMix13(x += PHI); + final long nMinus1 = n - 1; + // Shortcut for powers of two + if ((n & nMinus1) == 0) return t & nMinus1; + // Rejection-based algorithm to get uniform integers in the general case + for (long u = t >>> 1; u + nMinus1 - (t = u % n) < 0; u = staffordMix13(x += PHI) >>> 1); + return t; + } + + @Override + public double nextDouble() { + return (staffordMix13(x += PHI) >>> 11) * 0x1.0p-53; + } + + @Override + public float nextFloat() { + return (staffordMix4Upper32(x += PHI) >>> 8) * 0x1.0p-24f; + } + + @Override + public boolean nextBoolean() { + return staffordMix4Upper32(x += PHI) < 0; + } + + @Override + public void nextBytes(final byte[] bytes) { + int i = bytes.length, n = 0; + while (i != 0) { + n = Math.min(i, 8); + for (long bits = staffordMix13(x += PHI); n-- != 0; bits >>= 8) bytes[--i] = (byte) bits; + } + } + + /** + * Sets the seed of this generator. + * + *

+ * The seed will be passed through {@link HashCommon#murmurHash3(long)}. + * + * @param seed a seed for this generator. + */ + @Override + public void setSeed(final long seed) { + x = HashCommon.murmurHash3(seed); + } + + /** + * Sets the state of this generator. + * + * @param state the new state for this generator (must be nonzero). + */ + public void setState(final long state) { + x = state; + } +} diff --git a/src/main/java/gregtech/api/util/random/Util.java b/src/main/java/gregtech/api/util/random/Util.java new file mode 100644 index 00000000000..9adccc6d4af --- /dev/null +++ b/src/main/java/gregtech/api/util/random/Util.java @@ -0,0 +1,47 @@ +/* + * DSI utilities + * + * Copyright (C) 2002-2023 Sebastiano Vigna + * + * This program and the accompanying materials are made available under the + * terms of the GNU Lesser General Public License v2.1 or later, + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html, + * or the Apache Software License 2.0, which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. + * + * SPDX-License-Identifier: LGPL-2.1-or-later OR Apache-2.0 + */ +package gregtech.api.util.random; + +/** + * All-purpose static-method container class. + * + * @author Sebastiano Vigna + * @since 0.1 + */ + +final class Util { + + private Util() {} + + private static final XoRoShiRo128PlusRandom seedUniquifier = new XoRoShiRo128PlusRandom(System.nanoTime()); + + /** + * Returns a random seed generated by taking the output of a {@link XoRoShiRo128PlusRandom} + * (seeded at startup with {@link System#nanoTime()}) and xoring it with {@link System#nanoTime()}. + * + * @return a reasonably good random seed. + */ + public static long randomSeed() { + final long x; + synchronized (seedUniquifier) { + x = seedUniquifier.nextLong(); + } + return x ^ System.nanoTime(); + } +} diff --git a/src/main/java/gregtech/api/util/random/XoRoShiRo128PlusRandom.java b/src/main/java/gregtech/api/util/random/XoRoShiRo128PlusRandom.java new file mode 100644 index 00000000000..44efb100d35 --- /dev/null +++ b/src/main/java/gregtech/api/util/random/XoRoShiRo128PlusRandom.java @@ -0,0 +1,356 @@ +/* + * DSI utilities + * + * Copyright (C) 2013-2023 Sebastiano Vigna + * + * This program and the accompanying materials are made available under the + * terms of the GNU Lesser General Public License v2.1 or later, + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html, + * or the Apache Software License 2.0, which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. + * + * SPDX-License-Identifier: LGPL-2.1-or-later OR Apache-2.0 + */ +package gregtech.api.util.random; + +import org.jetbrains.annotations.NotNull; + +import java.security.SecureRandom; +import java.util.Random; +import java.util.SplittableRandom; + +/** + * A fast, high-quality {@linkplain Random pseudorandom number generator} for floating-point generation. + * It has excellent speed, + * but its state space (128 bits) is large enough for + * mild parallelism only. It passes all tests we are aware of except for the four + * lower bits, which might fail linearity tests (and just those), so if + * low linear complexity is not considered an issue (as it is usually the + * case) it can be used to generate integer outputs, too; moreover, this + * generator has a very mild Hamming-weight dependency making our test + * fail after 8 TB of output; we believe + * this slight bias cannot affect any application. If you are concerned, + * use {@code XoRoShiRo128StarStarRandom} or {@code XoShiRo256PlusRandom}. + * More information can be found at our PRNG page. + * + *

+ * Warning: the constants used in this generator differ from the ones used in the 2016 version. + * + *

+ * If you need a general PRNG, use {@code XoRoShiRo128StarStarRandom}. If you can use more space, + * you might try {@code XoShiRo256PlusRandom}. + * + *

+ * By using the supplied {@link #jump()} method it is possible to generate non-overlapping long sequences + * for parallel computations; {@link #longJump()} makes it possible to create several + * starting points, each providing several non-overlapping sequences, for distributed computations. This class provides + * also a {@link #split()} method to support recursive parallel computations, in the spirit of + * {@link SplittableRandom}. + * + *

+ * Warning: before release 2.6.3, the {@link #split()} method + * would not alter the state of the caller, and it would return instances initialized in the same + * way if called multiple times. This was a major mistake in the implementation and it has been fixed, + * but as a consequence the output of the caller after a call to {@link #split()} is + * now different, and the result of {@link #split()} is initialized in a different way. + * + *

+ * Note that this is not a {@linkplain SecureRandom secure generator}. + * + * @version 1.0 + * @see Random + */ + +class XoRoShiRo128PlusRandom extends Random { + + private static final long serialVersionUID = 1L; + /** The internal state of the algorithm. */ + private long s0, s1; + + protected XoRoShiRo128PlusRandom(final long s0, final long s1) { + this.s0 = s0; + this.s1 = s1; + } + + /** Creates a new generator seeded using {@link Util#randomSeed()}. */ + public XoRoShiRo128PlusRandom() { + this(Util.randomSeed()); + } + + /** + * Creates a new generator using a given seed. + * + * @param seed a seed for the generator. + */ + public XoRoShiRo128PlusRandom(final long seed) { + setSeed(seed); + } + + /** + * Returns a copy of this generator. The sequences produced by this generator and by the returned generator will be + * identical. + * + *

+ * This method is particularly useful in conjunction with the {@link #jump()} (or {@link #longJump()}) method: by + * calling repeatedly + * {@link #jump() jump().copy()} over a generator it is possible to create several generators producing + * non-overlapping sequences. + * + * @return a copy of this generator. + */ + public XoRoShiRo128PlusRandom copy() { + return new XoRoShiRo128PlusRandom(s0, s1); + } + + @Override + public long nextLong() { + final long s0 = this.s0; + long s1 = this.s1; + final long result = s0 + s1; + s1 ^= s0; + this.s0 = Long.rotateLeft(s0, 24) ^ s1 ^ s1 << 16; + this.s1 = Long.rotateLeft(s1, 37); + return result; + } + + @Override + public int nextInt() { + return (int) (nextLong() >>> 32); + } + + @Override + public int nextInt(final int n) { + return (int) nextLong(n); + } + + /** + * Returns a pseudorandom uniformly distributed {@code long} value + * between 0 (inclusive) and the specified value (exclusive), drawn from + * this random number generator's sequence. The algorithm used to generate + * the value guarantees that the result is uniform, provided that the + * sequence of 64-bit values produced by this generator is. + * + * @param n the positive bound on the random number to be returned. + * @return the next pseudorandom {@code long} value between {@code 0} (inclusive) and {@code n} (exclusive). + */ + public long nextLong(final long n) { + if (n <= 0) throw new IllegalArgumentException("illegal bound " + n + " (must be positive)"); + long t = nextLong(); + final long nMinus1 = n - 1; + // For powers of 2 we return the high bits + // t >>> 1 >>> Long.numberOfLeadingZeros(n) might be faster but it would be backward incompatible + if ((n & nMinus1) == 0) return (t >>> Long.numberOfLeadingZeros(nMinus1)) & nMinus1; + // Rejection-based algorithm to get uniform integers in the general case + for (long u = t >>> 1; u + nMinus1 - (t = u % n) < 0; u = nextLong() >>> 1); + return t; + } + + @Override + public double nextDouble() { + return (nextLong() >>> 11) * 0x1.0p-53; + } + + /** + * Returns the next pseudorandom, uniformly distributed + * {@code double} value between {@code 0.0} and + * {@code 1.0} from this random number generator's sequence, + * using a fast multiplication-free method which, however, + * can provide only 52 significant bits. + * + *

+ * This method is faster than {@link #nextDouble()}, but it + * can return only dyadic rationals of the form k / 2−52, + * instead of the standard k / 2−53. Before + * version 2.4.1, this was actually the standard implementation of + * {@link #nextDouble()}, so you can use this method if you need to + * reproduce exactly results obtained using previous versions. + * + *

+ * The only difference between the output of this method and that of + * {@link #nextDouble()} is an additional least significant bit set in half of the + * returned values. For most applications, this difference is negligible. + * + * @return the next pseudorandom, uniformly distributed {@code double} + * value between {@code 0.0} and {@code 1.0} from this + * random number generator's sequence, using 52 significant bits only. + * + * @since 2.4.1 + */ + public double nextDoubleFast() { + return Double.longBitsToDouble(0x3FFL << 52 | nextLong() >>> 12) - 1.0; + } + + @Override + public float nextFloat() { + return (nextLong() >>> 40) * 0x1.0p-24f; + } + + @Override + public boolean nextBoolean() { + return nextLong() < 0; + } + + @Override + public void nextBytes(final byte @NotNull [] 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; + } + } + + protected @NotNull XoRoShiRo128PlusRandom jump(final long @NotNull [] jump) { + long s0 = 0; + long s1 = 0; + for (final long element : jump) + for (int b = 0; b < 64; b++) { + if ((element & 1L << b) != 0) { + s0 ^= this.s0; + s1 ^= this.s1; + } + nextLong(); + } + + this.s0 = s0; + this.s1 = s1; + return this; + } + + private static final long[] JUMP = { 0xdf900294d8f554a5L, 0x170865df4b3201fcL }; + + /** + * The jump function for this generator. It is equivalent to 264 + * calls to {@link #nextLong()}; it can be used to generate 264 + * non-overlapping subsequences for parallel computations. + * + * @return this generator. + * @see #copy() + */ + + public @NotNull XoRoShiRo128PlusRandom jump() { + return jump(JUMP); + } + + private static final long[] LONG_JUMP = { 0xd2a98b26625eee7bL, 0xdddf9b1090aa7ac1L }; + + /** + * The long-jump function for this generator. It is equivalent to 296 + * calls to {@link #nextLong()}; it can be used to generate 232 starting points, + * from each of which {@link #jump()} will generate 232 non-overlapping + * subsequences for parallel distributed computations. + * + * @return this generator. + * @see #copy() + */ + + public @NotNull XoRoShiRo128PlusRandom longJump() { + return jump(LONG_JUMP); + } + + /** + * Returns a new instance that shares no mutable state + * with this instance. The sequence generated by the new instance + * depends deterministically from the state of this instance, + * but the probability that the sequence generated by this + * instance and by the new instance overlap is negligible. + * + *

+ * Warning: before release 2.6.3, this method + * would not alter the state of the caller, and it would return instances initialized in the same + * way if called multiple times. This was a major mistake in the implementation and it has been fixed, + * but as a consequence the output of this instance after a call to this method is + * now different, and the returned instance is initialized in a different way. + * + * @return the new instance. + */ + public @NotNull XoRoShiRo128PlusRandom split() { + nextLong(); + final XoRoShiRo128PlusRandom split = copy(); + + long h0 = s0; + long h1 = s1; + long h2 = s0 + 0x55a650a4c1dac3e9L; // Random constants + long h3 = s1 + 0xb39ae98dfa439b73L; + + // A round of SpookyHash ShortMix + h2 = Long.rotateLeft(h2, 50); + h2 += h3; + h0 ^= h2; + h3 = Long.rotateLeft(h3, 52); + h3 += h0; + h1 ^= h3; + h0 = Long.rotateLeft(h0, 30); + h0 += h1; + h2 ^= h0; + h1 = Long.rotateLeft(h1, 41); + h1 += h2; + h3 ^= h1; + h2 = Long.rotateLeft(h2, 54); + h2 += h3; + h0 ^= h2; + h3 = Long.rotateLeft(h3, 48); + h3 += h0; + h1 ^= h3; + h0 = Long.rotateLeft(h0, 38); + h0 += h1; + h2 ^= h0; + h1 = Long.rotateLeft(h1, 37); + h1 += h2; + h3 ^= h1; + h2 = Long.rotateLeft(h2, 62); + h2 += h3; + h0 ^= h2; + h3 = Long.rotateLeft(h3, 34); + h3 += h0; + h1 ^= h3; + h0 = Long.rotateLeft(h0, 5); + h0 += h1; + h2 ^= h0; + h1 = Long.rotateLeft(h1, 36); + h1 += h2; + // h3 ^= h1; + + split.s0 = h0; + split.s1 = h1; + + return split; + } + + /** + * Sets the seed of this generator. + * + *

+ * The argument will be used to seed a {@link SplitMix64Random}, whose output + * will in turn be used to seed this generator. This approach makes “warmup” unnecessary, + * and makes the probability of starting from a state + * with a large fraction of bits set to zero astronomically small. + * + * @param seed a seed for this generator. + */ + @Override + public void setSeed(final long seed) { + final SplitMix64Random r = new SplitMix64Random(seed); + s0 = r.nextLong(); + s1 = r.nextLong(); + } + + /** + * Sets the state of this generator. + * + *

+ * The internal state of the generator will be reset, and the state array filled with the provided array. + * + * @param state an array of 2 longs; at least one must be nonzero. + */ + public void setState(final long @NotNull [] state) { + if (state.length != 2) throw new IllegalArgumentException( + "The argument array contains " + state.length + " longs instead of " + 2); + s0 = state[0]; + s1 = state[1]; + } +} diff --git a/src/main/java/gregtech/api/util/random/XoShiRo256PlusPlusRandom.java b/src/main/java/gregtech/api/util/random/XoShiRo256PlusPlusRandom.java new file mode 100644 index 00000000000..8eb31c94fdd --- /dev/null +++ b/src/main/java/gregtech/api/util/random/XoShiRo256PlusPlusRandom.java @@ -0,0 +1,386 @@ +/* + * DSI utilities + * + * Copyright (C) 2013-2023 Sebastiano Vigna + * + * This program and the accompanying materials are made available under the + * terms of the GNU Lesser General Public License v2.1 or later, + * which is available at + * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html, + * or the Apache Software License 2.0, which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. + * + * SPDX-License-Identifier: LGPL-2.1-or-later OR Apache-2.0 + */ +package gregtech.api.util.random; + +import org.jetbrains.annotations.NotNull; + +import java.security.SecureRandom; +import java.util.Random; +import java.util.SplittableRandom; + +/** + * A fast, all-purpose, rock-solid {@linkplain Random pseudorandom number generator}. It has + * excellent speed, a state space (256 bits) that is large enough for any parallel application, and + * it passes all tests we are aware of. In Java, it is slightly faster than a + * {@code XoShiRo256StarStarRandom}. More information can be found at our + * PRNG page. + * + *

+ * Note that starting with Java 17 you can find this generator in {@code java.util.random}. + * + *

+ * If you need to generate just floating-point numbers, {@code XoShiRo256PlusRandom} is slightly + * faster. If you are tight on space, you might try {@code XoRoShiRo128PlusPlusRandom}. + * + *

+ * By using the supplied {@link #jump()} method it is possible to generate non-overlapping long + * sequences for parallel computations; {@link #longJump()} makes it possible to create several + * starting points, each providing several non-overlapping sequences, for distributed computations. + * This class provides also a {@link #split()} method to support recursive parallel computations, in + * the spirit of {@link SplittableRandom}. + * + *

+ * Warning: before release 2.6.3, the {@link #split()} method would not alter the + * state of the caller, and it would return instances initialized in the same way if called multiple + * times. This was a major mistake in the implementation and it has been fixed, but as a consequence + * the output of the caller after a call to {@link #split()} is now different, and the result of + * {@link #split()} is initialized in a different way. + * + *

+ * Note that this is not a {@linkplain SecureRandom secure generator}. + * + * @version 1.0 + * @see Random + */ + +public class XoShiRo256PlusPlusRandom extends Random { + + private static final long serialVersionUID = 1L; + /** The internal state of the algorithm. */ + private long s0, s1, s2, s3; + + protected XoShiRo256PlusPlusRandom(final long s0, final long s1, final long s2, final long s3) { + this.s0 = s0; + this.s1 = s1; + this.s2 = s2; + this.s3 = s3; + } + + /** Creates a new generator seeded using {@link Util#randomSeed()}. */ + public XoShiRo256PlusPlusRandom() { + this(Util.randomSeed()); + } + + /** + * Creates a new generator using a given seed. + * + * @param seed a seed for the generator. + */ + public XoShiRo256PlusPlusRandom(final long seed) { + setSeed(seed); + } + + /** + * Returns a copy of this generator. The sequences produced by this generator and by the returned generator will be + * identical. + * + *

+ * This method is particularly useful in conjunction with the {@link #jump()} (or {@link #longJump()}) method: by + * calling repeatedly + * {@link #jump() jump().copy()} over a generator it is possible to create several generators producing + * non-overlapping sequences. + * + * @return a copy of this generator. + */ + public @NotNull XoShiRo256PlusPlusRandom copy() { + return new XoShiRo256PlusPlusRandom(s0, s1, s2, s3); + } + + @Override + public long nextLong() { + final long t0 = s0; + final long result = Long.rotateLeft(t0 + s3, 23) + t0; + + final long t = s1 << 17; + + s2 ^= t0; + s3 ^= s1; + s1 ^= s2; + s0 ^= s3; + + s2 ^= t; + + s3 = Long.rotateLeft(s3, 45); + + return result; + } + + @Override + public int nextInt() { + return (int) nextLong(); + } + + @Override + public int nextInt(final int n) { + return (int) nextLong(n); + } + + /** + * Returns a pseudorandom uniformly distributed {@code long} value + * between 0 (inclusive) and the specified value (exclusive), drawn from + * this random number generator's sequence. The algorithm used to generate + * the value guarantees that the result is uniform, provided that the + * sequence of 64-bit values produced by this generator is. + * + * @param n the positive bound on the random number to be returned. + * @return the next pseudorandom {@code long} value between {@code 0} (inclusive) and {@code n} (exclusive). + */ + public long nextLong(final long n) { + if (n <= 0) throw new IllegalArgumentException("illegal bound " + n + " (must be positive)"); + long t = nextLong(); + final long nMinus1 = n - 1; + // Rejection-based algorithm to get uniform integers in the general case + for (long u = t >>> 1; u + nMinus1 - (t = u % n) < 0; u = nextLong() >>> 1); + return t; + } + + @Override + public double nextDouble() { + return (nextLong() >>> 11) * 0x1.0p-53; + } + + /** + * Returns the next pseudorandom, uniformly distributed + * {@code double} value between {@code 0.0} and + * {@code 1.0} from this random number generator's sequence, + * using a fast multiplication-free method which, however, + * can provide only 52 significant bits. + * + *

+ * This method is faster than {@link #nextDouble()}, but it + * can return only dyadic rationals of the form k / 2−52, + * instead of the standard k / 2−53. Before + * version 2.4.1, this was actually the standard implementation of + * {@link #nextDouble()}, so you can use this method if you need to + * reproduce exactly results obtained using previous versions. + * + *

+ * The only difference between the output of this method and that of + * {@link #nextDouble()} is an additional least significant bit set in half of the + * returned values. For most applications, this difference is negligible. + * + * @return the next pseudorandom, uniformly distributed {@code double} + * value between {@code 0.0} and {@code 1.0} from this + * random number generator's sequence, using 52 significant bits only. + */ + public double nextDoubleFast() { + return Double.longBitsToDouble(0x3FFL << 52 | nextLong() >>> 12) - 1.0; + } + + @Override + public float nextFloat() { + return (nextLong() >>> 40) * 0x1.0p-24f; + } + + @Override + public boolean nextBoolean() { + return nextLong() < 0; + } + + @Override + public void nextBytes(final byte @NotNull [] 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; + } + } + + private static final long[] JUMP = { 0x180ec6d33cfd0abaL, 0xd5a61266f0c9392cL, 0xa9582618e03fc9aaL, + 0x39abdc4529b1661cL }; + + protected XoShiRo256PlusPlusRandom jump(final long[] jump) { + long s0 = 0; + long s1 = 0; + long s2 = 0; + long s3 = 0; + for (final long element : jump) + for (int b = 0; b < 64; b++) { + if ((element & 1L << b) != 0) { + s0 ^= this.s0; + s1 ^= this.s1; + s2 ^= this.s2; + s3 ^= this.s3; + } + nextLong(); + } + + this.s0 = s0; + this.s1 = s1; + this.s2 = s2; + this.s3 = s3; + return this; + } + + /** + * The jump function for this generator. It is equivalent to 2128 + * calls to {@link #nextLong()}; it can be used to generate 2128 + * non-overlapping subsequences for parallel computations. + * + * @return this generator. + * @see #copy() + */ + + public @NotNull XoShiRo256PlusPlusRandom jump() { + return jump(JUMP); + } + + private static final long[] LONG_JUMP = { 0x76e15d3efefdcbbfL, 0xc5004e441c522fb3L, 0x77710069854ee241L, + 0x39109bb02acbe635L }; + + /** + * The long-jump function for this generator. It is equivalent to 2192 + * calls to {@link #nextLong()}; it can be used to generate 264 starting points, + * from each of which {@link #jump()} will generate 264 non-overlapping + * subsequences for parallel distributed computations. + * + * @return this generator. + * @see #copy() + */ + + public @NotNull XoShiRo256PlusPlusRandom longJump() { + return jump(LONG_JUMP); + } + + /** + * Returns a new instance that shares no mutable state + * with this instance. The sequence generated by the new instance + * depends deterministically from the state of this instance, + * but the probability that the sequence generated by this + * instance and by the new instance overlap is negligible. + * + *

+ * Warning: before release 2.6.3, this method + * would not alter the state of the caller, and it would return instances initialized in the same + * way if called multiple times. This was a major mistake in the implementation and it has been fixed, + * but as a consequence the output of this instance after a call to this method is + * now different, and the returned instance is initialized in a different way. + * + * @return the new instance. + */ + public @NotNull XoShiRo256PlusPlusRandom split() { + nextLong(); + final XoShiRo256PlusPlusRandom split = copy(); + + long h0 = split.s0; + long h1 = split.s1; + long h2 = split.s2; + long h3 = split.s3; + + // A round of SpookyHash ShortMix + h2 = Long.rotateLeft(h2, 50); + h2 += h3; + h0 ^= h2; + h3 = Long.rotateLeft(h3, 52); + h3 += h0; + h1 ^= h3; + h0 = Long.rotateLeft(h0, 30); + h0 += h1; + h2 ^= h0; + h1 = Long.rotateLeft(h1, 41); + h1 += h2; + h3 ^= h1; + h2 = Long.rotateLeft(h2, 54); + h2 += h3; + h0 ^= h2; + h3 = Long.rotateLeft(h3, 48); + h3 += h0; + h1 ^= h3; + h0 = Long.rotateLeft(h0, 38); + h0 += h1; + h2 ^= h0; + h1 = Long.rotateLeft(h1, 37); + h1 += h2; + h3 ^= h1; + h2 = Long.rotateLeft(h2, 62); + h2 += h3; + h0 ^= h2; + h3 = Long.rotateLeft(h3, 34); + h3 += h0; + h1 ^= h3; + h0 = Long.rotateLeft(h0, 5); + h0 += h1; + h2 ^= h0; + h1 = Long.rotateLeft(h1, 36); + h1 += h2; + h3 ^= h1; + + split.s0 = h0; + split.s1 = h1; + split.s2 = h2; + split.s3 = h3; + return split; + } + + /** + * Sets the seed of this generator. + * + *

+ * The argument will be used to seed a {@link SplitMix64Random}, whose output + * will in turn be used to seed this generator. This approach makes “warmup” unnecessary, + * and makes the probability of starting from a state + * with a large fraction of bits set to zero astronomically small. + * + * @param seed a seed for this generator. + */ + @Override + public void setSeed(final long seed) { + final SplitMix64Random r = new SplitMix64Random(seed); + s0 = r.nextLong(); + s1 = r.nextLong(); + s2 = r.nextLong(); + s3 = r.nextLong(); + } + + /** + * Sets the state of this generator. + * + *

+ * The internal state of the generator will be reset, and the state array filled with the provided array. + * + * @param state an array of 4 longs; at least one must be nonzero. + */ + public void setState(final long @NotNull [] state) { + if (state.length != 4) throw new IllegalArgumentException( + "The argument array contains " + state.length + " longs instead of " + 4); + s0 = state[0]; + s1 = state[1]; + s2 = state[2]; + s3 = state[3]; + } + + /** + * Sets the state of this generator. + * + *

+ * The internal state of the generator will be reset, and the state array filled with the provided array. + * + * @param state0 one of of 4 longs; at least one must be nonzero. + * @param state1 one of of 4 longs; at least one must be nonzero. + * @param state2 one of of 4 longs; at least one must be nonzero. + * @param state3 one of of 4 longs; at least one must be nonzero. + */ + public void setState(final long state0, final long state1, final long state2, final long state3) { + s0 = state0; + s1 = state1; + s2 = state2; + s3 = state3; + } +} diff --git a/src/main/java/gregtech/api/util/random/package-info.java b/src/main/java/gregtech/api/util/random/package-info.java new file mode 100644 index 00000000000..8be33096fc3 --- /dev/null +++ b/src/main/java/gregtech/api/util/random/package-info.java @@ -0,0 +1,12 @@ +/** + * This package contains {@link java.util.Random} implementations from + * DSI Utilities. + *

+ * Currently, {@link gregtech.api.util.random.XoShiRo256PlusPlusRandom} is exposed as {@code public} and should be + * superior to {@link java.util.Random} in effectively all scenarios. + *

+ * Additionally, {@link gregtech.api.util.random.SplitMix64Random} is exposed for seeding + * {@link gregtech.api.util.random.XoShiRo256PlusPlusRandom} externally. It should not be used as a general-purpose + * PRNG. + */ +package gregtech.api.util.random; diff --git a/src/main/java/gregtech/api/worldgen/bedrockFluids/BedrockFluidVeinHandler.java b/src/main/java/gregtech/api/worldgen/bedrockFluids/BedrockFluidVeinHandler.java index b858b02c94b..2f2f65cc5d6 100644 --- a/src/main/java/gregtech/api/worldgen/bedrockFluids/BedrockFluidVeinHandler.java +++ b/src/main/java/gregtech/api/worldgen/bedrockFluids/BedrockFluidVeinHandler.java @@ -4,7 +4,7 @@ import gregtech.api.GregTechAPI; import gregtech.api.util.FileUtility; import gregtech.api.util.GTLog; -import gregtech.api.util.XSTR; +import gregtech.api.util.random.XoShiRo256PlusPlusRandom; import gregtech.api.worldgen.config.BedrockFluidDepositDefinition; import gregtech.core.network.packets.PacketFluidVeinList; @@ -82,7 +82,8 @@ public static FluidVeinWorldEntry getFluidVeinWorldEntry(@NotNull World world, i } } - Random random = new XSTR(31L * 31 * chunkX + chunkZ * 31L + Long.hashCode(world.getSeed())); + Random random = new XoShiRo256PlusPlusRandom( + 31L * 31 * chunkX + chunkZ * 31L + Long.hashCode(world.getSeed())); int maximumYield = 0; if (definition != null) { diff --git a/src/main/java/gregtech/api/worldgen/generator/CachedGridEntry.java b/src/main/java/gregtech/api/worldgen/generator/CachedGridEntry.java index 8fe39b4ceaf..78456a8257f 100644 --- a/src/main/java/gregtech/api/worldgen/generator/CachedGridEntry.java +++ b/src/main/java/gregtech/api/worldgen/generator/CachedGridEntry.java @@ -1,7 +1,7 @@ package gregtech.api.worldgen.generator; import gregtech.api.util.GTUtility; -import gregtech.api.util.XSTR; +import gregtech.api.util.random.XoShiRo256PlusPlusRandom; import gregtech.api.worldgen.config.OreDepositDefinition; import gregtech.api.worldgen.config.WorldGenRegistry; import gregtech.api.worldgen.populator.IBlockModifierAccess; @@ -81,7 +81,7 @@ public CachedGridEntry(World world, int gridX, int gridZ, int primerChunkX, int this.gridX = gridX; this.gridZ = gridZ; long worldSeed = world.getSeed(); - this.gridRandom = new XSTR(31L * 31 * gridX + gridZ * 31L + Long.hashCode(worldSeed)); + this.gridRandom = new XoShiRo256PlusPlusRandom(31L * 31 * gridX + gridZ * 31L + Long.hashCode(worldSeed)); int gridSizeX = WorldGeneratorImpl.GRID_SIZE_X * 16; int gridSizeZ = WorldGeneratorImpl.GRID_SIZE_Z * 16;