LANG-1592: Correct implementation of RandomUtils.nextLong(long, long)

The method has been changed to use exclusively the long datatype to
generate the value.

The implementation is taken from the Commons RNG project.
This commit is contained in:
Alex Herbert 2020-07-21 08:45:03 +01:00
parent 4e9460413a
commit ba7505d9a9
3 changed files with 48 additions and 2 deletions

View File

@ -48,6 +48,7 @@ The <action> type attribute can be add,update,fix,remove.
<release version="3.12" date="2020-MM-DD" description="New features and bug fixes.">
<action issue="LANG-1591" type="update" dev="kinow" due-to="bhawna94">Remove redundant argument from substring call.</action>
<action type="update" dev="ggregory" due-to="Enable Dependabot #587">Enable Dependabot #587.</action>
<action issue="LANG-1592" type="fix" dev="aherbert" due-to="Huang Pingcai, Alex Herbert">Correct implementation of RandomUtils.nextLong(long, long)</action>
</release>
<release version="3.11" date="2020-07-12" description="New features and bug fixes.">
<action type="update" dev="chtompki" due-to="Jin Xu">Refine test output for FastDateParserTest</action>

View File

@ -145,7 +145,7 @@ public class RandomUtils {
return startInclusive;
}
return (long) nextDouble(startInclusive, endExclusive);
return startInclusive + nextLong(endExclusive - startInclusive);
}
/**
@ -156,7 +156,27 @@ public class RandomUtils {
* @since 3.5
*/
public static long nextLong() {
return nextLong(0, Long.MAX_VALUE);
return nextLong(Long.MAX_VALUE);
}
/**
* Generates a {@code long} value between 0 (inclusive) and the specified
* value (exclusive).
*
* @param n Bound on the random number to be returned. Must be positive.
* @return a random {@code long} value between 0 (inclusive) and {@code n}
* (exclusive).
*/
private static long nextLong(long n) {
// Extracted from o.a.c.rng.core.BaseProvider.nextLong(long)
long bits;
long val;
do {
bits = RANDOM.nextLong() >>> 1;
val = bits % n;
} while (bits - val + (n - 1) < 0);
return val;
}
/**

View File

@ -19,6 +19,7 @@ package org.apache.commons.lang3;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ -262,4 +263,28 @@ public class RandomUtilsTest {
final double result = RandomUtils.nextDouble(0, Double.MAX_VALUE);
assertTrue(result >= 0 && result <= Double.MAX_VALUE);
}
/**
* Test a large value for long. A previous implementation using
* {@link RandomUtils#nextDouble(double, double)} could generate a value equal
* to the upper limit.
*
* <pre>
* return (long) nextDouble(startInclusive, endExclusive);
* </pre>
*
* <p>See LANG-1592.</p>
*/
@Test
public void testLargeValueRangeLong() {
final long startInclusive = 12900000000001L;
final long endExclusive = 12900000000016L;
// Note: The method using 'return (long) nextDouble(startInclusive, endExclusive)'
// takes thousands of calls to generate an error. This size loop fails most
// of the time with the previous method.
final int n = (int) (endExclusive - startInclusive) * 1000;
for (int i = 0; i < n; i++) {
assertNotEquals(endExclusive, RandomUtils.nextLong(startInclusive, endExclusive));
}
}
}