| /* | |
| * Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. | |
| * | |
| * This code 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. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| import java.util.ArrayList; | |
| import java.util.List; | |
| import java.util.PrimitiveIterator; | |
| import java.util.random.*; | |
| import java.util.Set; | |
| import java.util.concurrent.CompletableFuture; | |
| import java.util.concurrent.ExecutionException; | |
| import java.util.concurrent.ThreadLocalRandom; | |
| import java.util.concurrent.TimeUnit; | |
| import java.util.concurrent.TimeoutException; | |
| import java.util.function.IntSupplier; | |
| import java.util.function.LongSupplier; | |
| import java.util.function.BooleanSupplier; | |
| import java.util.function.Supplier; | |
| import java.util.function.Function; | |
| import java.util.stream.Stream; | |
| import static java.util.stream.Collectors.toList; | |
| import static java.util.stream.Collectors.toSet; | |
| /** | |
| * @test | |
| * @summary test bit sequences produced by clases that implement interface RandomGenerator | |
| * @bug 8248862 | |
| * @run main RandomTestChiSquared | |
| * @key randomness | |
| */ | |
| public class RandomTestChiSquared { | |
| static String currentRNG = ""; | |
| static int failCount = 0; | |
| static void exceptionOnFail() { | |
| if (failCount != 0) { | |
| throw new RuntimeException(failCount + " fails detected"); | |
| } | |
| } | |
| static void setRNG(String rng) { | |
| currentRNG = rng; | |
| } | |
| static void fail(String format, Object... args) { | |
| if (currentRNG.length() != 0) { | |
| System.err.println(currentRNG); | |
| currentRNG = ""; | |
| } | |
| System.err.format(" " + format, args); | |
| failCount++; | |
| } | |
| // Some simple chi-squared tests similar to that used for the FIPS 140 poker test, | |
| // but intended primarily to test production of random values from ranges. | |
| private static final int SEQUENCE_SIZE = 20000; | |
| // Entry k of this table was computed in Microsoft Excel using the formulae | |
| // =CHISQ.INV(0.0000000777517,k) and =CHISQ.INV.RT(0.00000142611,k) | |
| // (except that entry 0 is a dummy and should never be used). | |
| static final double[][] chiSquaredBounds = { | |
| { 0.0, 0.0 }, | |
| { 9.49598E-15, 23.24513154 }, | |
| { 1.55503E-07, 26.92112020 }, | |
| { 4.40485E-05, 29.93222467 }, | |
| { 0.000788782, 32.62391510 }, | |
| { 0.004636947, 35.11665045 }, | |
| { 0.015541535, 37.46947634 }, | |
| { 0.037678318, 39.71667636 }, | |
| { 0.074471919, 41.88031518 }, | |
| { 0.128297057, 43.97561646 }, | |
| { 0.200566433, 46.01362542 }, | |
| { 0.291944952, 48.00266676 }, | |
| { 0.402564694, 49.94920504 }, | |
| { 0.532199236, 51.85838271 }, | |
| { 0.680392565, 53.73437242 }, | |
| { 0.846549931, 55.58061674 }, | |
| { 1.030000010, 57.39999630 }, | |
| { 1.230036580, 59.19495111 }, | |
| { 1.445945898, 60.96756998 }, | |
| { 1.677024296, 62.71965780 }, | |
| { 1.922589129, 64.45278706 }, | |
| { 2.181985238, 66.16833789 }, | |
| { 2.454588423, 67.86752964 }, | |
| { 2.739806923, 69.55144602 }, | |
| { 3.037081611, 71.22105544 }, | |
| { 3.345885349, 72.87722754 }, | |
| { 3.665721841, 74.52074682 }, | |
| { 3.996124178, 76.15232388 }, | |
| { 4.336653242, 77.77260487 }, | |
| { 4.686896041, 79.38217943 }, | |
| { 5.046464051, 80.98158736 }, | |
| { 5.414991603, 82.57132439 } | |
| }; | |
| // N is the number of categories; every element of s ought to be in [0,N). | |
| // N must be in [1,31]. | |
| static boolean chiSquaredTest(String id, int N, IntSupplier theSupplier) { | |
| int[] stats = new int[N]; | |
| for (int j = 0; j < SEQUENCE_SIZE; j++) { | |
| int v = theSupplier.getAsInt(); | |
| // assert((0 <= v) && (v < N)); | |
| ++stats[v]; | |
| } | |
| int z = 0; | |
| for (int k = 0; k < stats.length; k++) { | |
| z += stats[k]*stats[k]; | |
| } | |
| double x = ((double)N / (double)SEQUENCE_SIZE) * (double)z - (double)SEQUENCE_SIZE; | |
| double lo = chiSquaredBounds[N][0]; | |
| double hi = chiSquaredBounds[N][1]; | |
| boolean chiSquaredSuccess = ((lo < x) && (x < hi)); | |
| if (!chiSquaredSuccess) fail("chi-squared test failure for %s: x=%g (should be in [%g,%g])\n", id, x, lo, hi); | |
| return chiSquaredSuccess; | |
| } | |
| static boolean testRngChiSquared(String id, int N, IntSupplier theSupplier) { | |
| if (chiSquaredTest(id, N, theSupplier)) return true; | |
| fail("testRngChiSquared glitch"); | |
| return chiSquaredTest(id, N, theSupplier) && chiSquaredTest(id, N, theSupplier); | |
| } | |
| static void tryIt(RandomGenerator rng, String kind, Function<String,BooleanSupplier> f) { | |
| String id = rng.getClass().getName() + " " + kind; | |
| // System.out.printf("Testing %s\n", id); | |
| boolean success = f.apply(id).getAsBoolean(); | |
| if (!success) { | |
| fail("*** Failure of %s\n", id); | |
| } | |
| } | |
| // To test one instance of an RandomGenerator with the BSI test suite, we test: | |
| // nextInt() | |
| // nextLong() | |
| // nextBoolean() | |
| // ints() | |
| // longs() | |
| // doubles() | |
| // nextDouble() | |
| // nextFloat() | |
| static final int[] testSizes = { 11, 13, 16, 17, 19, 23, 24 }; | |
| static void testOneRng(RandomGenerator rng, int failureTolerance) { | |
| String name = rng.getClass().getName(); | |
| for (int j = 0; j < testSizes.length; j++) { | |
| int N = testSizes[j]; | |
| tryIt(rng, "nextInt(" + N + ")", (String id) -> () -> testRngChiSquared(id, N, () -> rng.nextInt(N))); | |
| tryIt(rng, "nextInt(43, " + (N+43) + ")", (String id) -> () -> testRngChiSquared(id, N, () -> rng.nextInt(43, N+43) - 43)); | |
| tryIt(rng, "nextInt(-59, " + (N-59) + ")", (String id) -> () -> testRngChiSquared(id, N, () -> rng.nextInt(-59, N-59) + 59)); | |
| } | |
| for (int j = 0; j < testSizes.length; j++) { | |
| int N = testSizes[j]; | |
| tryIt(rng, "nextLong(" + N + ")", (String id) -> () -> testRngChiSquared(id, N, () -> (int)(rng.nextLong(N)))); | |
| tryIt(rng, "nextLong(43, " + (N+43) + ")", (String id) -> () -> testRngChiSquared(id, N, () -> (int)(rng.nextLong(43, N+43) - 43))); | |
| tryIt(rng, "nextLong(-59, " + (N-59) + ")", (String id) -> () -> testRngChiSquared(id, N, () -> (int)(rng.nextLong(-59, N-59) + 59))); | |
| } | |
| for (int j = 0; j < testSizes.length; j++) { | |
| int N = testSizes[j]; | |
| tryIt(rng, "nextFloat(" + N + ")", (String id) -> () -> testRngChiSquared(id, N, () -> (int)(rng.nextFloat(N)) % N)); | |
| tryIt(rng, "nextFloat(43, " + (N+43) + ")", (String id) -> () -> testRngChiSquared(id, N, () -> (int)(rng.nextFloat(43, N+43) - 43) % N)); | |
| tryIt(rng, "nextFloat(-59, " + (N-59) + ")", (String id) -> () -> testRngChiSquared(id, N, () -> (int)(rng.nextFloat(-59, N-59) + 59) % N)); | |
| } | |
| for (int j = 0; j < testSizes.length; j++) { | |
| int N = testSizes[j]; | |
| tryIt(rng, "nextDouble(" + N + ")", (String id) -> () -> testRngChiSquared(id, N, () -> (int)(rng.nextDouble(N)) % N)); | |
| tryIt(rng, "nextDouble(43, " + (N+43) + ")", (String id) -> () -> testRngChiSquared(id, N, () -> (int)(rng.nextDouble(43, N+43) - 43) % N)); | |
| tryIt(rng, "nextDouble(-59, " + (N-59) + ")", (String id) -> () -> testRngChiSquared(id, N, () -> (int)(rng.nextDouble(-59, N-59) + 59) % N)); | |
| } | |
| for (int j = 0; j < testSizes.length; j++) { | |
| int N = testSizes[j]; | |
| PrimitiveIterator.OfInt iter1 = rng.ints(0, N).iterator(); | |
| tryIt(rng, "ints(0, " + N + ")", (String id) -> () -> testRngChiSquared(id, N, iter1::next)); | |
| PrimitiveIterator.OfInt iter2 = rng.ints(43, N+43).iterator(); | |
| tryIt(rng, "ints(43, " + (N+43) + ")", (String id) -> () -> testRngChiSquared(id, N, () -> iter2.next() - 43)); | |
| PrimitiveIterator.OfInt iter3 = rng.ints(-59, N-59).iterator(); | |
| tryIt(rng, "ints(-59, " + (N-59) + ")", (String id) -> () -> testRngChiSquared(id, N, () -> iter3.next() + 59)); | |
| } | |
| for (int j = 0; j < testSizes.length; j++) { | |
| int N = testSizes[j]; | |
| PrimitiveIterator.OfLong iter1 = rng.longs(0, N).iterator(); | |
| tryIt(rng, "longs(0, " + N + ")", (String id) -> () -> testRngChiSquared(id, N, () -> (int)(iter1.next() + 0))); | |
| PrimitiveIterator.OfLong iter2 = rng.longs(43, N+43).iterator(); | |
| tryIt(rng, "longs(43, " + (N+43) + ")", (String id) -> () -> testRngChiSquared(id, N, () -> (int)(iter2.next() - 43))); | |
| PrimitiveIterator.OfLong iter3 = rng.longs(-59, N-59).iterator(); | |
| tryIt(rng, "longs(-59, " + (N-59) + ")", (String id) -> () -> testRngChiSquared(id, N, () -> (int)(iter3.next() + 59))); | |
| } | |
| for (int j = 0; j < testSizes.length; j++) { | |
| int N = testSizes[j]; | |
| PrimitiveIterator.OfDouble iter1 = rng.doubles(0, N).iterator(); | |
| tryIt(rng, "doubles(0, " + N + ")", (String id) -> () -> testRngChiSquared(id, N, () -> (int)(iter1.next() + 0) % N)); | |
| PrimitiveIterator.OfDouble iter2 = rng.doubles(43, N+43).iterator(); | |
| tryIt(rng, "doubles(43, " + (N+43) + ")", (String id) -> () -> testRngChiSquared(id, N, () -> (int)(iter2.next() - 43) % N)); | |
| PrimitiveIterator.OfDouble iter3 = rng.doubles(-59, N-59).iterator(); | |
| tryIt(rng, "doubles(-59, " + (N-59) + ")", (String id) -> () -> testRngChiSquared(id, N, () -> (int)(iter3.next() + 59) % N)); | |
| } | |
| } | |
| public static void main(String[] args) { | |
| RandomGeneratorFactory.all() | |
| .filter(f -> !f.name().equals("SecureRandom")) | |
| .forEach(factory -> { | |
| setRNG(factory.name()); | |
| if (factory.name().equals("Random")) { | |
| testOneRng(factory.create(417), 1); | |
| } else { | |
| testOneRng(factory.create(417), 0); | |
| } | |
| }); | |
| exceptionOnFail(); | |
| } | |
| } | |