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.
      */

Reply via email to