1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.rng;
18
19 import java.math.BigDecimal;
20 import java.math.MathContext;
21 import java.util.ArrayList;
22 import java.util.Arrays;
23 import java.util.List;
24 import java.util.SplittableRandom;
25 import java.util.concurrent.ThreadLocalRandom;
26 import java.util.function.LongSupplier;
27 import java.util.stream.Collectors;
28 import java.util.stream.Stream;
29 import org.junit.jupiter.api.Assertions;
30 import org.junit.jupiter.api.Test;
31 import org.junit.jupiter.params.ParameterizedTest;
32 import org.junit.jupiter.params.provider.Arguments;
33 import org.junit.jupiter.params.provider.CsvSource;
34 import org.junit.jupiter.params.provider.MethodSource;
35 import org.junit.jupiter.params.provider.ValueSource;
36
37
38
39
40
41
42
43
44
45
46 class UniformRandomProviderTest {
47
48 private static final int SAMPLE_SIZE = 1000;
49
50 private static final BigDecimal SAMPLE_SIZE_BD = BigDecimal.valueOf(SAMPLE_SIZE);
51
52 private static final double RELATIVE_ERROR = Math.ulp(1.0) * 2;
53
54
55
56
57 private static class DummyGenerator implements UniformRandomProvider {
58
59 static final DummyGenerator INSTANCE = new DummyGenerator();
60
61 @Override
62 public long nextLong() {
63 throw new UnsupportedOperationException("The nextLong method should not be invoked");
64 }
65 }
66
67 static int[] invalidNextIntBound() {
68 return new int[] {0, -1, Integer.MIN_VALUE};
69 }
70
71 static Stream<Arguments> invalidNextIntOriginBound() {
72 return Stream.of(
73 Arguments.of(1, 1),
74 Arguments.of(2, 1),
75 Arguments.of(-1, -1),
76 Arguments.of(-1, -2)
77 );
78 }
79
80 static long[] invalidNextLongBound() {
81 return new long[] {0, -1, Long.MIN_VALUE};
82 }
83
84 static Stream<Arguments> invalidNextLongOriginBound() {
85 return Stream.of(
86 Arguments.of(1L, 1L),
87 Arguments.of(2L, 1L),
88 Arguments.of(-1L, -1L),
89 Arguments.of(-1L, -2L)
90 );
91 }
92
93 static float[] invalidNextFloatBound() {
94 return new float[] {0, -1, -Float.MAX_VALUE, Float.NaN, Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY};
95 }
96
97 static Stream<Arguments> invalidNextFloatOriginBound() {
98 return Stream.of(
99 Arguments.of(1f, 1f),
100 Arguments.of(2f, 1f),
101 Arguments.of(-1f, -1f),
102 Arguments.of(-1f, -2f),
103 Arguments.of(Float.NEGATIVE_INFINITY, 0f),
104 Arguments.of(0f, Float.POSITIVE_INFINITY),
105 Arguments.of(0f, Float.NaN),
106 Arguments.of(Float.NaN, 1f)
107 );
108 }
109
110 static double[] invalidNextDoubleBound() {
111 return new double[] {0, -1, -Double.MAX_VALUE, Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY};
112 }
113
114 static Stream<Arguments> invalidNextDoubleOriginBound() {
115 return Stream.of(
116 Arguments.of(1, 1),
117 Arguments.of(2, 1),
118 Arguments.of(-1, -1),
119 Arguments.of(-1, -2),
120 Arguments.of(Double.NEGATIVE_INFINITY, 0),
121 Arguments.of(0, Double.POSITIVE_INFINITY),
122 Arguments.of(0, Double.NaN),
123 Arguments.of(Double.NaN, 1)
124 );
125 }
126
127
128
129
130
131
132
133
134 private static UniformRandomProvider createRNG(long seed) {
135
136
137
138 return new SplittableRandom(seed)::nextLong;
139 }
140
141 @Test
142 void testNextBytesThrows() {
143 final UniformRandomProvider rng = DummyGenerator.INSTANCE;
144 Assertions.assertThrows(NullPointerException.class, () -> rng.nextBytes(null));
145 Assertions.assertThrows(NullPointerException.class, () -> rng.nextBytes(null, 0, 1));
146
147 final int length = 10;
148 final byte[] bytes = new byte[length];
149 Assertions.assertThrows(IndexOutOfBoundsException.class, () -> rng.nextBytes(bytes, -1, 1), "start < 0");
150 Assertions.assertThrows(IndexOutOfBoundsException.class, () -> rng.nextBytes(bytes, length, 1), "start >= length");
151 Assertions.assertThrows(IndexOutOfBoundsException.class, () -> rng.nextBytes(bytes, 0, -1), "len < 0");
152 Assertions.assertThrows(IndexOutOfBoundsException.class, () -> rng.nextBytes(bytes, 5, 10), "start + len > length");
153 Assertions.assertThrows(IndexOutOfBoundsException.class, () -> rng.nextBytes(bytes, 5, Integer.MAX_VALUE), "start + len > length, taking into account integer overflow");
154 }
155
156 @ParameterizedTest
157 @MethodSource(value = {"invalidNextIntBound"})
158 void testNextIntBoundThrows(int bound) {
159 final UniformRandomProvider rng = DummyGenerator.INSTANCE;
160 Assertions.assertThrows(IllegalArgumentException.class, () -> rng.nextInt(bound));
161 }
162
163 @ParameterizedTest
164 @MethodSource(value = {"invalidNextIntOriginBound"})
165 void testNextIntOriginBoundThrows(int origin, int bound) {
166 final UniformRandomProvider rng = DummyGenerator.INSTANCE;
167 Assertions.assertThrows(IllegalArgumentException.class, () -> rng.nextInt(origin, bound));
168 }
169
170 @ParameterizedTest
171 @MethodSource(value = {"invalidNextLongBound"})
172 void testNextLongBoundThrows(long bound) {
173 final UniformRandomProvider rng = DummyGenerator.INSTANCE;
174 Assertions.assertThrows(IllegalArgumentException.class, () -> rng.nextLong(bound));
175 }
176
177 @ParameterizedTest
178 @MethodSource(value = {"invalidNextLongOriginBound"})
179 void testNextLongOriginBoundThrows(long origin, long bound) {
180 final UniformRandomProvider rng = DummyGenerator.INSTANCE;
181 Assertions.assertThrows(IllegalArgumentException.class, () -> rng.nextLong(origin, bound));
182 }
183
184 @ParameterizedTest
185 @MethodSource(value = {"invalidNextFloatBound"})
186 void testNextFloatBoundThrows(float bound) {
187 final UniformRandomProvider rng = DummyGenerator.INSTANCE;
188 Assertions.assertThrows(IllegalArgumentException.class, () -> rng.nextFloat(bound));
189 }
190
191 @ParameterizedTest
192 @MethodSource(value = {"invalidNextFloatOriginBound"})
193 void testNextFloatOriginBoundThrows(float origin, float bound) {
194 final UniformRandomProvider rng = DummyGenerator.INSTANCE;
195 Assertions.assertThrows(IllegalArgumentException.class, () -> rng.nextFloat(origin, bound));
196 }
197
198 @ParameterizedTest
199 @MethodSource(value = {"invalidNextDoubleBound"})
200 void testNextDoubleBoundThrows(double bound) {
201 final UniformRandomProvider rng = DummyGenerator.INSTANCE;
202 Assertions.assertThrows(IllegalArgumentException.class, () -> rng.nextDouble(bound));
203 }
204
205 @ParameterizedTest
206 @MethodSource(value = {"invalidNextDoubleOriginBound"})
207 void testNextDoubleOriginBoundThrows(double origin, double bound) {
208 final UniformRandomProvider rng = DummyGenerator.INSTANCE;
209 Assertions.assertThrows(IllegalArgumentException.class, () -> rng.nextDouble(origin, bound));
210 }
211
212 @Test
213 void testNextFloatExtremes() {
214 final UniformRandomProvider rng = new DummyGenerator() {
215 private int i;
216 private final int[] values = {0, -1, 1 << 8};
217 @Override
218 public int nextInt() {
219 return values[i++];
220 }
221 };
222 Assertions.assertEquals(0, rng.nextFloat(), "Expected zero bits to return 0");
223 Assertions.assertEquals(Math.nextDown(1f), rng.nextFloat(), "Expected all bits to return ~1");
224
225 Assertions.assertEquals(1f - Math.nextDown(1f), rng.nextFloat(), "Expected 1 bit (shifted) to return ~0");
226 }
227
228 @ParameterizedTest
229 @ValueSource(floats = {Float.MIN_NORMAL, Float.MIN_NORMAL / 2})
230 void testNextFloatBoundRounding(float bound) {
231
232 final UniformRandomProvider rng = new DummyGenerator() {
233 @Override
234 public float nextFloat() {
235 return Math.nextDown(1.0f);
236 }
237 };
238 Assertions.assertEquals(bound, rng.nextFloat() * bound, "Expected result to be rounded up");
239 Assertions.assertEquals(Math.nextDown(bound), rng.nextFloat(bound), "Result was not correctly rounded down");
240 }
241
242 @Test
243 void testNextFloatOriginBoundInfiniteRange() {
244
245 final UniformRandomProvider rng = new DummyGenerator() {
246 private int i;
247 private final float[] values = {0, 0.25f, 0.5f, 0.75f, 1};
248 @Override
249 public float nextFloat() {
250 return values[i++];
251 }
252 };
253 final float x = Float.MAX_VALUE;
254 Assertions.assertEquals(-x, rng.nextFloat(-x, x));
255 Assertions.assertEquals(-x / 2, rng.nextFloat(-x, x), Math.ulp(x / 2));
256 Assertions.assertEquals(0, rng.nextFloat(-x, x));
257 Assertions.assertEquals(x / 2, rng.nextFloat(-x, x), Math.ulp(x / 2));
258 Assertions.assertEquals(Math.nextDown(x), rng.nextFloat(-x, x));
259 }
260
261 @Test
262 void testNextFloatOriginBoundRounding() {
263
264 final float v = Math.nextDown(1.0f);
265 final UniformRandomProvider rng = new DummyGenerator() {
266 @Override
267 public float nextFloat() {
268 return v;
269 }
270 };
271 float origin;
272 float bound;
273
274
275 origin = 3.5f;
276 bound = 4.5f;
277 Assertions.assertEquals(bound, origin + v * (bound - origin), "Expected result to be rounded up");
278 Assertions.assertEquals(Math.nextDown(bound), rng.nextFloat(origin, bound), "Result was not correctly rounded down");
279
280
281
282 origin = -Float.MIN_NORMAL / 2;
283 bound = Float.MIN_NORMAL / 2;
284 Assertions.assertEquals(bound, origin * (1 - v) + v * bound, "Expected result to be rounded up");
285 Assertions.assertEquals(Math.nextDown(bound), rng.nextFloat(origin, bound), "Result was not correctly rounded down");
286 }
287
288 @Test
289 void testNextDoubleExtremes() {
290 final UniformRandomProvider rng = new DummyGenerator() {
291 private int i;
292 private final long[] values = {0, -1, 1L << 11};
293 @Override
294 public long nextLong() {
295 return values[i++];
296 }
297 };
298 Assertions.assertEquals(0, rng.nextDouble(), "Expected zero bits to return 0");
299 Assertions.assertEquals(Math.nextDown(1.0), rng.nextDouble(), "Expected all bits to return ~1");
300
301 Assertions.assertEquals(1.0 - Math.nextDown(1.0), rng.nextDouble(), "Expected 1 bit (shifted) to return ~0");
302 }
303
304 @ParameterizedTest
305 @ValueSource(doubles = {Double.MIN_NORMAL, Double.MIN_NORMAL / 2})
306 void testNextDoubleBoundRounding(double bound) {
307
308 final UniformRandomProvider rng = new DummyGenerator() {
309 @Override
310 public double nextDouble() {
311 return Math.nextDown(1.0);
312 }
313 };
314 Assertions.assertEquals(bound, rng.nextDouble() * bound, "Expected result to be rounded up");
315 Assertions.assertEquals(Math.nextDown(bound), rng.nextDouble(bound), "Result was not correctly rounded down");
316 }
317
318 @Test
319 void testNextDoubleOriginBoundInfiniteRange() {
320
321 final UniformRandomProvider rng = new DummyGenerator() {
322 private int i;
323 private final double[] values = {0, 0.25, 0.5, 0.75, 1};
324 @Override
325 public double nextDouble() {
326 return values[i++];
327 }
328 };
329 final double x = Double.MAX_VALUE;
330 Assertions.assertEquals(-x, rng.nextDouble(-x, x));
331 Assertions.assertEquals(-x / 2, rng.nextDouble(-x, x), Math.ulp(x / 2));
332 Assertions.assertEquals(0, rng.nextDouble(-x, x));
333 Assertions.assertEquals(x / 2, rng.nextDouble(-x, x), Math.ulp(x / 2));
334 Assertions.assertEquals(Math.nextDown(x), rng.nextDouble(-x, x));
335 }
336
337 @Test
338 void testNextDoubleOriginBoundRounding() {
339
340 final double v = Math.nextDown(1.0);
341 final UniformRandomProvider rng = new DummyGenerator() {
342 @Override
343 public double nextDouble() {
344 return v;
345 }
346 };
347 double origin;
348 double bound;
349
350
351 origin = 3.5;
352 bound = 4.5;
353 Assertions.assertEquals(bound, origin + v * (bound - origin), "Expected result to be rounded up");
354 Assertions.assertEquals(Math.nextDown(bound), rng.nextDouble(origin, bound), "Result was not correctly rounded down");
355
356
357
358 origin = -Double.MIN_NORMAL / 2;
359 bound = Double.MIN_NORMAL / 2;
360 Assertions.assertEquals(bound, origin * (1 - v) + v * bound, "Expected result to be rounded up");
361 Assertions.assertEquals(Math.nextDown(bound), rng.nextDouble(origin, bound), "Result was not correctly rounded down");
362 }
363
364 @Test
365 void testNextBooleanIsIntSignBit() {
366 final int size = 10;
367 final int[] values = ThreadLocalRandom.current().ints(size).toArray();
368 final UniformRandomProvider rng = new DummyGenerator() {
369 private int i;
370 @Override
371 public int nextInt() {
372 return values[i++];
373 }
374 };
375 for (int i = 0; i < size; i++) {
376 Assertions.assertEquals(values[i] < 0, rng.nextBoolean());
377 }
378 }
379
380
381
382
383
384 @ParameterizedTest
385 @ValueSource(longs = {263784628482L, -2563472, -2367482842368L})
386 void testNextIntMonobit(long seed) {
387 final UniformRandomProvider rng = createRNG(seed);
388 int bitCount = 0;
389 final int n = 100;
390 for (int i = 0; i < n; i++) {
391 bitCount += Integer.bitCount(rng.nextInt());
392 }
393 final int numberOfBits = n * Integer.SIZE;
394 assertMonobit(bitCount, numberOfBits);
395 }
396
397 @ParameterizedTest
398 @ValueSource(longs = {263784628L, 253674, -23568426834L})
399 void testNextDoubleMonobit(long seed) {
400 final UniformRandomProvider rng = createRNG(seed);
401 int bitCount = 0;
402 final int n = 100;
403 for (int i = 0; i < n; i++) {
404 bitCount += Long.bitCount((long) (rng.nextDouble() * (1L << 53)));
405 }
406 final int numberOfBits = n * 53;
407 assertMonobit(bitCount, numberOfBits);
408 }
409
410 @ParameterizedTest
411 @ValueSource(longs = {265342L, -234232, -672384648284L})
412 void testNextBooleanMonobit(long seed) {
413 final UniformRandomProvider rng = createRNG(seed);
414 int bitCount = 0;
415 final int n = 1000;
416 for (int i = 0; i < n; i++) {
417 if (rng.nextBoolean()) {
418 bitCount++;
419 }
420 }
421 final int numberOfBits = n;
422 assertMonobit(bitCount, numberOfBits);
423 }
424
425 @ParameterizedTest
426 @ValueSource(longs = {-1526731, 263846, 4545})
427 void testNextFloatMonobit(long seed) {
428 final UniformRandomProvider rng = createRNG(seed);
429 int bitCount = 0;
430 final int n = 100;
431 for (int i = 0; i < n; i++) {
432 bitCount += Integer.bitCount((int) (rng.nextFloat() * (1 << 24)));
433 }
434 final int numberOfBits = n * 24;
435 assertMonobit(bitCount, numberOfBits);
436 }
437
438 @ParameterizedTest
439 @CsvSource({
440 "-2638468223894, 16",
441 "235647674, 17",
442 "-928738475, 23",
443 })
444 void testNextBytesMonobit(long seed, int range) {
445 final UniformRandomProvider rng = createRNG(seed);
446 final byte[] bytes = new byte[range];
447 int bitCount = 0;
448 final int n = 100;
449 for (int i = 0; i < n; i++) {
450 rng.nextBytes(bytes);
451 for (final byte b1 : bytes) {
452 bitCount += Integer.bitCount(b1 & 0xff);
453 }
454 }
455 final int numberOfBits = n * Byte.SIZE * range;
456 assertMonobit(bitCount, numberOfBits);
457 }
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475 private static void assertMonobit(int bitCount, int numberOfBits) {
476
477 final double sum = 2.0 * bitCount - numberOfBits;
478
479
480
481
482 final double absSum = Math.abs(sum);
483 final double max = Math.sqrt(numberOfBits) * 2.5758293035489004;
484 Assertions.assertTrue(absSum <= max,
485 () -> "Walked too far astray: " + absSum + " > " + max +
486 " (test will fail randomly about 1 in 100 times)");
487 }
488
489
490
491 @ParameterizedTest
492 @CsvSource({
493 "263746283, 23, 0, 23",
494 "-126536861889, 16, 0, 16",
495 "617868181124, 1234, 567, 89",
496 "-56788, 512, 0, 233",
497 "6787535424, 512, 233, 279",
498 })
499 void testNextBytesUniform(long seed,
500 int length, int start, int size) {
501 final UniformRandomProvider rng = createRNG(seed);
502 final byte[] buffer = new byte[length];
503
504 final Runnable nextMethod = start == 0 && size == length ?
505 () -> rng.nextBytes(buffer) :
506 () -> rng.nextBytes(buffer, start, size);
507
508 final int last = start + size;
509 Assertions.assertTrue(isUniformNextBytes(buffer, start, last, nextMethod),
510 "Expected uniform bytes");
511
512
513 for (int i = 0; i < start; i++) {
514 Assertions.assertEquals(0, buffer[i], () -> "Filled < start: " + start);
515 }
516 for (int i = last; i < length; i++) {
517 Assertions.assertEquals(0, buffer[i], () -> "Filled >= last: " + last);
518 }
519 }
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535 private static boolean isUniformNextBytes(byte[] buffer,
536 int first,
537 int last,
538 Runnable nextMethod) {
539 final int sampleSize = 10000;
540
541
542 final int byteRange = 256;
543
544
545 final double chi2CriticalValue = 310.45738821990585;
546
547
548 final long[] observed = new long[byteRange];
549 final double expected = (double) sampleSize * (last - first) / byteRange;
550
551 for (int k = 0; k < sampleSize; k++) {
552 nextMethod.run();
553
554 for (int i = first; i < last; i++) {
555
556 ++observed[buffer[i] & 0xff];
557 }
558 }
559
560
561 double chi2 = 0;
562 for (int k = 0; k < byteRange; k++) {
563 final double diff = observed[k] - expected;
564 chi2 += diff * diff / expected;
565 }
566
567
568 return chi2 <= chi2CriticalValue;
569 }
570
571
572
573 @ParameterizedTest
574 @CsvSource({
575
576 "2673846826, 0, 10",
577 "-23658268, 0, 12",
578 "263478624, 0, 31",
579 "1278332, 0, 32",
580 "99734765, 0, 2016128993",
581 "-63485384, 0, 1834691456",
582 "3876457638, 0, 869657561",
583 "-126784782, 0, 1570504788",
584 "2637846, 0, 2147483647",
585
586 "2634682, 567576, 567586",
587 "-56757798989, -1000, -100",
588 "-97324785, -54656, 12",
589 "23423235, -526783468, 257",
590
591 "-2634682, -1073741824, 1073741824",
592 "6786868132, -1263842626, 1237846372",
593 "-263846723, -368268, 2147483647",
594 "7352352, -2147483648, 61523457",
595 })
596 void testNextIntUniform(long seed, int origin, int bound) {
597 final UniformRandomProvider rng = createRNG(seed);
598 final LongSupplier nextMethod = origin == 0 ?
599 () -> rng.nextInt(bound) :
600 () -> rng.nextInt(origin, bound);
601 checkNextInRange("nextInt", origin, bound, nextMethod);
602 }
603
604 @ParameterizedTest
605 @CsvSource({
606
607 "2673846826, 0, 11",
608 "-23658268, 0, 19",
609 "263478624, 0, 31",
610 "1278332, 0, 32",
611 "99734765, 0, 2326378468282368421",
612 "-63485384, 0, 4872347624242243222",
613 "3876457638, 0, 6263784682638866843",
614 "-126784782, 0, 7256684297832668332",
615 "2637846, 0, 9223372036854775807",
616
617 "2634682, 567576, 567586",
618 "-56757798989, -1000, -100",
619 "-97324785, -54656, 12",
620 "23423235, -526783468, 257",
621
622 "-2634682, -4611686018427387904, 4611686018427387904",
623 "6786868132, -4962836478223688590, 6723648246224929947",
624 "-263846723, -368268, 9223372036854775807",
625 "7352352, -9223372036854775808, 61523457",
626 })
627 void testNextLongUniform(long seed, long origin, long bound) {
628 final UniformRandomProvider rng = createRNG(seed);
629 final LongSupplier nextMethod = origin == 0 ?
630 () -> rng.nextLong(bound) :
631 () -> rng.nextLong(origin, bound);
632 checkNextInRange("nextLong", origin, bound, nextMethod);
633 }
634
635 @ParameterizedTest
636 @CsvSource({
637
638
639
640
641
642 "2673846826, 0, 11",
643 "-23658268, 0, 19",
644 "263478624, 0, 31",
645 "1278332, 0, 32",
646 "99734765, 0, 1234",
647 "-63485384, 0, 578",
648 "3876457638, 0, 10000",
649 "-126784782, 0, 2983423",
650 "2637846, 0, 16777216",
651
652 "2634682, 567576, 567586",
653 "-56757798989, -1000, -100",
654 "-97324785, -54656, 12",
655 "23423235, -526783468, 257",
656 "-2634682, -688689797, -516827",
657 "6786868132, -67, 67",
658 "-263846723, -5678, 42",
659 "7352352, 678687, 61523457",
660 })
661 void testNextFloatUniform(long seed, float origin, float bound) {
662 Assertions.assertEquals((long) origin, origin, "origin");
663 Assertions.assertEquals((long) bound, bound, "bound");
664 final UniformRandomProvider rng = createRNG(seed);
665
666
667 final LongSupplier nextMethod = origin == 0 ?
668 () -> (long) rng.nextFloat(bound) :
669 () -> (long) Math.floor(rng.nextFloat(origin, bound));
670 checkNextInRange("nextFloat", (long) origin, (long) bound, nextMethod);
671 }
672
673
674 @ParameterizedTest
675 @CsvSource({
676
677
678
679
680
681 "2673846826, 0, 11",
682 "-23658268, 0, 19",
683 "263478624, 0, 31",
684 "1278332, 0, 32",
685 "99734765, 0, 1234",
686 "-63485384, 0, 578",
687 "3876457638, 0, 10000",
688 "-126784782, 0, 2983423",
689 "2637846, 0, 9007199254740992",
690
691 "2634682, 567576, 567586",
692 "-56757798989, -1000, -100",
693 "-97324785, -54656, 12",
694 "23423235, -526783468, 257",
695 "-2634682, -688689797, -516827",
696 "6786868132, -67, 67",
697 "-263846723, -5678, 42",
698 "7352352, 678687, 61523457",
699 })
700 void testNextDoubleUniform(long seed, double origin, double bound) {
701 Assertions.assertEquals((long) origin, origin, "origin");
702 Assertions.assertEquals((long) bound, bound, "bound");
703 final UniformRandomProvider rng = createRNG(seed);
704
705
706 final LongSupplier nextMethod = origin == 0 ?
707 () -> (long) rng.nextDouble(bound) :
708 () -> (long) Math.floor(rng.nextDouble(origin, bound));
709 checkNextInRange("nextDouble", (long) origin, (long) bound, nextMethod);
710 }
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725 private static void checkNextInRange(String method,
726 long origin,
727 long bound,
728 LongSupplier nextMethod) {
729
730
731 final int numTests = 500;
732 final int numBins = 10;
733
734
735 final long[] binUpperBounds = new long[numBins];
736
737 final BigDecimal range = BigDecimal.valueOf(bound)
738 .subtract(BigDecimal.valueOf(origin));
739 final double step = range.divide(BigDecimal.TEN).doubleValue();
740 for (int k = 1; k < numBins; k++) {
741 binUpperBounds[k - 1] = origin + (long) (k * step);
742 }
743
744 binUpperBounds[numBins - 1] = bound;
745
746
747 final double[] expected = new double[numBins];
748 long previousUpperBound = origin;
749 final double scale = SAMPLE_SIZE_BD.divide(range, MathContext.DECIMAL128).doubleValue();
750 double sum = 0;
751 for (int k = 0; k < numBins; k++) {
752 final long binWidth = binUpperBounds[k] - previousUpperBound;
753 expected[k] = scale * binWidth;
754 sum += expected[k];
755 previousUpperBound = binUpperBounds[k];
756 }
757 Assertions.assertEquals(SAMPLE_SIZE, sum, SAMPLE_SIZE * RELATIVE_ERROR, "Invalid expected frequencies");
758
759 final int[] observed = new int[numBins];
760
761
762 final double chi2CriticalValue = 21.665994333461924;
763
764
765 final List<Double> failedStat = new ArrayList<>();
766 try {
767 final int lastDecileIndex = numBins - 1;
768 for (int i = 0; i < numTests; i++) {
769 Arrays.fill(observed, 0);
770 SAMPLE: for (int j = 0; j < SAMPLE_SIZE; j++) {
771 final long value = nextMethod.getAsLong();
772 if (value < origin) {
773 Assertions.fail(String.format("Sample %d not within bound [%d, %d)",
774 value, origin, bound));
775 }
776
777 for (int k = 0; k < lastDecileIndex; k++) {
778 if (value < binUpperBounds[k]) {
779 ++observed[k];
780 continue SAMPLE;
781 }
782 }
783 if (value >= bound) {
784 Assertions.fail(String.format("Sample %d not within bound [%d, %d)",
785 value, origin, bound));
786 }
787 ++observed[lastDecileIndex];
788 }
789
790
791 double chi2 = 0;
792 for (int k = 0; k < numBins; k++) {
793 final double diff = observed[k] - expected[k];
794 chi2 += diff * diff / expected[k];
795 }
796
797
798 if (chi2 > chi2CriticalValue) {
799 failedStat.add(chi2);
800 }
801 }
802 } catch (final Exception e) {
803
804 throw new RuntimeException("Unexpected", e);
805 }
806
807
808
809
810
811
812
813
814
815 if (failedStat.size() > 11) {
816 Assertions.fail(String.format(
817 "%s: Too many failures for n = %d, sample size = %d " +
818 "(%d out of %d tests failed, chi2 > %.3f=%s)",
819 method, bound, SAMPLE_SIZE, failedStat.size(), numTests, chi2CriticalValue,
820 failedStat.stream().map(d -> String.format("%.3f", d))
821 .collect(Collectors.joining(", ", "[", "]"))));
822 }
823 }
824 }