This is an automated email from the ASF dual-hosted git repository. garydgregory pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-lang.git
commit 65f00d83ca2d1ad5743f838a58eab01a3630e498 Author: Gary Gregory <[email protected]> AuthorDate: Thu Oct 15 14:29:49 2026 -0400 Keep RandomUtils's randomDouble() and randomFloat() below the exclusive upper bound (#1817) Document RandomUtils boundary behavior and extend regression tests Add deterministic float and double tests for exclusive upper bounds, subnormal ranges, maximum finite values, and positive infinity. Document equal-bound and infinite-upper-bound behavior, and tighten existing maximum-range assertions to enforce exclusive bounds. --- .../java/org/apache/commons/lang3/RandomUtils.java | 13 +++++ .../org/apache/commons/lang3/RandomUtilsTest.java | 58 ++++++++++++++++++++-- 2 files changed, 67 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/apache/commons/lang3/RandomUtils.java b/src/main/java/org/apache/commons/lang3/RandomUtils.java index f3f5b4e21..ededb6922 100644 --- a/src/main/java/org/apache/commons/lang3/RandomUtils.java +++ b/src/main/java/org/apache/commons/lang3/RandomUtils.java @@ -143,6 +143,10 @@ public static double nextDouble() { /** * Generates a random double within the specified range. + * <p> + * If the bounds are equal, returns the common bound. If {@code startInclusive} is finite and {@code endExclusive} is positive infinity, returns + * {@link Double#MAX_VALUE}. + * </p> * * @param startInclusive The smallest value that can be returned, must be non-negative. * @param endExclusive The upper bound (not included). @@ -171,6 +175,9 @@ public static float nextFloat() { /** * Generates a random float within the specified range. * + * <p>If the bounds are equal, returns the common bound. If {@code startInclusive} is finite and {@code endExclusive} is positive infinity, + * returns {@link Float#MAX_VALUE}.</p> + * * @param startInclusive The smallest value that can be returned, must be non-negative. * @param endExclusive The upper bound (not included). * @throws IllegalArgumentException Thrown if {@code startInclusive > endExclusive} or if {@code startInclusive} is negative. @@ -333,6 +340,9 @@ public double randomDouble() { /** * Generates a random double within the specified range. * + * <p>If the bounds are equal, returns the common bound. If {@code startInclusive} is finite and {@code endExclusive} is positive infinity, + * returns {@link Double#MAX_VALUE}.</p> + * * @param startInclusive The smallest value that can be returned, must be non-negative. * @param endExclusive The upper bound (not included). * @throws IllegalArgumentException Thrown if {@code startInclusive > endExclusive} or if {@code startInclusive} is negative. @@ -364,6 +374,9 @@ public float randomFloat() { /** * Generates a random float within the specified range. * + * <p>If the bounds are equal, returns the common bound. If {@code startInclusive} is finite and {@code endExclusive} is positive infinity, + * returns {@link Float#MAX_VALUE}.</p> + * * @param startInclusive The smallest value that can be returned, must be non-negative. * @param endExclusive The upper bound (not included). * @throws IllegalArgumentException Thrown if {@code startInclusive > endExclusive} or if {@code startInclusive} is negative. diff --git a/src/test/java/org/apache/commons/lang3/RandomUtilsTest.java b/src/test/java/org/apache/commons/lang3/RandomUtilsTest.java index 46a5e4c83..9bffbe1e8 100644 --- a/src/test/java/org/apache/commons/lang3/RandomUtilsTest.java +++ b/src/test/java/org/apache/commons/lang3/RandomUtilsTest.java @@ -23,11 +23,13 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.Random; import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; /** * Tests for {@link RandomUtils} @@ -44,6 +46,28 @@ class RandomUtilsTest extends AbstractLangTest { */ private static final int EXCLUSIVE_BOUND_TRIALS = 1000; + private static RandomUtils fixedRandom(final double value) { + final Random random = new Random(0) { + private static final long serialVersionUID = 1L; + + @Override + public double nextDouble() { + return value; + } + + @Override + public float nextFloat() { + return (float) value; + } + }; + return new RandomUtils() { + @Override + Random random() { + return random; + } + }; + } + static Stream<RandomUtils> randomProvider() { return Stream.of(RandomUtils.secure(), RandomUtils.secureStrong(), RandomUtils.insecure()); } @@ -75,14 +99,14 @@ void testConstructor() { @Test void testExtremeRangeDouble() { final double result = RandomUtils.nextDouble(0, Double.MAX_VALUE); - assertTrue(result >= 0 && result <= Double.MAX_VALUE); // TODO: should be <max? + assertTrue(result >= 0 && result < Double.MAX_VALUE); } @ParameterizedTest @MethodSource("randomProvider") void testExtremeRangeDouble(final RandomUtils ru) { final double result = ru.randomDouble(0, Double.MAX_VALUE); - assertTrue(result >= 0 && result <= Double.MAX_VALUE); // TODO: should be <max? + assertTrue(result >= 0 && result < Double.MAX_VALUE); } /** @@ -91,7 +115,7 @@ void testExtremeRangeDouble(final RandomUtils ru) { @Test void testExtremeRangeFloat() { final float result = RandomUtils.nextFloat(0, Float.MAX_VALUE); - assertTrue(result >= 0f && result <= Float.MAX_VALUE); // TODO: should be <max? + assertTrue(result >= 0f && result < Float.MAX_VALUE); } /** @@ -101,7 +125,7 @@ void testExtremeRangeFloat() { @MethodSource("randomProvider") void testExtremeRangeFloat(final RandomUtils ru) { final float result = ru.randomFloat(0, Float.MAX_VALUE); - assertTrue(result >= 0f && result <= Float.MAX_VALUE); // TODO: should be <max? + assertTrue(result >= 0f && result < Float.MAX_VALUE); } /** @@ -593,6 +617,19 @@ void testRandomDoubleExclusiveUpperBound(final RandomUtils ru) { } } + @ParameterizedTest + @ValueSource(doubles = {0, 0.5, 0.75, 0.9999999999999999}) + void testRandomDoubleFixedBoundaries(final double value) { + final RandomUtils ru = fixedRandom(value); + assertEquals(10d, ru.randomDouble(10d, Math.nextUp(10d))); + assertEquals(0d, ru.randomDouble(0d, Double.MIN_VALUE)); + assertEquals(Double.MIN_VALUE, ru.randomDouble(Double.MIN_VALUE, 2 * Double.MIN_VALUE)); + assertEquals(Math.nextDown(Double.MAX_VALUE), ru.randomDouble(Math.nextDown(Double.MAX_VALUE), Double.MAX_VALUE)); + assertEquals(value, ru.randomDouble(0d, 1d)); + assertEquals(Double.MAX_VALUE, ru.randomDouble(10d, Double.POSITIVE_INFINITY)); + assertEquals(Double.POSITIVE_INFINITY, ru.randomDouble(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY)); + } + /** * Test a float range one ULP wide. A previous implementation could round the scaled value up to the * exclusive upper limit about half of the time. @@ -607,6 +644,19 @@ void testRandomFloatExclusiveUpperBound(final RandomUtils ru) { } } + @ParameterizedTest + @ValueSource(doubles = {0, 0.5, 0.75, 0.9999999403953552}) + void testRandomFloatFixedBoundaries(final double value) { + final RandomUtils ru = fixedRandom(value); + assertEquals(10f, ru.randomFloat(10f, Math.nextUp(10f))); + assertEquals(0f, ru.randomFloat(0f, Float.MIN_VALUE)); + assertEquals(Float.MIN_VALUE, ru.randomFloat(Float.MIN_VALUE, 2 * Float.MIN_VALUE)); + assertEquals(Math.nextDown(Float.MAX_VALUE), ru.randomFloat(Math.nextDown(Float.MAX_VALUE), Float.MAX_VALUE)); + assertEquals((float) value, ru.randomFloat(0f, 1f)); + assertEquals(Float.MAX_VALUE, ru.randomFloat(10f, Float.POSITIVE_INFINITY)); + assertEquals(Float.POSITIVE_INFINITY, ru.randomFloat(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY)); + } + /** * Tests a zero byte array length. */
