Move StdLCG to GTNHLib (#304)

This commit is contained in:
ah-OOG-ah authored and GitHub committed 2026-03-13 17:39:36 +01:00
1 parent 2751220f03
commit bc518d841d
2 files changed
+112

No files matched your search

@@ -0,0 +1,65 @@
package com.gtnewhorizon.gtnhlib.util;
import java.util.Random;
/// A basic LCG, based on Knuth's work and using the same constants as {@link Random}. As such, it produces the same
/// output, just MUCH faster since it's not reliant on atomic ops. However, each instance can only be used by one thread
/// at a time.
public class StdLCG extends Random {
protected static final long mask = -1L >>> 16;
protected static final long multiplier = 0x5DEECE66DL;
protected static final long increment = 11;
protected long seed;
protected boolean haveNextNextGaussian = false;
protected double nextNextGaussian = 0.0;
/// Don't ask me why stdlib scrambles the seed, because I don't know.
public StdLCG(long seed) {
// this default constructor performs an atomic op on a global variable
super(seed);
}
public StdLCG() {
this(System.nanoTime());
}
public static long scramble(long seed) {
return (seed ^ multiplier) & mask;
}
@Override
public void setSeed(long seed) {
this.seed = scramble(seed);
haveNextNextGaussian = false;
}
@Override
protected int next(int bits) {
seed = (seed * multiplier + increment) & mask;
return (int) (seed >>> 48 - bits);
}
/// A reimplementation of Knuth's algorithm, except I read the 2nd edition instead of the 3rd. It's probably close
/// enough.
@Override
public double nextGaussian() {
if (haveNextNextGaussian) {
haveNextNextGaussian = false;
return nextNextGaussian;
}
double s, v1, v2;
do {
v1 = 2 * nextDouble() - 1;
v2 = 2 * nextDouble() - 1;
s = v1 * v1 + v2 * v2;
// Knuth doesn't say to return if s==0, but stdlib does. We need to match stdlib, so another clause it is.
} while (s >= 1 || s == 0);
final var tmpRoot = StrictMath.sqrt(-2 * StrictMath.log(s) / s);
nextNextGaussian = v2 * tmpRoot;
haveNextNextGaussian = true;
return v1 * tmpRoot;
}
}
@@ -0,0 +1,47 @@
package com.gtnewhorizon.gtnhlib.test.util;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.Random;
import org.junit.jupiter.api.Test;
import com.gtnewhorizon.gtnhlib.util.StdLCG;
/// Ensures {@link StdLCG} matches {@link Random}'s output in a variety of scenarios.
public class StdLCGTest {
@Test
void testNextLongConformance() {
// ...after construction,
final var stdlib = new Random(900);
final var lcg = new StdLCG(900);
assertEquals(stdlib.nextLong(), lcg.nextLong());
assertEquals(stdlib.nextLong(), lcg.nextLong());
assertEquals(stdlib.nextLong(), lcg.nextLong());
// ...and after post-construction seeding
stdlib.setSeed(9425125);
lcg.setSeed(9425125);
assertEquals(stdlib.nextLong(), lcg.nextLong());
assertEquals(stdlib.nextLong(), lcg.nextLong());
assertEquals(stdlib.nextLong(), lcg.nextLong());
}
@Test
void testNextGaussianConformance() {
// ...after construction,
final var stdlib = new Random(900);
final var lcg = new StdLCG(900);
assertEquals(stdlib.nextGaussian(), lcg.nextGaussian());
assertEquals(stdlib.nextGaussian(), lcg.nextGaussian());
assertEquals(stdlib.nextGaussian(), lcg.nextGaussian());
// ...and after post-construction seeding
stdlib.setSeed(9425125);
lcg.setSeed(9425125);
assertEquals(stdlib.nextGaussian(), lcg.nextGaussian());
assertEquals(stdlib.nextGaussian(), lcg.nextGaussian());
assertEquals(stdlib.nextGaussian(), lcg.nextGaussian());
}
}