Author: luc Date: Sun Mar 10 19:02:54 2013 New Revision: 1454897 URL: http://svn.apache.org/r1454897 Log: Fixed generation of long random numbers between two bounds.
We now directly use discrete raw values to build the int/double instead of relying on floating point arithmetic. JIRA: MATH-936 Modified: commons/proper/math/trunk/src/changes/changes.xml commons/proper/math/trunk/src/main/java/org/apache/commons/math3/random/BitsStreamGenerator.java commons/proper/math/trunk/src/main/java/org/apache/commons/math3/random/RandomDataGenerator.java commons/proper/math/trunk/src/main/java/org/apache/commons/math3/stat/ranking/NaturalRanking.java commons/proper/math/trunk/src/test/java/org/apache/commons/math3/random/MersenneTwisterTest.java commons/proper/math/trunk/src/test/java/org/apache/commons/math3/random/RandomDataGeneratorTest.java commons/proper/math/trunk/src/test/java/org/apache/commons/math3/random/Well512aTest.java commons/proper/math/trunk/src/test/java/org/apache/commons/math3/stat/ranking/NaturalRankingTest.java Modified: commons/proper/math/trunk/src/changes/changes.xml URL: http://svn.apache.org/viewvc/commons/proper/math/trunk/src/changes/changes.xml?rev=1454897&r1=1454896&r2=1454897&view=diff ============================================================================== --- commons/proper/math/trunk/src/changes/changes.xml (original) +++ commons/proper/math/trunk/src/changes/changes.xml Sun Mar 10 19:02:54 2013 @@ -55,10 +55,13 @@ This is a minor release: It combines bug Changes to existing features were made in a backwards-compatible way such as to allow drop-in replacement of the v3.1[.1] JAR file. "> + <action dev="luc" type="fix" issue="MATH-936" > + Fixed generation of long random numbers between two bounds. + </action> <action dev="luc" type="fix" issue="MATH-942" due-to="Piotr Wydrych" > Fixed creation of generic array. </action> - <action dev="luc" type="add" issue="MATH-914" > + <action dev="luc" type="add" issue="MATH-914" > Check bounds in multi-start vector optimizers. </action> <action dev="luc" type="add" issue="MATH-941" due-to="Piotr Wydrych" > Modified: commons/proper/math/trunk/src/main/java/org/apache/commons/math3/random/BitsStreamGenerator.java URL: http://svn.apache.org/viewvc/commons/proper/math/trunk/src/main/java/org/apache/commons/math3/random/BitsStreamGenerator.java?rev=1454897&r1=1454896&r2=1454897&view=diff ============================================================================== --- commons/proper/math/trunk/src/main/java/org/apache/commons/math3/random/BitsStreamGenerator.java (original) +++ commons/proper/math/trunk/src/main/java/org/apache/commons/math3/random/BitsStreamGenerator.java Sun Mar 10 19:02:54 2013 @@ -163,6 +163,31 @@ public abstract class BitsStreamGenerato } /** + * Returns a pseudorandom, uniformly distributed <tt>long</tt> value + * between 0 (inclusive) and the specified value (exclusive), drawn from + * this random number generator's sequence. + * + * @param n the bound on the random number to be returned. Must be + * positive. + * @return a pseudorandom, uniformly distributed <tt>long</tt> + * value between 0 (inclusive) and n (exclusive). + * @throws IllegalArgumentException if n is not positive. + */ + public long nextLong(long n) throws IllegalArgumentException { + if (n > 0) { + long bits; + long val; + do { + bits = ((long) next(31)) << 32; + bits = bits | (((long) next(32)) & 0xffffffffL); + val = bits % n; + } while (bits - val + (n - 1) < 0); + return val; + } + throw new NotStrictlyPositiveException(n); + } + + /** * Clears the cache used by the default implementation of * {@link #nextGaussian}. */ Modified: commons/proper/math/trunk/src/main/java/org/apache/commons/math3/random/RandomDataGenerator.java URL: http://svn.apache.org/viewvc/commons/proper/math/trunk/src/main/java/org/apache/commons/math3/random/RandomDataGenerator.java?rev=1454897&r1=1454896&r2=1454897&view=diff ============================================================================== --- commons/proper/math/trunk/src/main/java/org/apache/commons/math3/random/RandomDataGenerator.java (original) +++ commons/proper/math/trunk/src/main/java/org/apache/commons/math3/random/RandomDataGenerator.java Sun Mar 10 19:02:54 2013 @@ -45,7 +45,6 @@ import org.apache.commons.math3.exceptio import org.apache.commons.math3.exception.NumberIsTooLargeException; import org.apache.commons.math3.exception.OutOfRangeException; import org.apache.commons.math3.exception.util.LocalizedFormats; -import org.apache.commons.math3.util.FastMath; /** * Implements the {@link RandomData} interface using a {@link RandomGenerator} @@ -194,25 +193,82 @@ public class RandomDataGenerator impleme } /** {@inheritDoc} */ - public int nextInt(int lower, int upper) throws NumberIsTooLargeException { + public int nextInt(final int lower, final int upper) throws NumberIsTooLargeException { if (lower >= upper) { throw new NumberIsTooLargeException(LocalizedFormats.LOWER_BOUND_NOT_BELOW_UPPER_BOUND, lower, upper, false); } - double r = getRan().nextDouble(); - double scaled = r * upper + (1.0 - r) * lower + r; - return (int) FastMath.floor(scaled); + final int max = (upper - lower) + 1; + if (max <= 0) { + // the range is too wide to fit in a positive int (larger than 2^31); as it covers + // more than half the integer range, we use directly a simple rejection method + final RandomGenerator rng = getRan(); + while (true) { + final int r = rng.nextInt(); + if (r >= lower && r <= upper) { + return r; + } + } + } else { + // we can shift the range and generate directly a positive int + return lower + getRan().nextInt(max); + } } /** {@inheritDoc} */ - public long nextLong(long lower, long upper) throws NumberIsTooLargeException { + public long nextLong(final long lower, final long upper) throws NumberIsTooLargeException { if (lower >= upper) { throw new NumberIsTooLargeException(LocalizedFormats.LOWER_BOUND_NOT_BELOW_UPPER_BOUND, lower, upper, false); } - double r = getRan().nextDouble(); - double scaled = r * upper + (1.0 - r) * lower + r; - return (long)FastMath.floor(scaled); + final long max = (upper - lower) + 1; + if (max <= 0) { + // the range is too wide to fit in a positive long (larger than 2^63); as it covers + // more than half the long range, we use directly a simple rejection method + final RandomGenerator rng = getRan(); + while (true) { + final long r = rng.nextLong(); + if (r >= lower && r <= upper) { + return r; + } + } + } else if (max < Integer.MAX_VALUE){ + // we can shift the range and generate directly a positive int + return lower + getRan().nextInt((int) max); + } else { + // we can shift the range and generate directly a positive long + return lower + nextLong(getRan(), max); + } + } + + /** + * Returns a pseudorandom, uniformly distributed <tt>long</tt> value + * between 0 (inclusive) and the specified value (exclusive), drawn from + * this random number generator's sequence. + * + * @param n the bound on the random number to be returned. Must be + * positive. + * @return a pseudorandom, uniformly distributed <tt>long</tt> + * value between 0 (inclusive) and n (exclusive). + * @throws IllegalArgumentException if n is not positive. + */ + private static long nextLong(final RandomGenerator rng, final long n) throws IllegalArgumentException { + if (n > 0) { + final byte[] byteArray = new byte[8]; + long bits; + long val; + do { + rng.nextBytes(byteArray); + bits = 0; + for (final byte b : byteArray) { + bits = (bits << 8) | (((long) b) & 0xffL); + } + bits = bits & 0x7fffffffffffffffL; + val = bits % n; + } while (bits - val + (n - 1) < 0); + return val; + } + throw new NotStrictlyPositiveException(n); } /** @@ -282,27 +338,82 @@ public class RandomDataGenerator impleme } /** {@inheritDoc} */ - public int nextSecureInt(int lower, int upper) throws NumberIsTooLargeException { + public int nextSecureInt(final int lower, final int upper) throws NumberIsTooLargeException { if (lower >= upper) { throw new NumberIsTooLargeException(LocalizedFormats.LOWER_BOUND_NOT_BELOW_UPPER_BOUND, lower, upper, false); } - SecureRandom sec = getSecRan(); - final double r = sec.nextDouble(); - final double scaled = r * upper + (1.0 - r) * lower + r; - return (int)FastMath.floor(scaled); + final int max = (upper - lower) + 1; + if (max <= 0) { + // the range is too wide to fit in a positive int (larger than 2^31); as it covers + // more than half the integer range, we use directly a simple rejection method + final SecureRandom rng = getSecRan(); + while (true) { + final int r = rng.nextInt(); + if (r >= lower && r <= upper) { + return r; + } + } + } else { + // we can shift the range and generate directly a positive int + return lower + getSecRan().nextInt(max); + } } /** {@inheritDoc} */ - public long nextSecureLong(long lower, long upper) throws NumberIsTooLargeException { + public long nextSecureLong(final long lower, final long upper) throws NumberIsTooLargeException { if (lower >= upper) { throw new NumberIsTooLargeException(LocalizedFormats.LOWER_BOUND_NOT_BELOW_UPPER_BOUND, lower, upper, false); } - SecureRandom sec = getSecRan(); - final double r = sec.nextDouble(); - final double scaled = r * upper + (1.0 - r) * lower + r; - return (long)FastMath.floor(scaled); + final long max = (upper - lower) + 1; + if (max <= 0) { + // the range is too wide to fit in a positive long (larger than 2^63); as it covers + // more than half the long range, we use directly a simple rejection method + final SecureRandom rng = getSecRan(); + while (true) { + final long r = rng.nextLong(); + if (r >= lower && r <= upper) { + return r; + } + } + } else if (max < Integer.MAX_VALUE){ + // we can shift the range and generate directly a positive int + return lower + getSecRan().nextInt((int) max); + } else { + // we can shift the range and generate directly a positive long + return lower + nextLong(getSecRan(), max); + } + } + + /** + * Returns a pseudorandom, uniformly distributed <tt>long</tt> value + * between 0 (inclusive) and the specified value (exclusive), drawn from + * this random number generator's sequence. + * + * @param n the bound on the random number to be returned. Must be + * positive. + * @return a pseudorandom, uniformly distributed <tt>long</tt> + * value between 0 (inclusive) and n (exclusive). + * @throws IllegalArgumentException if n is not positive. + */ + private static long nextLong(final SecureRandom rng, final long n) throws IllegalArgumentException { + if (n > 0) { + final byte[] byteArray = new byte[8]; + long bits; + long val; + do { + rng.nextBytes(byteArray); + bits = 0; + for (final byte b : byteArray) { + bits = (bits << 8) | (((long) b) & 0xffL); + } + bits = bits & 0x7fffffffffffffffL; + val = bits % n; + } while (bits - val + (n - 1) < 0); + return val; + } + throw new NotStrictlyPositiveException(n); } /** Modified: commons/proper/math/trunk/src/main/java/org/apache/commons/math3/stat/ranking/NaturalRanking.java URL: http://svn.apache.org/viewvc/commons/proper/math/trunk/src/main/java/org/apache/commons/math3/stat/ranking/NaturalRanking.java?rev=1454897&r1=1454896&r2=1454897&view=diff ============================================================================== --- commons/proper/math/trunk/src/main/java/org/apache/commons/math3/stat/ranking/NaturalRanking.java (original) +++ commons/proper/math/trunk/src/main/java/org/apache/commons/math3/stat/ranking/NaturalRanking.java Sun Mar 10 19:02:54 2013 @@ -24,8 +24,7 @@ import java.util.List; import org.apache.commons.math3.exception.MathInternalError; import org.apache.commons.math3.exception.NotANumberException; -import org.apache.commons.math3.random.RandomData; -import org.apache.commons.math3.random.RandomDataImpl; +import org.apache.commons.math3.random.RandomDataGenerator; import org.apache.commons.math3.random.RandomGenerator; import org.apache.commons.math3.util.FastMath; @@ -84,7 +83,7 @@ public class NaturalRanking implements R private final TiesStrategy tiesStrategy; /** Source of random data - used only when ties strategy is RANDOM */ - private final RandomData randomData; + private final RandomDataGenerator randomData; /** * Create a NaturalRanking with default strategies for handling ties and NaNs. @@ -105,7 +104,7 @@ public class NaturalRanking implements R super(); this.tiesStrategy = tiesStrategy; nanStrategy = DEFAULT_NAN_STRATEGY; - randomData = new RandomDataImpl(); + randomData = new RandomDataGenerator(); } /** @@ -130,7 +129,7 @@ public class NaturalRanking implements R super(); this.nanStrategy = nanStrategy; this.tiesStrategy = tiesStrategy; - randomData = new RandomDataImpl(); + randomData = new RandomDataGenerator(); } /** @@ -143,7 +142,7 @@ public class NaturalRanking implements R super(); this.tiesStrategy = TiesStrategy.RANDOM; nanStrategy = DEFAULT_NAN_STRATEGY; - randomData = new RandomDataImpl(randomGenerator); + randomData = new RandomDataGenerator(randomGenerator); } @@ -159,7 +158,7 @@ public class NaturalRanking implements R super(); this.nanStrategy = nanStrategy; this.tiesStrategy = TiesStrategy.RANDOM; - randomData = new RandomDataImpl(randomGenerator); + randomData = new RandomDataGenerator(randomGenerator); } /** Modified: commons/proper/math/trunk/src/test/java/org/apache/commons/math3/random/MersenneTwisterTest.java URL: http://svn.apache.org/viewvc/commons/proper/math/trunk/src/test/java/org/apache/commons/math3/random/MersenneTwisterTest.java?rev=1454897&r1=1454896&r2=1454897&view=diff ============================================================================== --- commons/proper/math/trunk/src/test/java/org/apache/commons/math3/random/MersenneTwisterTest.java (original) +++ commons/proper/math/trunk/src/test/java/org/apache/commons/math3/random/MersenneTwisterTest.java Sun Mar 10 19:02:54 2013 @@ -23,7 +23,7 @@ public class MersenneTwisterTest extends @Override protected RandomGenerator makeGenerator() { - return new MersenneTwister(100); + return new MersenneTwister(111); } // TODO: Some of the tests moved up to RandomGeneratorAbstractTest tested alternative seeding / constructors Modified: commons/proper/math/trunk/src/test/java/org/apache/commons/math3/random/RandomDataGeneratorTest.java URL: http://svn.apache.org/viewvc/commons/proper/math/trunk/src/test/java/org/apache/commons/math3/random/RandomDataGeneratorTest.java?rev=1454897&r1=1454896&r2=1454897&view=diff ============================================================================== --- commons/proper/math/trunk/src/test/java/org/apache/commons/math3/random/RandomDataGeneratorTest.java (original) +++ commons/proper/math/trunk/src/test/java/org/apache/commons/math3/random/RandomDataGeneratorTest.java Sun Mar 10 19:02:54 2013 @@ -113,25 +113,26 @@ public class RandomDataGeneratorTest { checkNextIntUniform(-3, 6); } } - + @Test public void testNextIntNegativeRange() { for (int i = 0; i < 5; i++) { checkNextIntUniform(-7, -4); checkNextIntUniform(-15, -2); + checkNextIntUniform(Integer.MIN_VALUE + 1, Integer.MIN_VALUE + 12); } } - + @Test public void testNextIntPositiveRange() { for (int i = 0; i < 5; i++) { checkNextIntUniform(0, 3); checkNextIntUniform(2, 12); checkNextIntUniform(1,2); + checkNextIntUniform(Integer.MAX_VALUE - 12, Integer.MAX_VALUE - 1); } } - - + private void checkNextIntUniform(int min, int max) { final Frequency freq = new Frequency(); for (int i = 0; i < smallSampleSize; i++) { @@ -153,6 +154,24 @@ public class RandomDataGeneratorTest { } @Test + public void testNextIntWideRange() { + int lower = -0x6543210F; + int upper = 0x456789AB; + int max = Integer.MIN_VALUE; + int min = Integer.MAX_VALUE; + for (int i = 0; i < 1000000; ++i) { + int r = randomData.nextInt(lower, upper); + max = FastMath.max(max, r); + min = FastMath.min(min, r); + Assert.assertTrue(r >= lower); + Assert.assertTrue(r <= upper); + } + double ratio = (((double) max) - ((double) min)) / + (((double) upper) - ((double) lower)); + Assert.assertTrue(ratio > 0.99999); + } + + @Test public void testNextLongIAE() { try { randomData.nextLong(4, 3); @@ -161,7 +180,7 @@ public class RandomDataGeneratorTest { // ignored } } - + @Test public void testNextLongNegativeToPositiveRange() { for (int i = 0; i < 5; i++) { @@ -169,31 +188,34 @@ public class RandomDataGeneratorTest { checkNextLongUniform(-3, 6); } } - + @Test public void testNextLongNegativeRange() { for (int i = 0; i < 5; i++) { checkNextLongUniform(-7, -4); checkNextLongUniform(-15, -2); + checkNextLongUniform(Long.MIN_VALUE + 1, Long.MIN_VALUE + 12); } } - + @Test public void testNextLongPositiveRange() { for (int i = 0; i < 5; i++) { checkNextLongUniform(0, 3); checkNextLongUniform(2, 12); + checkNextLongUniform(Long.MAX_VALUE - 12, Long.MAX_VALUE - 1); } } - - private void checkNextLongUniform(int min, int max) { + + private void checkNextLongUniform(long min, long max) { final Frequency freq = new Frequency(); for (int i = 0; i < smallSampleSize; i++) { final long value = randomData.nextLong(min, max); - Assert.assertTrue("nextLong range", (value >= min) && (value <= max)); + Assert.assertTrue("nextLong range: " + value + " " + min + " " + max, + (value >= min) && (value <= max)); freq.addValue(value); } - final int len = max - min + 1; + final int len = ((int) (max - min)) + 1; final long[] observed = new long[len]; for (int i = 0; i < len; i++) { observed[i] = freq.getCount(min + i); @@ -207,6 +229,24 @@ public class RandomDataGeneratorTest { } @Test + public void testNextLongWideRange() { + long lower = -0x6543210FEDCBA987L; + long upper = 0x456789ABCDEF0123L; + long max = Long.MIN_VALUE; + long min = Long.MAX_VALUE; + for (int i = 0; i < 10000000; ++i) { + long r = randomData.nextLong(lower, upper); + max = FastMath.max(max, r); + min = FastMath.min(min, r); + Assert.assertTrue(r >= lower); + Assert.assertTrue(r <= upper); + } + double ratio = (((double) max) - ((double) min)) / + (((double) upper) - ((double) lower)); + Assert.assertTrue(ratio > 0.99999); + } + + @Test public void testNextSecureLongIAE() { try { randomData.nextSecureLong(4, 3); Modified: commons/proper/math/trunk/src/test/java/org/apache/commons/math3/random/Well512aTest.java URL: http://svn.apache.org/viewvc/commons/proper/math/trunk/src/test/java/org/apache/commons/math3/random/Well512aTest.java?rev=1454897&r1=1454896&r2=1454897&view=diff ============================================================================== --- commons/proper/math/trunk/src/test/java/org/apache/commons/math3/random/Well512aTest.java (original) +++ commons/proper/math/trunk/src/test/java/org/apache/commons/math3/random/Well512aTest.java Sun Mar 10 19:02:54 2013 @@ -23,7 +23,7 @@ public class Well512aTest extends Random @Override public RandomGenerator makeGenerator() { - return new Well512a(100); + return new Well512a(101); } @Test public void testReferenceCode() { Modified: commons/proper/math/trunk/src/test/java/org/apache/commons/math3/stat/ranking/NaturalRankingTest.java URL: http://svn.apache.org/viewvc/commons/proper/math/trunk/src/test/java/org/apache/commons/math3/stat/ranking/NaturalRankingTest.java?rev=1454897&r1=1454896&r2=1454897&view=diff ============================================================================== --- commons/proper/math/trunk/src/test/java/org/apache/commons/math3/stat/ranking/NaturalRankingTest.java (original) +++ commons/proper/math/trunk/src/test/java/org/apache/commons/math3/stat/ranking/NaturalRankingTest.java Sun Mar 10 19:02:54 2013 @@ -176,22 +176,22 @@ public class NaturalRankingTest { NaturalRanking ranking = new NaturalRanking(NaNStrategy.FIXED, randomGenerator); double[] ranks = ranking.rank(exampleData); - double[] correctRanks = { 5, 4, 6, 7, 3, 8, Double.NaN, 1, 4 }; + double[] correctRanks = { 5, 3, 6, 7, 3, 8, Double.NaN, 1, 2 }; TestUtils.assertEquals(correctRanks, ranks, 0d); ranks = ranking.rank(tiesFirst); - correctRanks = new double[] { 1, 1, 4, 3, 5 }; + correctRanks = new double[] { 1, 2, 4, 3, 5 }; TestUtils.assertEquals(correctRanks, ranks, 0d); ranks = ranking.rank(tiesLast); - correctRanks = new double[] { 3, 4, 2, 1 }; + correctRanks = new double[] { 3, 3, 2, 1 }; TestUtils.assertEquals(correctRanks, ranks, 0d); ranks = ranking.rank(multipleNaNs); correctRanks = new double[] { 1, 2, Double.NaN, Double.NaN }; TestUtils.assertEquals(correctRanks, ranks, 0d); ranks = ranking.rank(multipleTies); - correctRanks = new double[] { 3, 2, 5, 5, 7, 6, 1 }; + correctRanks = new double[] { 3, 2, 4, 4, 6, 7, 1 }; TestUtils.assertEquals(correctRanks, ranks, 0d); ranks = ranking.rank(allSame); - correctRanks = new double[] { 1, 3, 4, 4 }; + correctRanks = new double[] { 2, 3, 3, 3 }; TestUtils.assertEquals(correctRanks, ranks, 0d); }