Skip to content

Commit c8cff1b

Browse files
rgiuliettijddarcy
authored andcommitted
8202449: overflow handling in Random.doubles
Reviewed-by: darcy
1 parent c15e10f commit c8cff1b

File tree

5 files changed

+82
-71
lines changed

5 files changed

+82
-71
lines changed

src/java.base/share/classes/java/util/Random.java

+8-33
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,8 @@ public String toString() {
256256
private static final long addend = 0xBL;
257257
private static final long mask = (1L << 48) - 1;
258258

259-
private static final double DOUBLE_UNIT = 0x1.0p-53; // 1.0 / (1L << 53)
259+
private static final double DOUBLE_UNIT = 0x1.0p-53; // 1.0 / (1L << Double.PRECISION)
260+
private static final float FLOAT_UNIT = 0x1.0p-24f; // 1.0f / (1 << Float.PRECISION)
260261

261262
/**
262263
* Creates a new random number generator. This constructor sets
@@ -598,12 +599,12 @@ public boolean nextBoolean() {
598599
* low-order bit of the significand would be 0 than that it would be 1.]
599600
*
600601
* @return the next pseudorandom, uniformly distributed {@code float}
601-
* value between {@code 0.0} and {@code 1.0} from this
602+
* value between {@code 0.0f} and {@code 1.0f} from this
602603
* random number generator's sequence
603604
*/
604605
@Override
605606
public float nextFloat() {
606-
return next(24) / ((float)(1 << 24));
607+
return next(Float.PRECISION) * FLOAT_UNIT;
607608
}
608609

609610
/**
@@ -644,7 +645,7 @@ public float nextFloat() {
644645
*/
645646
@Override
646647
public double nextDouble() {
647-
return (((long)(next(26)) << 27) + next(27)) * DOUBLE_UNIT;
648+
return (((long)(next(Double.PRECISION - 27)) << 27) + next(27)) * DOUBLE_UNIT;
648649
}
649650

650651
private double nextNextGaussian;
@@ -1070,26 +1071,12 @@ public DoubleStream doubles() {
10701071
* pseudorandom {@code double} values, each conforming to the given origin
10711072
* (inclusive) and bound (exclusive).
10721073
*
1073-
* <p>A pseudorandom {@code double} value is generated as if it's the result
1074-
* of calling the following method with the origin and bound:
1075-
* <pre> {@code
1076-
* double nextDouble(double origin, double bound) {
1077-
* double r = nextDouble();
1078-
* r = r * (bound - origin) + origin;
1079-
* if (r >= bound) // correct for rounding
1080-
* r = Math.nextDown(bound);
1081-
* return r;
1082-
* }}</pre>
1083-
*
10841074
* @param streamSize the number of values to generate
10851075
* @param randomNumberOrigin the origin (inclusive) of each random value
10861076
* @param randomNumberBound the bound (exclusive) of each random value
10871077
* @return a stream of pseudorandom {@code double} values,
10881078
* each with the given origin (inclusive) and bound (exclusive)
1089-
* @throws IllegalArgumentException if {@code streamSize} is less than zero,
1090-
* or {@code randomNumberOrigin} is not finite,
1091-
* or {@code randomNumberBound} is not finite, or {@code randomNumberOrigin}
1092-
* is greater than or equal to {@code randomNumberBound}
1079+
* @throws IllegalArgumentException {@inheritDoc}
10931080
* @since 1.8
10941081
*/
10951082
@Override
@@ -1101,27 +1088,15 @@ public DoubleStream doubles(long streamSize, double randomNumberOrigin, double r
11011088
* Returns an effectively unlimited stream of pseudorandom {@code
11021089
* double} values, each conforming to the given origin (inclusive) and bound
11031090
* (exclusive).
1104-
*
1105-
* <p>A pseudorandom {@code double} value is generated as if it's the result
1106-
* of calling the following method with the origin and bound:
1107-
* <pre> {@code
1108-
* double nextDouble(double origin, double bound) {
1109-
* double r = nextDouble();
1110-
* r = r * (bound - origin) + origin;
1111-
* if (r >= bound) // correct for rounding
1112-
* r = Math.nextDown(bound);
1113-
* return r;
1114-
* }}</pre>
1115-
*
1091+
11161092
* @implNote This method is implemented to be equivalent to {@code
11171093
* doubles(Long.MAX_VALUE, randomNumberOrigin, randomNumberBound)}.
11181094
*
11191095
* @param randomNumberOrigin the origin (inclusive) of each random value
11201096
* @param randomNumberBound the bound (exclusive) of each random value
11211097
* @return a stream of pseudorandom {@code double} values,
11221098
* each with the given origin (inclusive) and bound (exclusive)
1123-
* @throws IllegalArgumentException if {@code randomNumberOrigin}
1124-
* is greater than or equal to {@code randomNumberBound}
1099+
* @throws IllegalArgumentException {@inheritDoc}
11251100
* @since 1.8
11261101
*/
11271102
@Override

src/java.base/share/classes/java/util/SplittableRandom.java

+3-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2013, 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2013, 2022, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -24,7 +24,6 @@
2424
*/
2525
package java.util;
2626

27-
import java.math.BigInteger;
2827
import java.util.concurrent.atomic.AtomicLong;
2928
import java.util.random.RandomGenerator;
3029
import java.util.random.RandomGenerator.SplittableGenerator;
@@ -551,9 +550,7 @@ public DoubleStream doubles() {
551550
* @param randomNumberBound the bound (exclusive) of each random value
552551
* @return a stream of pseudorandom {@code double} values,
553552
* each with the given origin (inclusive) and bound (exclusive)
554-
* @throws IllegalArgumentException if {@code streamSize} is
555-
* less than zero, or {@code randomNumberOrigin}
556-
* is greater than or equal to {@code randomNumberBound}
553+
* @throws IllegalArgumentException {@inheritDoc}
557554
*/
558555
@Override
559556
public DoubleStream doubles(long streamSize, double randomNumberOrigin, double randomNumberBound) {
@@ -572,8 +569,7 @@ public DoubleStream doubles(long streamSize, double randomNumberOrigin, double r
572569
* @param randomNumberBound the bound (exclusive) of each random value
573570
* @return a stream of pseudorandom {@code double} values,
574571
* each with the given origin (inclusive) and bound (exclusive)
575-
* @throws IllegalArgumentException if {@code randomNumberOrigin}
576-
* is greater than or equal to {@code randomNumberBound}
572+
* @throws IllegalArgumentException {@inheritDoc}
577573
*/
578574
@Override
579575
public DoubleStream doubles(double randomNumberOrigin, double randomNumberBound) {

src/java.base/share/classes/java/util/random/RandomGenerator.java

+3-9
Original file line numberDiff line numberDiff line change
@@ -540,10 +540,7 @@ default float nextFloat(float bound) {
540540
*
541541
* @throws IllegalArgumentException if {@code origin} is not finite,
542542
* or {@code bound} is not finite, or {@code origin}
543-
* is greater than or equal to {@code bound}, or
544-
* the difference between {@code bound} and {@code origin}
545-
* is so large that it cannot be represented as a finite
546-
* {@code float} value
543+
* is greater than or equal to {@code bound}
547544
*
548545
* @implSpec The default implementation verifies that the {@code origin}
549546
* and {@code bound} are valid then invokes {@code nextFloat()}
@@ -606,11 +603,8 @@ default double nextDouble(double bound) {
606603
*
607604
* @throws IllegalArgumentException if {@code origin} is not finite,
608605
* or {@code bound} is not finite, or {@code origin}
609-
* is greater than or equal to {@code bound}, or
610-
* the difference between {@code bound} and {@code origin}
611-
* is so large that it cannot be represented as a finite
612-
* {@code double} value
613-
*
606+
* is greater than or equal to {@code bound}
607+
614608
* @implSpec The default implementation verifies that the {@code origin}
615609
* and {@code bound} are valid, then invokes {@code nextDouble()}
616610
* scaling and translating the result to fit between {@code origin}

src/java.base/share/classes/jdk/internal/util/random/RandomSupport.java

+37-21
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ public static void checkStreamSize(long streamSize) {
145145
* @throws IllegalArgumentException if {@code bound} fails to be positive and finite
146146
*/
147147
public static void checkBound(float bound) {
148-
if (!(bound > 0.0 && bound < Float.POSITIVE_INFINITY)) {
148+
if (!(0.0f < bound && bound < Float.POSITIVE_INFINITY)) {
149149
throw new IllegalArgumentException(BAD_FLOATING_BOUND);
150150
}
151151
}
@@ -158,7 +158,7 @@ public static void checkBound(float bound) {
158158
* @throws IllegalArgumentException if {@code bound} fails to be positive and finite
159159
*/
160160
public static void checkBound(double bound) {
161-
if (!(bound > 0.0 && bound < Double.POSITIVE_INFINITY)) {
161+
if (!(0.0 < bound && bound < Double.POSITIVE_INFINITY)) {
162162
throw new IllegalArgumentException(BAD_FLOATING_BOUND);
163163
}
164164
}
@@ -195,11 +195,13 @@ public static void checkBound(long bound) {
195195
* @param origin the least value (inclusive) in the range
196196
* @param bound the upper bound (exclusive) of the range
197197
*
198-
* @throws IllegalArgumentException if {@code origin} is not finite, {@code bound} is not finite,
199-
* or {@code bound - origin} is not finite
198+
* @throws IllegalArgumentException if {@code origin} is not finite,
199+
* or {@code bound} is not finite, or {@code origin}
200+
* is greater than or equal to {@code bound}
200201
*/
201202
public static void checkRange(float origin, float bound) {
202-
if (!(origin < bound && (bound - origin) < Float.POSITIVE_INFINITY)) {
203+
if (!(Float.NEGATIVE_INFINITY < origin && origin < bound &&
204+
bound < Float.POSITIVE_INFINITY)) {
203205
throw new IllegalArgumentException(BAD_RANGE);
204206
}
205207
}
@@ -210,11 +212,13 @@ public static void checkRange(float origin, float bound) {
210212
* @param origin the least value (inclusive) in the range
211213
* @param bound the upper bound (exclusive) of the range
212214
*
213-
* @throws IllegalArgumentException if {@code origin} is not finite, {@code bound} is not finite,
214-
* or {@code bound - origin} is not finite
215+
* @throws IllegalArgumentException if {@code origin} is not finite,
216+
* or {@code bound} is not finite, or {@code origin}
217+
* is greater than or equal to {@code bound}
215218
*/
216219
public static void checkRange(double origin, double bound) {
217-
if (!(origin < bound && (bound - origin) < Double.POSITIVE_INFINITY)) {
220+
if (!(Double.NEGATIVE_INFINITY < origin && origin < bound &&
221+
bound < Double.POSITIVE_INFINITY)) {
218222
throw new IllegalArgumentException(BAD_RANGE);
219223
}
220224
}
@@ -343,8 +347,8 @@ public static int[] convertSeedBytesToInts(byte[] seed, int n, int z) {
343347
* This is the form of {@link RandomGenerator#nextLong() nextLong}() used by
344348
* a {@link LongStream} {@link Spliterator} and by the public method
345349
* {@link RandomGenerator#nextLong(long, long) nextLong}(origin, bound). If
346-
* {@code origin} is greater than {@code bound}, then this method simply
347-
* calls the unbounded version of
350+
* {@code origin} is greater than or equal to {@code bound},
351+
* then this method simply calls the unbounded version of
348352
* {@link RandomGenerator#nextLong() nextLong}(), choosing pseudorandomly
349353
* from among all 2<sup>64</sup> possible {@code long} values}, and
350354
* otherwise uses one or more calls to
@@ -508,8 +512,8 @@ public static long boundedNextLong(RandomGenerator rng, long bound) {
508512
* This is the form of {@link RandomGenerator#nextInt() nextInt}() used by
509513
* an {@link IntStream} {@link Spliterator} and by the public method
510514
* {@link RandomGenerator#nextInt(int, int) nextInt}(origin, bound). If
511-
* {@code origin} is greater than {@code bound}, then this method simply
512-
* calls the unbounded version of
515+
* {@code origin} is greater than or equal to {@code bound},
516+
* then this method simply calls the unbounded version of
513517
* {@link RandomGenerator#nextInt() nextInt}(), choosing pseudorandomly
514518
* from among all 2<sup>64</sup> possible {@code int} values}, and otherwise
515519
* uses one or more calls to {@link RandomGenerator#nextInt() nextInt}() to
@@ -604,8 +608,8 @@ public static int boundedNextInt(RandomGenerator rng, int bound) {
604608
* used by a {@link DoubleStream} {@link Spliterator} and by the public
605609
* method
606610
* {@link RandomGenerator#nextDouble(double, double) nextDouble}(origin, bound).
607-
* If {@code origin} is greater than {@code bound}, then this method simply
608-
* calls the unbounded version of
611+
* {@code origin} is greater than or equal to {@code bound},
612+
* then this method simply calls the unbounded version of
609613
* {@link RandomGenerator#nextDouble() nextDouble}(), and otherwise scales
610614
* and translates the result of a call to
611615
* {@link RandomGenerator#nextDouble() nextDouble}() so that it lies between
@@ -643,9 +647,15 @@ public static int boundedNextInt(RandomGenerator rng, int bound) {
643647
public static double boundedNextDouble(RandomGenerator rng, double origin, double bound) {
644648
double r = rng.nextDouble();
645649
if (origin < bound) {
646-
r = r * (bound - origin) + origin;
650+
if (bound - origin < Double.POSITIVE_INFINITY) {
651+
r = r * (bound - origin) + origin;
652+
} else {
653+
/* avoids overflow at the cost of 3 more multiplications */
654+
double halfOrigin = 0.5 * origin;
655+
r = (r * (0.5 * bound - halfOrigin) + halfOrigin) * 2.0;
656+
}
647657
if (r >= bound) // may need to correct a rounding problem
648-
r = Math.nextAfter(bound, origin);
658+
r = Math.nextDown(bound);
649659
}
650660
return r;
651661
}
@@ -686,8 +696,8 @@ public static double boundedNextDouble(RandomGenerator rng, double bound) {
686696
* by a {@link Stream<Float>} {@link Spliterator} (if there were any) and by
687697
* the public method
688698
* {@link RandomGenerator#nextFloat(float, float) nextFloat}(origin, bound).
689-
* If {@code origin} is greater than {@code bound}, then this method simply
690-
* calls the unbounded version of
699+
* {@code origin} is greater than or equal to {@code bound},
700+
* then this method simply calls the unbounded version of
691701
* {@link RandomGenerator#nextFloat() nextFloat}(), and otherwise scales and
692702
* translates the result of a call to
693703
* {@link RandomGenerator#nextFloat() nextFloat}() so that it lies between
@@ -715,9 +725,15 @@ public static double boundedNextDouble(RandomGenerator rng, double bound) {
715725
public static float boundedNextFloat(RandomGenerator rng, float origin, float bound) {
716726
float r = rng.nextFloat();
717727
if (origin < bound) {
718-
r = r * (bound - origin) + origin;
728+
if (bound - origin < Float.POSITIVE_INFINITY) {
729+
r = r * (bound - origin) + origin;
730+
} else {
731+
/* avoids overflow at the cost of 3 more multiplications */
732+
float halfOrigin = 0.5f * origin;
733+
r = (r * (0.5f * bound - halfOrigin) + halfOrigin) * 2.0f;
734+
}
719735
if (r >= bound) // may need to correct a rounding problem
720-
r = Float.intBitsToFloat(Float.floatToIntBits(bound) - 1);
736+
r = Math.nextDown(r);
721737
}
722738
return r;
723739
}
@@ -749,7 +765,7 @@ public static float boundedNextFloat(RandomGenerator rng, float bound) {
749765
float r = rng.nextFloat();
750766
r = r * bound;
751767
if (r >= bound) // may need to correct a rounding problem
752-
r = Float.intBitsToFloat(Float.floatToIntBits(bound) - 1);
768+
r = Math.nextDown(r);
753769
return r;
754770
}
755771

test/jdk/java/util/Random/RandomNextDoubleBoundary.java

+31-1
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,24 @@
2424
/*
2525
* @test
2626
* @summary Verify nextDouble stays within range
27-
* @bug 8280550 8280950 8281183
27+
* @bug 8280550 8280950 8281183 8202449
28+
*
29+
* @key randomness
30+
* @library /test/lib
31+
* @build jdk.test.lib.RandomFactory
32+
* @run main RandomNextDoubleBoundary
33+
2834
*/
2935

3036
import java.util.SplittableRandom;
3137
import java.util.random.RandomGenerator;
38+
import jdk.test.lib.RandomFactory;
3239

3340
public class RandomNextDoubleBoundary {
3441
public static void main(String... args) {
3542
negativeBounds();
3643
positiveBounds();
44+
nextDoubleHugeRange();
3745
}
3846

3947
private static void negativeBounds() {
@@ -86,9 +94,31 @@ public long nextLong() {
8694
}
8795
}
8896

97+
public static void nextDoubleHugeRange() {
98+
var random = RandomFactory.getRandom();
99+
var n = 100_000;
100+
101+
var origin = -(3.0 / 4.0) * Double.MAX_VALUE;
102+
var bound = (3.0 / 4.0) * Double.MAX_VALUE;
103+
assertTrue(bound - origin == Double.POSITIVE_INFINITY);
104+
105+
/* all are within [origin, bound) */
106+
assertTrue(random.doubles(n, origin, bound)
107+
.allMatch(d -> origin <= d && d < bound));
108+
109+
/* some are near the origin */
110+
assertTrue(random.doubles(n, origin, bound)
111+
.anyMatch(d -> d < (15.0 / 16.0) * origin));
112+
113+
/* some are near the bound */
114+
assertTrue(random.doubles(n, origin, bound)
115+
.anyMatch(d -> d > (15.0 / 16.0) * bound));
116+
}
117+
89118
public static void assertTrue(boolean condition) {
90119
if (!condition) {
91120
throw new AssertionError();
92121
}
93122
}
123+
94124
}

0 commit comments

Comments
 (0)