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