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


The following commit(s) were added to refs/heads/master by this push:
     new 98fb6efc5 Keep RandomUtils's randomDouble() and randomFloat() below 
the exclusive upper bound (#1817)
98fb6efc5 is described below

commit 98fb6efc58ea63eeba75cddc077db4c33b9a40ed
Author: alhuda <[email protected]>
AuthorDate: Thu Oct 15 23:54:25 2026 +0530

    Keep RandomUtils's randomDouble() and randomFloat() below the exclusive 
upper bound (#1817)
    
    * keep randomDouble and randomFloat below the exclusive upper bound
    
    * Clarify comment on clamping behavior for nextDouble
    
    Co-authored-by: Copilot Autofix powered by AI 
<[email protected]>
    
    * Rename instance exclusive-bound tests and align the float clamp comment
    
    * Use a constant for the exclusive-bound test trial count
    
    ---------
    
    Co-authored-by: Gary Gregory <[email protected]>
    Co-authored-by: Copilot Autofix powered by AI 
<[email protected]>
---
 .../java/org/apache/commons/lang3/RandomUtils.java |  8 ++-
 .../org/apache/commons/lang3/RandomUtilsTest.java  | 59 ++++++++++++++++++++++
 2 files changed, 65 insertions(+), 2 deletions(-)

diff --git a/src/main/java/org/apache/commons/lang3/RandomUtils.java 
b/src/main/java/org/apache/commons/lang3/RandomUtils.java
index 0b0ac8a98..f3f5b4e21 100644
--- a/src/main/java/org/apache/commons/lang3/RandomUtils.java
+++ b/src/main/java/org/apache/commons/lang3/RandomUtils.java
@@ -345,7 +345,9 @@ public double randomDouble(final double startInclusive, 
final double endExclusiv
         if (startInclusive == endExclusive) {
             return startInclusive;
         }
-        return startInclusive + (endExclusive - startInclusive) * 
random().nextDouble();
+        final double result = startInclusive + (endExclusive - startInclusive) 
* random().nextDouble();
+        // Rounding in the scaling above can reach endExclusive; clamp to the 
greatest value below endExclusive.
+        return result < endExclusive ? result : Math.nextDown(endExclusive);
     }
 
     /**
@@ -374,7 +376,9 @@ public float randomFloat(final float startInclusive, final 
float endExclusive) {
         if (startInclusive == endExclusive) {
             return startInclusive;
         }
-        return startInclusive + (endExclusive - startInclusive) * 
random().nextFloat();
+        final float result = startInclusive + (endExclusive - startInclusive) 
* random().nextFloat();
+        // Rounding in the scaling above can reach endExclusive; clamp to the 
greatest value below endExclusive.
+        return result < endExclusive ? result : Math.nextDown(endExclusive);
     }
 
     /**
diff --git a/src/test/java/org/apache/commons/lang3/RandomUtilsTest.java 
b/src/test/java/org/apache/commons/lang3/RandomUtilsTest.java
index 97bebae7b..46a5e4c83 100644
--- a/src/test/java/org/apache/commons/lang3/RandomUtilsTest.java
+++ b/src/test/java/org/apache/commons/lang3/RandomUtilsTest.java
@@ -39,6 +39,11 @@ class RandomUtilsTest extends AbstractLangTest {
      */
     private static final double DELTA = 1e-5;
 
+    /**
+     * Number of calls made by the exclusive upper bound tests.
+     */
+    private static final int EXCLUSIVE_BOUND_TRIALS = 1000;
+
     static Stream<RandomUtils> randomProvider() {
         return Stream.of(RandomUtils.secure(), RandomUtils.secureStrong(), 
RandomUtils.insecure());
     }
@@ -245,6 +250,19 @@ void testNextDouble(final RandomUtils ru) {
         assertTrue(result < 42d);
     }
 
+    /**
+     * Test a double range one ULP wide. A previous implementation could round 
the scaled value up to the
+     * exclusive upper limit about half of the time.
+     */
+    @Test
+    void testNextDoubleExclusiveUpperBound() {
+        final double startInclusive = 10d;
+        final double endExclusive = Math.nextUp(startInclusive);
+        for (int i = 0; i < EXCLUSIVE_BOUND_TRIALS; i++) {
+            assertEquals(startInclusive, 
RandomUtils.nextDouble(startInclusive, endExclusive));
+        }
+    }
+
     @Test
     void testNextDoubleLowerGreaterUpper() {
         assertIllegalArgumentException(() -> RandomUtils.nextDouble(2, 1));
@@ -326,6 +344,19 @@ void testNextFloat(final RandomUtils ru) {
         assertTrue(result < 42f);
     }
 
+    /**
+     * 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.
+     */
+    @Test
+    void testNextFloatExclusiveUpperBound() {
+        final float startInclusive = 10f;
+        final float endExclusive = Math.nextUp(startInclusive);
+        for (int i = 0; i < EXCLUSIVE_BOUND_TRIALS; i++) {
+            assertEquals(startInclusive, RandomUtils.nextFloat(startInclusive, 
endExclusive));
+        }
+    }
+
     @Test
     void testNextFloatLowerGreaterUpper() {
         assertIllegalArgumentException(() -> RandomUtils.nextFloat(2, 1));
@@ -548,6 +579,34 @@ void testNextLongRandomResult(final RandomUtils ru) {
         assertTrue(result < Long.MAX_VALUE);
     }
 
+    /**
+     * Test a double range one ULP wide. A previous implementation could round 
the scaled value up to the
+     * exclusive upper limit about half of the time.
+     */
+    @ParameterizedTest
+    @MethodSource("randomProvider")
+    void testRandomDoubleExclusiveUpperBound(final RandomUtils ru) {
+        final double startInclusive = 10d;
+        final double endExclusive = Math.nextUp(startInclusive);
+        for (int i = 0; i < EXCLUSIVE_BOUND_TRIALS; i++) {
+            assertEquals(startInclusive, ru.randomDouble(startInclusive, 
endExclusive));
+        }
+    }
+
+    /**
+     * 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.
+     */
+    @ParameterizedTest
+    @MethodSource("randomProvider")
+    void testRandomFloatExclusiveUpperBound(final RandomUtils ru) {
+        final float startInclusive = 10f;
+        final float endExclusive = Math.nextUp(startInclusive);
+        for (int i = 0; i < EXCLUSIVE_BOUND_TRIALS; i++) {
+            assertEquals(startInclusive, ru.randomFloat(startInclusive, 
endExclusive));
+        }
+    }
+
     /**
      * Tests a zero byte array length.
      */

Reply via email to