This is an automated email from the ASF dual-hosted git repository. aherbert pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-rng.git
commit 801d7acca82d0ca760de05eadf3a323c04da954d Author: Alex Herbert <[email protected]> AuthorDate: Sun Sep 1 15:34:23 2019 +0100 RNG-19: Create a wrapper for JDK Random. This can wrap a SecureRandom instance to provide access to the underlying OS source of randomness when on a supported platform. --- .../commons/rng/simple/JDKRandomWrapper.java | 123 +++++++++++++++ .../commons/rng/simple/JDKRandomWrapperTest.java | 170 +++++++++++++++++++++ 2 files changed, 293 insertions(+) diff --git a/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/JDKRandomWrapper.java b/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/JDKRandomWrapper.java new file mode 100644 index 0000000..998e4ec --- /dev/null +++ b/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/JDKRandomWrapper.java @@ -0,0 +1,123 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.rng.simple; + +import org.apache.commons.rng.UniformRandomProvider; + +import java.util.Random; + +/** + * Wraps a {@link Random} instance to implement {@link UniformRandomProvider}. All methods from + * the {@code Random} that match those in {@code UniformRandomProvider} are used directly. + * + * <p>This class can be used to wrap an instance of + * {@link java.security.SecureRandom SecureRandom}. The {@code SecureRandom} class provides + * cryptographic random number generation. The features available depend on the Java version + * and platform. Consult the Java documentation for more details.</p> + * + * <p>Note: Use of {@code java.util.Random} is <em>not</em> recommended for applications. + * There are many other pseudo-random number generators that are statistically superior and often + * faster (see {@link RandomSource}).</p> + * + * @since 1.3 + * @see java.security.SecureRandom + * @see RandomSource + */ +public final class JDKRandomWrapper implements UniformRandomProvider { + /** The JDK Random instance. */ + private final Random rng; + + /** + * Create a wrapper around a Random instance. + * + * @param rng JDK {@link Random} instance to which the random number + * generation is delegated. + */ + public JDKRandomWrapper(Random rng) { + this.rng = rng; + } + + /** {@inheritDoc} */ + @Override + public void nextBytes(byte[] bytes) { + rng.nextBytes(bytes); + } + + /** {@inheritDoc} */ + @Override + public void nextBytes(byte[] bytes, + int start, + int len) { + final byte[] reduced = new byte[len]; + rng.nextBytes(reduced); + System.arraycopy(reduced, 0, bytes, start, len); + } + + /** {@inheritDoc} */ + @Override + public int nextInt() { + return rng.nextInt(); + } + + /** {@inheritDoc} */ + @Override + public int nextInt(int n) { + return rng.nextInt(n); + } + + /** {@inheritDoc} */ + @Override + public long nextLong() { + return rng.nextLong(); + } + + /** {@inheritDoc} */ + @Override + public long nextLong(long n) { + // Code copied from "o.a.c.rng.core.BaseProvider". + if (n <= 0) { + throw new IllegalArgumentException("Must be strictly positive: " + n); + } + + long bits; + long val; + do { + bits = nextLong() >>> 1; + val = bits % n; + } while (bits - val + (n - 1) < 0); + + return val; + } + + /** {@inheritDoc} */ + @Override + public boolean nextBoolean() { + return rng.nextBoolean(); + } + + /** {@inheritDoc} */ + @Override + public float nextFloat() { + return rng.nextFloat(); + } + + /** {@inheritDoc} */ + @Override + public double nextDouble() { + return rng.nextDouble(); + } +} diff --git a/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/JDKRandomWrapperTest.java b/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/JDKRandomWrapperTest.java new file mode 100644 index 0000000..e4e9895 --- /dev/null +++ b/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/JDKRandomWrapperTest.java @@ -0,0 +1,170 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.rng.simple; + +import java.util.Random; + +import org.apache.commons.rng.UniformRandomProvider; +import org.apache.commons.rng.core.source64.LongProvider; +import org.junit.Assert; +import org.junit.Test; + +/** + * Tests for the {@link JDKRandomWrapper} class. + */ +public class JDKRandomWrapperTest { + /** + * Test all the methods shared by Random and UniformRandomProvider are equivalent. + */ + @Test + public void testJDKRandomEquivalence() { + // Initialize. + final long seed = RandomSource.createLong(); + final Random rng1 = new Random(seed); + final UniformRandomProvider rng2 = new JDKRandomWrapper(new Random(seed)); + checkSameSequence(rng1, rng2); + } + + /** + * Ensure that both generators produce the same sequences. + * + * @param rng1 RNG. + * @param rng2 RNG. + */ + private static void checkSameSequence(Random rng1, + UniformRandomProvider rng2) { + for (int i = 0; i < 4; i++) { + Assert.assertEquals(rng1.nextInt(), + rng2.nextInt()); + } + for (int i = 0; i < 7; i++) { + Assert.assertEquals(rng1.nextLong(), + rng2.nextLong()); + } + for (int i = 0; i < 9; i++) { + Assert.assertEquals(rng1.nextFloat(), + rng2.nextFloat(), + 0f); + } + for (int i = 0; i < 12; i++) { + Assert.assertEquals(rng1.nextDouble(), + rng2.nextDouble(), + 0d); + } + for (int i = 0; i < 18; i++) { + Assert.assertEquals(rng1.nextBoolean(), + rng2.nextBoolean()); + } + for (int i = 0; i < 19; i++) { + final int max = i + 123456; + Assert.assertEquals(rng1.nextInt(max), + rng2.nextInt(max)); + } + + final int len = 233; + final byte[] store1 = new byte[len]; + final byte[] store2 = new byte[len]; + rng1.nextBytes(store1); + rng2.nextBytes(store2); + for (int i = 0; i < len; i++) { + Assert.assertEquals(store1[i], + store2[i]); + } + } + + /** + * Test {@link UniformRandomProvider#nextLong(long)} matches that from the core + * BaseProvider implementation. + */ + @Test + public void testNextLongInRange() { + final long seed = RandomSource.createLong(); + // This will use the RNG core BaseProvider implementation. + // Use a LongProvider to directly use the Random::nextLong method + // which is different from IntProvider::nextLong. + final UniformRandomProvider rng1 = new LongProvider() { + private final Random random = new Random(seed); + + @Override + public long next() { + return random.nextLong(); + } + }; + final UniformRandomProvider rng2 = new JDKRandomWrapper(new Random(seed)); + + // Test cases + // 1 : Smallest range + // 256 : Integer power of 2 + // 56757 : Integer range + // 1L << 32 : Non-integer power of 2 + // (1L << 62) + 1 : Worst case for rejection rate for the algorithm. + // Reject probability is approximately 0.5 thus the test hits + // all code paths. + for (final long max : new long[] {1, 256, 56757, 1L << 32, (1L << 62) + 1}) { + for (int i = 0; i < 10; i++) { + Assert.assertEquals(rng1.nextLong(max), + rng2.nextLong(max)); + } + } + } + + @Test(expected = IllegalArgumentException.class) + public void testNextLongInRangeThrows() { + final UniformRandomProvider rng1 = new JDKRandomWrapper(new Random(5675767L)); + rng1.nextLong(0); + } + + /** + * Test the bytes created by {@link UniformRandomProvider#nextBytes(byte[], int, int)} matches + * {@link Random#nextBytes(byte[])}. + */ + @Test + public void testNextByteInRange() { + final long seed = RandomSource.createLong(); + final Random rng1 = new Random(seed); + final UniformRandomProvider rng2 = new JDKRandomWrapper(new Random(seed)); + + checkSameBytes(rng1, rng2, 1, 0, 1); + checkSameBytes(rng1, rng2, 100, 0, 100); + checkSameBytes(rng1, rng2, 100, 10, 90); + checkSameBytes(rng1, rng2, 245, 67, 34); + } + + /** + * Ensure that the bytes produced in a sub-range of a byte array by + * {@link UniformRandomProvider#nextBytes(byte[], int, int)} match the bytes created + * by the JDK {@link Random#nextBytes(byte[])}. + * + * @param rng1 JDK Random. + * @param rng2 RNG. + * @param size Size of byte array. + * @param start Index at which to start inserting the generated bytes. + * @param len Number of bytes to insert. + */ + private static void checkSameBytes(Random rng1, + UniformRandomProvider rng2, + int size, int start, int length) { + final byte[] store1 = new byte[length]; + final byte[] store2 = new byte[size]; + rng1.nextBytes(store1); + rng2.nextBytes(store2, start, length); + for (int i = 0; i < length; i++) { + Assert.assertEquals(store1[i], + store2[i + start]); + } + } +}
