From 4a081db1c3a856614c9966677c5099fa4950ecbb Mon Sep 17 00:00:00 2001 From: Ivan Hetman Date: Tue, 19 Sep 2023 10:39:57 +0300 Subject: [PATCH] classlib: update RandomGenerator implementation (#743) --- .../org/teavm/classlib/impl/RandomUtils.java | 94 ++++ .../org/teavm/classlib/java/util/TRandom.java | 427 +----------------- .../java/util/random/TRandomGenerator.java | 223 +++++++++ .../teavm/classlib/java/util/RandomTest.java | 171 +++++++ 4 files changed, 503 insertions(+), 412 deletions(-) create mode 100644 classlib/src/main/java/org/teavm/classlib/impl/RandomUtils.java create mode 100644 classlib/src/main/java/org/teavm/classlib/java/util/random/TRandomGenerator.java create mode 100644 tests/src/test/java/org/teavm/classlib/java/util/RandomTest.java diff --git a/classlib/src/main/java/org/teavm/classlib/impl/RandomUtils.java b/classlib/src/main/java/org/teavm/classlib/impl/RandomUtils.java new file mode 100644 index 000000000..e8d6c44f9 --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/impl/RandomUtils.java @@ -0,0 +1,94 @@ +/* + * Copyright 2023 ihromant. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.impl; + +import java.util.function.DoubleSupplier; + +public class RandomUtils { + public static void checkStreamSize(long streamSize) { + if (streamSize < 0L) { + throw new IllegalArgumentException(); + } + } + + public static void checkBound(float bound) { + if (!(bound > 0.0 && bound < Float.POSITIVE_INFINITY)) { + throw new IllegalArgumentException(); + } + } + + public static void checkBound(double bound) { + if (!(bound > 0.0 && bound < Double.POSITIVE_INFINITY)) { + throw new IllegalArgumentException(); + } + } + + public static void checkBound(int bound) { + if (bound <= 0) { + throw new IllegalArgumentException(); + } + } + + public static void checkBound(long bound) { + if (bound <= 0) { + throw new IllegalArgumentException(); + } + } + + public static void checkRange(float origin, float bound) { + if (!(origin < bound && bound - origin < Float.POSITIVE_INFINITY)) { + throw new IllegalArgumentException(); + } + } + + public static void checkRange(double origin, double bound) { + if (!(origin < bound && bound - origin < Double.POSITIVE_INFINITY)) { + throw new IllegalArgumentException(); + } + } + + public static void checkRange(int origin, int bound) { + if (origin >= bound) { + throw new IllegalArgumentException(); + } + } + + public static void checkRange(long origin, long bound) { + if (origin >= bound) { + throw new IllegalArgumentException(); + } + } + + public static double[] pairGaussian(DoubleSupplier rng) { + /* + * This implementation uses the polar method to generate two gaussian + * values at a time. One is returned, and the other is stored to be returned + * next time. + */ + double v1; + double v2; + double s; + do { + v1 = 2 * rng.getAsDouble() - 1; + v2 = 2 * rng.getAsDouble() - 1; + s = v1 * v1 + v2 * v2; + } while (s >= 1 || s == 0); + + double m = StrictMath.sqrt(-2 * StrictMath.log(s) / s); + + return new double[] { v1 * m, v2 * m }; + } +} diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/TRandom.java b/classlib/src/main/java/org/teavm/classlib/java/util/TRandom.java index 30c2401ff..d0fccb06b 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/util/TRandom.java +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TRandom.java @@ -15,32 +15,23 @@ */ package org.teavm.classlib.java.util; -import java.util.function.DoublePredicate; -import java.util.function.IntPredicate; -import java.util.function.LongPredicate; import org.teavm.backend.wasm.runtime.WasmSupport; import org.teavm.classlib.PlatformDetector; +import org.teavm.classlib.impl.RandomUtils; import org.teavm.classlib.java.io.TSerializable; -import org.teavm.classlib.java.lang.TMath; import org.teavm.classlib.java.lang.TObject; -import org.teavm.classlib.java.util.stream.TDoubleStream; -import org.teavm.classlib.java.util.stream.TIntStream; -import org.teavm.classlib.java.util.stream.TLongStream; -import org.teavm.classlib.java.util.stream.doubleimpl.TSimpleDoubleStreamImpl; -import org.teavm.classlib.java.util.stream.intimpl.TSimpleIntStreamImpl; -import org.teavm.classlib.java.util.stream.longimpl.TSimpleLongStreamImpl; +import org.teavm.classlib.java.util.random.TRandomGenerator; import org.teavm.interop.Import; import org.teavm.interop.Unmanaged; import org.teavm.jso.JSBody; -public class TRandom extends TObject implements TSerializable { - +public class TRandom extends TObject implements TRandomGenerator, TSerializable { /** A stored gaussian value for nextGaussian() */ private double storedGaussian; - + /** Whether storedGuassian value is valid */ private boolean haveStoredGaussian; - + public TRandom() { } @@ -50,24 +41,12 @@ public class TRandom extends TObject implements TSerializable { public void setSeed(@SuppressWarnings("unused") long seed) { } - protected int next(int bits) { - if (bits == 32) { - return (int) (nextDouble() * ((1L << 32) - 1) + Integer.MIN_VALUE); - } else { - return (int) (nextDouble() * (1L << TMath.min(32, bits))); - } - } - - public void nextBytes(byte[] bytes) { - for (int i = 0; i < bytes.length; ++i) { - bytes[i] = (byte) next(8); - } - } - + @Override public int nextInt() { - return next(32); + return (int) (0x1.0p+32 * nextDouble() + Integer.MIN_VALUE); } + @Override public int nextInt(int n) { if (n <= 0) { throw new IllegalArgumentException(); @@ -75,73 +54,17 @@ public class TRandom extends TObject implements TSerializable { return (int) (nextDouble() * n); } - public int nextInt(int origin, int bound) { - if (origin >= bound) { - throw new IllegalArgumentException(); - } - int range = bound - origin; - if (range > 0) { - return nextInt(range) + origin; - } else { - while (true) { - int value = nextInt(); - if (value >= origin && value < bound) { - return value; - } - } - } - } - + @Override public long nextLong() { return ((long) nextInt() << 32) | nextInt(); } - public long nextLong(long bound) { - if (bound <= 0) { - throw new IllegalArgumentException(); - } - while (true) { - long value = nextLong(); - long result = value % bound; - if (value - result + (bound - 1) < 0) { - return result; - } - } - } - - public long nextLong(long origin, long bound) { - if (origin >= bound) { - throw new IllegalArgumentException(); - } - long range = bound - origin; - if (range > 0) { - return nextLong(range) + origin; - } else { - while (true) { - long value = nextLong(); - if (value >= origin && value < bound) { - return value; - } - } - } - } - - public boolean nextBoolean() { - return nextInt() % 2 == 0; - } - + @Override public float nextFloat() { return (float) nextDouble(); } - public float nextFloat(float bound) { - return (float) nextDouble(bound); - } - - public float nextFloat(float origin, float bound) { - return (float) nextDouble(origin, bound); - } - + @Override public double nextDouble() { if (PlatformDetector.isC()) { return crand(); @@ -152,24 +75,6 @@ public class TRandom extends TObject implements TSerializable { } } - public double nextDouble(double bound) { - if (bound <= 0) { - throw new IllegalArgumentException(); - } - double value = nextDouble() * bound; - if (value == bound) { - value = Math.nextDown(value); - } - return value; - } - - public double nextDouble(double origin, double bound) { - if (origin >= bound) { - throw new IllegalArgumentException(); - } - return origin + nextDouble(bound - origin); - } - @Import(name = "teavm_rand") @Unmanaged private static native double crand(); @@ -178,329 +83,27 @@ public class TRandom extends TObject implements TSerializable { * Generate a random number with Gaussian distribution: * centered around 0 with a standard deviation of 1.0. */ + @Override public double nextGaussian() { - /* * This implementation uses the polar method to generate two gaussian * values at a time. One is returned, and the other is stored to be returned * next time. */ - if (haveStoredGaussian) { haveStoredGaussian = false; return storedGaussian; } - double v1; - double v2; - double s; - do { - v1 = 2 * nextDouble() - 1; - v2 = 2 * nextDouble() - 1; - s = v1 * v1 + v2 * v2; - } while (s >= 1 || s == 0); - - double m = StrictMath.sqrt(-2 * StrictMath.log(s) / s); - storedGaussian = v2 * m; + double[] pair = RandomUtils.pairGaussian(this::nextDouble); haveStoredGaussian = true; + storedGaussian = pair[1]; - return v1 * m; + return pair[0]; } @JSBody(script = "return Math.random();") @Import(module = "teavmMath", name = "random") @Unmanaged private static native double random(); - - public TIntStream ints(long streamSize) { - if (streamSize < 0) { - throw new IllegalArgumentException(); - } - return new TSimpleIntStreamImpl() { - private long remaining = streamSize; - - @Override - public boolean next(IntPredicate consumer) { - while (remaining > 0) { - --remaining; - if (!consumer.test(nextInt())) { - return true; - } - } - return false; - } - }; - } - - public TIntStream ints() { - return new TSimpleIntStreamImpl() { - @Override - public boolean next(IntPredicate consumer) { - while (consumer.test(nextInt())) { - // go on - } - return true; - } - }; - } - - public TIntStream ints(long streamSize, int randomNumberOrigin, int randomNumberBound) { - if (streamSize < 0 || randomNumberOrigin >= randomNumberBound) { - throw new IllegalArgumentException(); - } - - int range = randomNumberBound - randomNumberOrigin; - if (range > 0) { - return new TSimpleIntStreamImpl() { - long remaining = streamSize; - - @Override - public boolean next(IntPredicate consumer) { - while (remaining > 0) { - --remaining; - if (!consumer.test(nextInt(range) + randomNumberOrigin)) { - return true; - } - } - return false; - } - }; - } else { - return new TSimpleIntStreamImpl() { - long remaining = streamSize; - - @Override - public boolean next(IntPredicate consumer) { - while (remaining > 0) { - --remaining; - int n; - do { - n = nextInt(); - } while (n < randomNumberOrigin || n >= randomNumberBound); - if (!consumer.test(n)) { - return true; - } - } - return false; - } - }; - } - } - - public TIntStream ints(int randomNumberOrigin, int randomNumberBound) { - if (randomNumberOrigin >= randomNumberBound) { - throw new IllegalArgumentException(); - } - - int range = randomNumberBound - randomNumberOrigin; - if (range > 0) { - return new TSimpleIntStreamImpl() { - @Override - public boolean next(IntPredicate consumer) { - while (true) { - if (!consumer.test(nextInt(range) + randomNumberOrigin)) { - return true; - } - } - } - }; - } else { - return new TSimpleIntStreamImpl() { - @Override - public boolean next(IntPredicate consumer) { - while (true) { - int n; - do { - n = nextInt(); - } while (n < randomNumberOrigin || n >= randomNumberBound); - if (!consumer.test(n)) { - return true; - } - } - } - }; - } - } - - public TLongStream longs(long streamSize) { - if (streamSize < 0) { - throw new IllegalArgumentException(); - } - return new TSimpleLongStreamImpl() { - private long remaining = streamSize; - - @Override - public boolean next(LongPredicate consumer) { - while (remaining > 0) { - --remaining; - if (!consumer.test(nextLong())) { - return true; - } - } - return false; - } - }; - } - - public TLongStream longs() { - return new TSimpleLongStreamImpl() { - @Override - public boolean next(LongPredicate consumer) { - while (consumer.test(nextLong())) { - // go on - } - return true; - } - }; - } - - public TLongStream longs(long streamSize, long randomNumberOrigin, long randomNumberBound) { - if (streamSize < 0 || randomNumberOrigin >= randomNumberBound) { - throw new IllegalArgumentException(); - } - - long range = randomNumberBound - randomNumberOrigin; - if (range > 0) { - return new TSimpleLongStreamImpl() { - long remaining = streamSize; - - @Override - public boolean next(LongPredicate consumer) { - while (remaining > 0) { - --remaining; - if (!consumer.test(nextLong(range) + randomNumberOrigin)) { - return true; - } - } - return false; - } - }; - } else { - return new TSimpleLongStreamImpl() { - long remaining = streamSize; - - @Override - public boolean next(LongPredicate consumer) { - while (remaining > 0) { - --remaining; - long n; - do { - n = nextLong(); - } while (n < randomNumberOrigin || n >= randomNumberBound); - if (!consumer.test(n)) { - return true; - } - } - return false; - } - }; - } - } - - public TLongStream longs(long randomNumberOrigin, long randomNumberBound) { - if (randomNumberOrigin >= randomNumberBound) { - throw new IllegalArgumentException(); - } - - long range = randomNumberBound - randomNumberOrigin; - if (range > 0) { - return new TSimpleLongStreamImpl() { - @Override - public boolean next(LongPredicate consumer) { - while (true) { - if (!consumer.test(nextLong(range) + randomNumberOrigin)) { - return true; - } - } - } - }; - } else { - return new TSimpleLongStreamImpl() { - @Override - public boolean next(LongPredicate consumer) { - while (true) { - long n; - do { - n = nextLong(); - } while (n < randomNumberOrigin || n >= randomNumberBound); - if (!consumer.test(n)) { - return true; - } - } - } - }; - } - } - - public TDoubleStream doubles(long streamSize) { - if (streamSize < 0) { - throw new IllegalArgumentException(); - } - return new TSimpleDoubleStreamImpl() { - private long remaining = streamSize; - - @Override - public boolean next(DoublePredicate consumer) { - while (remaining > 0) { - --remaining; - if (!consumer.test(nextDouble())) { - return true; - } - } - return false; - } - }; - } - - public TDoubleStream doubles() { - return new TSimpleDoubleStreamImpl() { - @Override - public boolean next(DoublePredicate consumer) { - while (consumer.test(nextDouble())) { - // go on - } - return true; - } - }; - } - - public TDoubleStream doubles(long streamSize, double randomNumberOrigin, double randomNumberBound) { - if (streamSize < 0 || randomNumberOrigin >= randomNumberBound) { - throw new IllegalArgumentException(); - } - - double range = randomNumberBound - randomNumberOrigin; - return new TSimpleDoubleStreamImpl() { - long remaining = streamSize; - - @Override - public boolean next(DoublePredicate consumer) { - while (remaining > 0) { - --remaining; - if (!consumer.test(nextDouble() * range + randomNumberOrigin)) { - return true; - } - } - return false; - } - }; - } - - public TDoubleStream doubles(double randomNumberOrigin, double randomNumberBound) { - if (randomNumberOrigin >= randomNumberBound) { - throw new IllegalArgumentException(); - } - - double range = randomNumberBound - randomNumberOrigin; - return new TSimpleDoubleStreamImpl() { - @Override - public boolean next(DoublePredicate consumer) { - while (true) { - if (!consumer.test(nextDouble() * range + randomNumberOrigin)) { - return true; - } - } - } - }; - } } diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/random/TRandomGenerator.java b/classlib/src/main/java/org/teavm/classlib/java/util/random/TRandomGenerator.java new file mode 100644 index 000000000..47aad1d93 --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/util/random/TRandomGenerator.java @@ -0,0 +1,223 @@ +/* + * Copyright 2023 ihromant. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.java.util.random; + +import org.teavm.classlib.impl.RandomUtils; +import org.teavm.classlib.java.util.stream.TDoubleStream; +import org.teavm.classlib.java.util.stream.TIntStream; +import org.teavm.classlib.java.util.stream.TLongStream; + +public interface TRandomGenerator { + default boolean isDeprecated() { + return false; + } + + default TDoubleStream doubles() { + return TDoubleStream.generate(this::nextDouble); + } + + default TDoubleStream doubles(double origin, double bound) { + RandomUtils.checkRange(origin, bound); + return TDoubleStream.generate(() -> nextDouble(origin, bound)); + } + + default TDoubleStream doubles(long streamSize) { + RandomUtils.checkStreamSize(streamSize); + return doubles().limit(streamSize); + } + + default TDoubleStream doubles(long streamSize, double origin, + double bound) { + RandomUtils.checkStreamSize(streamSize); + RandomUtils.checkRange(origin, bound); + return doubles(origin, bound).limit(streamSize); + } + + default TIntStream ints() { + return TIntStream.generate(this::nextInt); + } + + default TIntStream ints(int origin, int bound) { + RandomUtils.checkRange(origin, bound); + return TIntStream.generate(() -> nextInt(origin, bound)); + } + + default TIntStream ints(long streamSize) { + RandomUtils.checkStreamSize(streamSize); + return ints().limit(streamSize); + } + + default TIntStream ints(long streamSize, int origin, + int bound) { + RandomUtils.checkStreamSize(streamSize); + RandomUtils.checkRange(origin, bound); + return ints(origin, bound).limit(streamSize); + } + + default TLongStream longs() { + return TLongStream.generate(this::nextLong); + } + + default TLongStream longs(long origin, long bound) { + RandomUtils.checkRange(origin, bound); + return TLongStream.generate(() -> nextLong(origin, bound)); + } + + default TLongStream longs(long streamSize) { + RandomUtils.checkStreamSize(streamSize); + return longs().limit(streamSize); + } + + default TLongStream longs(long streamSize, long origin, + long bound) { + RandomUtils.checkStreamSize(streamSize); + RandomUtils.checkRange(origin, bound); + return longs(origin, bound).limit(streamSize); + } + + default boolean nextBoolean() { + return nextInt() < 0; + } + + default void nextBytes(byte[] bytes) { + if (bytes.length == 0) { + return; + } + int len = (bytes.length - 1) / Integer.BYTES + 1; + for (int i = 0; i < len; i++) { + int rnd = nextInt(); + for (int j = 0; j < Integer.BYTES; j++) { + int idx = Integer.BYTES * i + j; + if (idx < bytes.length) { + bytes[idx] = (byte) (0xFF & (rnd >> (i * Byte.SIZE))); + } + } + } + } + + default float nextFloat() { + return (nextInt() >>> 8) * 0x1.0p-24f; + } + + default float nextFloat(float bound) { + RandomUtils.checkBound(bound); + float res = nextFloat() * bound; + if (res >= bound) { + res = Math.nextDown(bound); + } + return res; + } + + default float nextFloat(float origin, float bound) { + RandomUtils.checkRange(origin, bound); + float res = nextFloat() * (bound - origin) + origin; + if (res >= bound) { + res = Math.nextAfter(bound, origin); + } + return res; + } + + default double nextDouble() { + return (nextLong() >>> 11) * 0x1.0p-53; + } + + default double nextDouble(double bound) { + RandomUtils.checkBound(bound); + double res = nextDouble() * bound; + if (res >= bound) { + res = Math.nextDown(bound); + } + return res; + } + + default double nextDouble(double origin, double bound) { + RandomUtils.checkRange(origin, bound); + double res = nextDouble() * (bound - origin) + origin; + if (res >= bound) { + res = Math.nextAfter(bound, origin); + } + return res; + } + + default int nextInt() { + return (int) (nextLong() >>> 32); + } + + default int nextInt(int bound) { + RandomUtils.checkBound(bound); + int mask = (Integer.highestOneBit(bound) << 1) - 1; + while (true) { + int res = nextInt() & mask; + if (res < bound) { + return res; + } + } + } + + default int nextInt(int origin, int bound) { + RandomUtils.checkRange(origin, bound); + int range = bound - origin; + if (range > 0) { + return nextInt(range) + origin; + } else { + while (true) { + int res = nextInt(); + if (res >= origin && res < bound) { + return res; + } + } + } + } + + long nextLong(); + + default long nextLong(long bound) { + RandomUtils.checkBound(bound); + long mask = (Long.highestOneBit(bound) << 1) - 1; + while (true) { + long res = nextLong() & mask; + if (res < bound) { + return res; + } + } + } + + default long nextLong(long origin, long bound) { + RandomUtils.checkRange(origin, bound); + long range = bound - origin; + if (range > 0) { + return nextLong(range) + origin; + } else { + while (true) { + long res = nextLong(); + if (res >= origin && res < bound) { + return res; + } + } + } + } + + default double nextGaussian() { + return RandomUtils.pairGaussian(this::nextDouble)[0]; + } + + default double nextGaussian(double mean, double stddev) { + if (stddev < 0.0) { + throw new IllegalArgumentException(); + } + return mean + stddev * nextGaussian(); + } +} diff --git a/tests/src/test/java/org/teavm/classlib/java/util/RandomTest.java b/tests/src/test/java/org/teavm/classlib/java/util/RandomTest.java new file mode 100644 index 000000000..2d5b42d5d --- /dev/null +++ b/tests/src/test/java/org/teavm/classlib/java/util/RandomTest.java @@ -0,0 +1,171 @@ +/* + * Copyright 2023 ihromant. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.java.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import java.util.Arrays; +import java.util.Random; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.DoubleStream; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.teavm.junit.TeaVMTestRunner; +import org.teavm.junit.WholeClassCompilation; + +@RunWith(TeaVMTestRunner.class) +@WholeClassCompilation +public class RandomTest { + @Test + public void testDoubles() { + Random random = new Random(); + double[] doubles = IntStream.range(0, 1000).mapToDouble(i -> random.nextDouble()).toArray(); + for (double d : doubles) { + assertTrue(d >= 0.0 && d < 1.0); + } + try { + random.nextDouble(-1.0); + fail(); + } catch (IllegalArgumentException e) { + // normal + } + doubles = IntStream.range(0, 1000).mapToDouble(i -> random.nextDouble(20.0)).toArray(); + for (double d : doubles) { + assertTrue(d >= 0.0 && d < 20.0); + } + try { + random.nextDouble(-1.0, -2.0); + fail(); + } catch (IllegalArgumentException e) { + // normal + } + doubles = IntStream.range(0, 1000).mapToDouble(i -> random.nextDouble(-2.0, -1.0)).toArray(); + for (double d : doubles) { + assertTrue(d >= -2.0 && d < 1.0); + } + } + + @Test + public void testIntegers() { + Random random = new Random(); + int[] ints = IntStream.range(0, 10000).map(i -> random.nextInt()) + .toArray(); // 10 000 enough for almost 100% probability + int ones = IntStream.of(ints).reduce(0, (id, i) -> id | i); + Set unique = Arrays.stream(ints).boxed().collect(Collectors.toSet()); + assertEquals(-1, ones); // all ones present + assertTrue(unique.size() > 9900); + try { + random.nextInt(-5); + fail(); + } catch (IllegalArgumentException e) { + // normal + } + ints = IntStream.range(0, 1000).map(i -> random.nextInt(512)).toArray(); + for (int i : ints) { + assertTrue(i >= 0 && i < 512); + } + ints = IntStream.range(0, 1000).map(i -> random.nextInt(20)).toArray(); + for (int i : ints) { + assertTrue(i >= 0 && i < 20); + } + try { + random.nextInt(-3, -5); + fail(); + } catch (IllegalArgumentException e) { + // normal + } + ints = IntStream.range(0, 1000).map(i -> random.nextInt(Integer.MIN_VALUE / 3 * 2, Integer.MAX_VALUE / 3 * 2)) + .toArray(); + for (int i : ints) { + assertTrue(i >= Integer.MIN_VALUE / 3 * 2 && i < Integer.MAX_VALUE / 3 * 2); + } + Arrays.stream(ints).anyMatch(i -> i < Integer.MIN_VALUE / 2); + Arrays.stream(ints).anyMatch(i -> i > Integer.MAX_VALUE / 2); + } + + @Test + public void testLongs() { + Random random = new Random(); + long[] longs = IntStream.range(0, 10000).mapToLong(i -> random.nextLong()) + .toArray(); // 10 000 enough for almost 100% probability + long ones = LongStream.of(longs).reduce(0L, (id, i) -> id | i); + Set unique = Arrays.stream(longs).boxed().collect(Collectors.toSet()); + assertEquals(-1L, ones); // all ones present + assertTrue(unique.size() > 9900); + try { + random.nextLong(-5); + fail(); + } catch (IllegalArgumentException e) { + // normal + } + longs = IntStream.range(0, 1000).mapToLong(i -> random.nextLong(512L)).toArray(); + for (long l : longs) { + assertTrue(l >= 0L && l < 512L); + } + longs = IntStream.range(0, 1000).mapToLong(i -> random.nextLong(20L)).toArray(); + for (long l : longs) { + assertTrue(l >= 0 && l < 20); + } + try { + random.nextLong(-3, -5); + fail(); + } catch (IllegalArgumentException e) { + // normal + } + longs = IntStream.range(0, 1000).mapToLong(i -> random.nextLong(Long.MIN_VALUE / 3 * 2, Long.MAX_VALUE / 3 * 2)) + .toArray(); + for (long l : longs) { + assertTrue(l >= Long.MIN_VALUE / 3 * 2 && l < Long.MAX_VALUE / 3 * 2); + } + Arrays.stream(longs).anyMatch(l -> l < Long.MIN_VALUE / 2); + Arrays.stream(longs).anyMatch(l -> l > Long.MAX_VALUE / 2); + } + + @Test + public void testNextBytes() { + Random rand = new Random(); + rand.nextBytes(new byte[0]); + assertTrue(IntStream.range(0, 1000).anyMatch(i -> testNonZero(rand, 5))); + assertTrue(IntStream.range(0, 1000).anyMatch(i -> testNonZero(rand, 6))); + assertTrue(IntStream.range(0, 1000).anyMatch(i -> testNonZero(rand, 7))); + assertTrue(IntStream.range(0, 1000).anyMatch(i -> testNonZero(rand, 8))); + byte[] bytes = new byte[1000]; + rand.nextBytes(bytes); + assertEquals(0xFF, IntStream.range(0, bytes.length).map(i -> 0xFF & bytes[i]).reduce(0, (id, i) -> id | i)); + } + + private boolean testNonZero(Random rand, int size) { + byte[] bytes = new byte[size]; + rand.nextBytes(bytes); + for (byte b : bytes) { + if (b == 0) { + return false; + } + } + return true; + } + + @Test + public void testGaussian() { + Random rand = new Random(); + double[] doubles = IntStream.range(0, 10000).mapToDouble(i -> rand.nextGaussian(30, 10)).toArray(); + assertTrue(DoubleStream.of(doubles).filter(d -> d < 0.0 || d > 60.0).count() < 100); + } +}