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]);
+ }
+ }
+}