Skip to content

Commit 3242d5b

Browse files
committed
improve new random generators and add tests for them
1 parent 6922814 commit 3242d5b

File tree

14 files changed

+460
-93
lines changed

14 files changed

+460
-93
lines changed

src/main/java/de/tilman_neumann/jml/random/LehmerRng64.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ public class LehmerRng64 {
2727

2828
private Uint128 state = mult;
2929

30+
/**
31+
* @return a random long number N with Long.MIN_VALUE <= N <= Long.MAX_VALUE.
32+
*/
3033
public long nextLong() {
3134
long result = state.getHigh();
3235
state = Uint128.mul128_getLow(state, mult);

src/main/java/de/tilman_neumann/jml/random/LehmerRng64MH.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ public class LehmerRng64MH {
2727

2828
private Uint128 state = mult;
2929

30+
/**
31+
* @return a random long number N with Long.MIN_VALUE <= N <= Long.MAX_VALUE.
32+
*/
3033
public long nextLong() {
3134
long result = state.getHigh();
3235
state = Uint128.mul128MH_getLow(state, mult);

src/main/java/de/tilman_neumann/jml/random/SpRand32.java

Lines changed: 26 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -19,73 +19,70 @@
1919
/**
2020
* My Java port of the pseudo-random number generator from tinyEcm.c by Ben Buhrow.
2121
*
22+
* This generator is special in that nextInt() creates strictly non-negative random numbers.
23+
*
2224
* @author Tilman Neumann
2325
*/
2426
public class SpRand32 {
2527
private static final Logger LOG = LogManager.getLogger(SpRand32.class);
2628

2729
private static final boolean DEBUG = false;
2830

31+
private static final long MAX_INT_AS_LONG = Integer.MAX_VALUE;
32+
2933
// seed
30-
//private long LCGSTATE = 65537 * new Random().nextInt();
3134
private long LCGSTATE = 4295098403L; // rng comparable with C version of TinyEcm
3235

33-
public int nextInt(int lower, int upper) {
36+
/**
37+
* @return a random int number N with 0 <= N <= Integer.MAX_VALUE.
38+
*/
39+
public int nextInt() {
3440
if (DEBUG) LOG.debug("LCGSTATE=" + LCGSTATE);
3541

36-
// fix rng for negative upper values
37-
long upperl = (long) upper;
38-
if (upperl<0) upperl += (1L<<32);
39-
long diff = upperl - lower;
40-
if (DEBUG) LOG.debug("lower=" + lower + ", upper=" + upperl + ", diff=" + diff);
41-
4242
// advance the state of the LCG and return the appropriate result
4343
LCGSTATE = 6364136223846793005L * LCGSTATE + 1442695040888963407L;
4444
long LCGSTATE_shifted = LCGSTATE >>> 32;
4545
if (DEBUG) LOG.debug("LCGSTATE=" + LCGSTATE + ", LCGSTATE_shifted=" + LCGSTATE_shifted);
4646

4747
double quot = (double)LCGSTATE_shifted / 4294967296.0; // dividend is 2^32
48-
double prod = diff * quot;
48+
double prod = MAX_INT_AS_LONG * quot;
4949
int rand = (int)(0xFFFFFFFF & (long)prod); // (int)prod does not work for prod >= 2^31
50-
int result = lower + rand;
50+
int result = rand;
5151
if (DEBUG) LOG.debug("quot=" + quot + ", prod=" + prod + ", rand=" + rand + ", result=" + result);
5252
return result;
5353
}
5454

55-
public int nextInt(int upper) {
55+
/**
56+
* @param max
57+
* @return a random int number N with 0 <= N <= max.
58+
*/
59+
public int nextInt(int max) {
5660
if (DEBUG) LOG.debug("LCGSTATE=" + LCGSTATE);
5761

5862
// fix rng for negative upper values
59-
long upperl = (long) upper;
60-
if (upperl<0) upperl += (1L<<32);
61-
if (DEBUG) LOG.debug("upper=" + upperl);
63+
long maxLong = (long) max;
64+
if (maxLong<0) maxLong += (1L<<32);
65+
if (DEBUG) LOG.debug("max=" + maxLong);
6266

6367
// advance the state of the LCG and return the appropriate result
6468
LCGSTATE = 6364136223846793005L * LCGSTATE + 1442695040888963407L;
6569
long LCGSTATE_shifted = LCGSTATE >>> 32;
6670
if (DEBUG) LOG.debug("LCGSTATE=" + LCGSTATE + ", LCGSTATE_shifted=" + LCGSTATE_shifted);
6771

6872
double quot = (double)LCGSTATE_shifted / 4294967296.0; // dividend is 2^32
69-
double prod = upperl * quot;
73+
double prod = maxLong * quot;
7074
int rand = (int)(0xFFFFFFFF & (long)prod); // (int)prod does not work for prod >= 2^31
7175
int result = rand;
7276
if (DEBUG) LOG.debug("quot=" + quot + ", prod=" + prod + ", rand=" + rand + ", result=" + result);
7377
return result;
7478
}
7579

76-
public int nextInt() {
77-
if (DEBUG) LOG.debug("LCGSTATE=" + LCGSTATE);
78-
79-
// advance the state of the LCG and return the appropriate result
80-
LCGSTATE = 6364136223846793005L * LCGSTATE + 1442695040888963407L;
81-
long LCGSTATE_shifted = LCGSTATE >>> 32;
82-
if (DEBUG) LOG.debug("LCGSTATE=" + LCGSTATE + ", LCGSTATE_shifted=" + LCGSTATE_shifted);
83-
84-
double quot = (double)LCGSTATE_shifted / 4294967296.0; // dividend is 2^32
85-
double prod = Integer.MAX_VALUE * quot;
86-
int rand = (int)(0xFFFFFFFF & (long)prod); // (int)prod does not work for prod >= 2^31
87-
int result = rand;
88-
if (DEBUG) LOG.debug("quot=" + quot + ", prod=" + prod + ", rand=" + rand + ", result=" + result);
89-
return result;
80+
/**
81+
* @param min
82+
* @param max
83+
* @return a random int number N with lower <= N <= max.
84+
*/
85+
public int nextInt(int min, int max) {
86+
return min + nextInt(max - min);
9087
}
9188
}

src/main/java/de/tilman_neumann/jml/random/Xorshf32.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,16 @@
1717
* Java port of Marsaglia's xorshf generator.
1818
* @see https://stackoverflow.com/questions/1640258/need-a-fast-random-generator-for-c<br/><br/>
1919
*
20-
* <strong>WARNING: This class is experimental!</strong> SpRand32 looks faster for numbers close to 31 bit.
21-
* Maybe I need to improve the conversion from C "unsigned long" to Java int (shifts, masks etc.).
20+
* This generator is special in that nextInt() creates strictly non-negative random numbers.
2221
*
2322
* @author Tilman Neumann
2423
*/
2524
public class Xorshf32 {
2625

2726
private int x=123456789, y=362436069, z=521288629;
2827

29-
public int nextInt(int min, int max) {
30-
return min + nextInt(max - min);
28+
public int nextInt() {
29+
return nextInt(Integer.MAX_VALUE);
3130
}
3231

3332
public int nextInt(int max) {
@@ -43,8 +42,8 @@ public int nextInt(int max) {
4342

4443
return Math.abs(z % max);
4544
}
46-
47-
public int nextInt() {
48-
return nextInt(Integer.MAX_VALUE);
45+
46+
public int nextInt(int min, int max) {
47+
return min + nextInt(max - min);
4948
}
5049
}

src/main/java/de/tilman_neumann/jml/random/Xorshf32b.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,17 @@
1414
package de.tilman_neumann.jml.random;
1515

1616
/**
17-
* An <strong>experimental</strong> variant of Marsaglia's xorshf generator.
17+
* A variant of Marsaglia's xorshf generator.
1818
*
1919
* @author Tilman Neumann
2020
*/
2121
public class Xorshf32b {
2222

2323
private int x=123456789, y=362436069, z=521288629;
2424

25+
/**
26+
* @return a random long number N with Integer.MIN_VALUE <= N <= Integer.MAX_VALUE.
27+
*/
2528
public int nextInt() {
2629
int t;
2730
x ^= x << 16;
@@ -36,8 +39,13 @@ public int nextInt() {
3639
return z;
3740
}
3841

42+
/**
43+
* @param max
44+
* @return a random int number N with 0 <= N <= max.
45+
*/
3946
public int nextInt(int max) {
40-
final long l = nextInt(); // up to unsigned 2^32 - 1
47+
final long i = nextInt();
48+
final long l = i<0 ? -i : i; // up to unsigned 2^32 - 1
4149
final long prod = l * max; // up to max * (2^32 - 1)
4250
return (int) (prod >>> 32);
4351
}

src/main/java/de/tilman_neumann/jml/random/Xorshf64.java

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,26 @@
1414
package de.tilman_neumann.jml.random;
1515

1616
/**
17-
* <strong>Experimental</strong> 64 bit random number generator computing longs from two consecutive 32 bit random numbers.
17+
* <strong>Experimental</strong> 64 bit random number generator computing longs from three consecutive 32 bit random numbers.
1818
*
1919
* @author Tilman Neumann
2020
*/
2121
public class Xorshf64 {
2222

23-
private Xorshf32 intGenerator = new Xorshf32();
23+
private Xorshf32b intGenerator = new Xorshf32b();
2424

25-
public long nextLong() {
26-
return ((long) intGenerator.nextInt()) * intGenerator.nextInt();
27-
}
28-
29-
// TODO nextLong(upper)
25+
private long i1, i2;
3026

31-
// TODO nextLong(lower, upper)
27+
public Xorshf64() {
28+
i1 = intGenerator.nextInt();
29+
i2 = intGenerator.nextInt();
30+
}
3231

32+
public long nextLong() {
33+
final long i3 = intGenerator.nextInt();
34+
long result = ((i1*i2)<<1) + i3; // make upper bound 63 bit
35+
i1 = i2;
36+
i2 = i3;
37+
return result;
38+
}
3339
}

src/main/java/de/tilman_neumann/jml/random/Xorshf64b.java

Lines changed: 0 additions & 35 deletions
This file was deleted.
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* java-math-library is a Java library focused on number theory, but not necessarily limited to it. It is based on the PSIQS 4.0 factoring project.
3+
* Copyright (C) 2018-2025 Tilman Neumann - [email protected]
4+
*
5+
* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License
6+
* as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
7+
*
8+
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
9+
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
10+
*
11+
* You should have received a copy of the GNU General Public License along with this program;
12+
* if not, see <http://www.gnu.org/licenses/>.
13+
*/
14+
package de.tilman_neumann.jml.random;
15+
16+
import static org.junit.Assert.assertTrue;
17+
18+
import org.apache.logging.log4j.LogManager;
19+
import org.apache.logging.log4j.Logger;
20+
import org.junit.BeforeClass;
21+
import org.junit.Test;
22+
23+
import de.tilman_neumann.util.ConfigUtil;
24+
25+
/**
26+
* Tests of the LehmerRng64MH random number generator.
27+
*
28+
* @author Tilman Neumann
29+
*/
30+
public class LehmerRng64MHTest {
31+
private static final Logger LOG = LogManager.getLogger(LehmerRng64MHTest.class);
32+
33+
private static final int NCOUNT = 1000000;
34+
35+
private static final LehmerRng64MH rng = new LehmerRng64MH();
36+
37+
@BeforeClass
38+
public static void setup() {
39+
ConfigUtil.initProject();
40+
}
41+
42+
@Test
43+
public void testNextLongNoArgs() {
44+
long min = Long.MAX_VALUE, max = Long.MIN_VALUE;
45+
for (int i=0; i<NCOUNT; i++) {
46+
long n = rng.nextLong();
47+
if (n<min) min = n;
48+
if (n>max) max = n;
49+
}
50+
LOG.debug(NCOUNT + " numbers from " + rng.getClass().getSimpleName() + ".nextLong() gave min = " + min + ", max = " + max);
51+
assertTrue(min < 0);
52+
}
53+
}
Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,53 @@
1+
/*
2+
* java-math-library is a Java library focused on number theory, but not necessarily limited to it. It is based on the PSIQS 4.0 factoring project.
3+
* Copyright (C) 2018-2025 Tilman Neumann - [email protected]
4+
*
5+
* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License
6+
* as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
7+
*
8+
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
9+
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
10+
*
11+
* You should have received a copy of the GNU General Public License along with this program;
12+
* if not, see <http://www.gnu.org/licenses/>.
13+
*/
114
package de.tilman_neumann.jml.random;
215

16+
import static org.junit.Assert.assertTrue;
17+
318
import org.apache.logging.log4j.LogManager;
419
import org.apache.logging.log4j.Logger;
20+
import org.junit.BeforeClass;
21+
import org.junit.Test;
522

623
import de.tilman_neumann.util.ConfigUtil;
724

825
/**
926
* Tests of Lehmer's random number generator for 64 bit numbers.
1027
*
11-
* Currently only prints some generated numbers.
12-
*
1328
* @author Tilman Neumann
1429
*/
1530
public class LehmerRng64Test {
1631
private static final Logger LOG = LogManager.getLogger(LehmerRng64Test.class);
32+
33+
private static final int NCOUNT = 1000000;
1734

18-
public static void main(String[] args) {
35+
private static final LehmerRng64 rng = new LehmerRng64();
36+
37+
@BeforeClass
38+
public static void setup() {
1939
ConfigUtil.initProject();
20-
21-
LehmerRng64 rng = new LehmerRng64();
22-
for (int i=0;i<100; i++) {
23-
LOG.debug("" + rng.nextLong());
40+
}
41+
42+
@Test
43+
public void testNextLongNoArgs() {
44+
long min = Long.MAX_VALUE, max = Long.MIN_VALUE;
45+
for (int i=0; i<NCOUNT; i++) {
46+
long n = rng.nextLong();
47+
if (n<min) min = n;
48+
if (n>max) max = n;
2449
}
50+
LOG.debug(NCOUNT + " numbers from " + rng.getClass().getSimpleName() + ".nextLong() gave min = " + min + ", max = " + max);
51+
assertTrue(min < 0);
2552
}
2653
}

src/test/java/de/tilman_neumann/jml/random/RngPerformanceTest.java

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ public class RngPerformanceTest {
4747
private static final LehmerRng64 lehmer64 = new LehmerRng64();
4848
private static final LehmerRng64MH lehmer64MH = new LehmerRng64MH();
4949
private static final Xorshf64 xorshf64 = new Xorshf64();
50-
private static final Xorshf64b xorshf64b = new Xorshf64b();
5150

5251
/**
5352
* Test.
@@ -172,9 +171,5 @@ public static void main(String[] args) {
172171
xorshf64.nextLong();
173172
}
174173
LOG.debug("Xorshf64.nextLong() took " + timer.capture() + " ms");
175-
for (int i=0; i<NCOUNT; i++) {
176-
xorshf64b.nextLong();
177-
}
178-
LOG.debug("Xorshf64b.nextLong() took " + timer.capture() + " ms");
179174
}
180175
}

0 commit comments

Comments
 (0)