Repository: commons-rng Updated Branches: refs/heads/multimodule 7e12202ac -> 79fcc8c29
RNG-24: Bridge to "java.util.Random". Project: http://git-wip-us.apache.org/repos/asf/commons-rng/repo Commit: http://git-wip-us.apache.org/repos/asf/commons-rng/commit/79fcc8c2 Tree: http://git-wip-us.apache.org/repos/asf/commons-rng/tree/79fcc8c2 Diff: http://git-wip-us.apache.org/repos/asf/commons-rng/diff/79fcc8c2 Branch: refs/heads/multimodule Commit: 79fcc8c29017bbeaaf88dc228c32f446efd845c5 Parents: 7e12202 Author: Gilles <er...@apache.org> Authored: Wed Nov 9 13:41:51 2016 +0100 Committer: Gilles <er...@apache.org> Committed: Wed Nov 9 13:41:51 2016 +0100 ---------------------------------------------------------------------- .../commons/rng/simple/JDKRandomBridge.java | 125 +++++++++++++++++++ .../commons/rng/simple/JDKRandomBridgeTest.java | 121 ++++++++++++++++++ 2 files changed, 246 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/commons-rng/blob/79fcc8c2/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/JDKRandomBridge.java ---------------------------------------------------------------------- diff --git a/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/JDKRandomBridge.java b/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/JDKRandomBridge.java new file mode 100644 index 0000000..b2831db --- /dev/null +++ b/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/JDKRandomBridge.java @@ -0,0 +1,125 @@ +/* + * 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.io.Serializable; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.ObjectInputStream; +import java.util.Random; +import org.apache.commons.rng.RestorableUniformRandomProvider; +import org.apache.commons.rng.core.RandomProviderDefaultState; + +/** + * Subclass of {@link Random} that {@link #next(int) delegates} to a + * {@link RestorableUniformRandomProvider} instance but will otherwise + * rely on the base class for generating all the random types. + * </p> + * + * <p> + * Legacy applications coded against the JDK's API could use this subclass + * of {@link Random} in order to replace its linear congruential generator + * by any {@link RandomSource}. + * Use of this class is however <em>not</em> recommended for new applications. + * </p> + * + * @since 1.0 + */ +public final class JDKRandomBridge extends Random { + /** Serializable version identifier. */ + private static final long serialVersionUID = 20161107L; + /** Source. */ + private final RandomSource source; + /** Delegate. */ + private transient RestorableUniformRandomProvider delegate; + /** Workaround JDK's "Random" bug: https://bugs.openjdk.java.net/browse/JDK-8154225 */ + private final boolean isInitialized; + + /** + * Creates a new instance. + * + * @param source Source of randomness. + * @param seed Seed. Can be {@code null}. + */ + public JDKRandomBridge(RandomSource source, + Object seed) { + this.source = source; + delegate = RandomSource.create(source, seed); + isInitialized = true; + } + + /** {@inheritDoc} */ + @Override + public synchronized void setSeed(long seed) { + if (isInitialized) { + delegate = RandomSource.create(source, seed); + + // Force the clearing of the "haveNextNextGaussian" flag + // (cf. Javadoc of the base class); the value passed here + // is irrelevant (since it will not be used). + super.setSeed(0L); + } + } + + /** + * Delegates the generation of 32 random bits to the + * {@code RandomSource} argument provided at + * {@link #JDKRandomBridge(RandomSource,Object) construction}. + * The returned value is such that if the source of randomness is + * {@link RandomSource#JDK}, all the generated values will be identical + * to those produced by the same sequence of calls on a {@link Random} + * instance initialized with the same seed. + * + * @param n Number of random bits which the requested value must contain. + * @return the value represented by the {@code n} high-order bits of a + * pseudo-random 32-bits integer. + */ + @Override + protected synchronized int next(int n) { + return delegate.nextInt() >>> (32 - n); + } + + /** + * @param out Output stream. + * @throws IOException if an error occurs. + */ + private synchronized void writeObject(ObjectOutputStream out) + throws IOException { + // Write non-transient fields. + out.defaultWriteObject(); + + // Save current state. + out.writeObject(((RandomProviderDefaultState) delegate.saveState()).getState()); + } + + /** + * @param in Input stream. + * @throws IOException if an error occurs. + * @throws ClassNotFoundException if an error occurs. + */ + private synchronized void readObject(ObjectInputStream in) + throws IOException, + ClassNotFoundException { + // Read non-transient fields. + in.defaultReadObject(); + + // Recreate the "delegate" from serialized info. + delegate = RandomSource.create(source); + // And restore its state. + delegate.restoreState(new RandomProviderDefaultState((byte[]) in.readObject())); + } +} http://git-wip-us.apache.org/repos/asf/commons-rng/blob/79fcc8c2/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/JDKRandomBridgeTest.java ---------------------------------------------------------------------- diff --git a/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/JDKRandomBridgeTest.java b/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/JDKRandomBridgeTest.java new file mode 100644 index 0000000..70fa450 --- /dev/null +++ b/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/JDKRandomBridgeTest.java @@ -0,0 +1,121 @@ +/* + * 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.io.IOException; +import java.io.ObjectOutputStream; +import java.io.ObjectInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ByteArrayInputStream; +import java.util.Random; +import org.junit.Assert; +import org.junit.Test; + +/** + * Tests for the {@link JDKRandomBridge} adaptor class. + */ +public class JDKRandomBridgeTest { + @Test + public void testJDKRandomEquivalence() { + // Initialize. + final long seed = RandomSource.createLong(); + final Random rng1 = new Random(seed); + final Random rng2 = new JDKRandomBridge(RandomSource.JDK, seed); + checkSameSequence(rng1, rng2); + + // Reseed. + final long newSeed = RandomSource.createLong(); + Assert.assertNotEquals(seed, newSeed); + rng1.setSeed(newSeed); + rng2.setSeed(newSeed); + checkSameSequence(rng1, rng2); + } + + @Test + public void testSerialization() + throws IOException, + ClassNotFoundException { + // Initialize. + final long seed = RandomSource.createLong(); + final Random rng = new JDKRandomBridge(RandomSource.SPLIT_MIX_64, seed); + + // Serialize. + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bos); + oos.writeObject(rng); + + // Retrieve from serialized stream. + ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); + ObjectInputStream ois = new ObjectInputStream(bis); + final Random serialRng = (Random) (ois.readObject()); + + // Check that the serialized data recreated the orginal state. + checkSameSequence(rng, serialRng); + } + + /** + * Ensure that both generators produce the same sequences. + * + * @param rng1 RNG. + * @param rng2 RNG. + */ + private void checkSameSequence(Random rng1, + Random 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 < 17; i++) { + Assert.assertEquals(rng1.nextGaussian(), + rng2.nextGaussian(), + 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]); + } + } +}