You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by ah...@apache.org on 2019/09/03 08:43:38 UTC

[commons-rng] 03/04: RNG-19: Create a wrapper for JDK Random.

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 <ah...@apache.org>
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]);
+        }
+    }
+}