From 3a5055c86d567ad9a4022d482179ce45636af8cc Mon Sep 17 00:00:00 2001 From: Alex Herbert Date: Mon, 24 Sep 2018 23:53:54 +0100 Subject: [PATCH 1/6] RNG-57: Added CachedUniformRandomProviderFactory --- .../CachedUniformRandomProviderFactory.java | 206 ++++++++++++++++ ...achedUniformRandomProviderFactoryTest.java | 219 ++++++++++++++++++ 2 files changed, 425 insertions(+) create mode 100644 commons-rng-simple/src/main/java/org/apache/commons/rng/simple/CachedUniformRandomProviderFactory.java create mode 100644 commons-rng-simple/src/test/java/org/apache/commons/rng/simple/CachedUniformRandomProviderFactoryTest.java diff --git a/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/CachedUniformRandomProviderFactory.java b/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/CachedUniformRandomProviderFactory.java new file mode 100644 index 000000000..7bad57d97 --- /dev/null +++ b/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/CachedUniformRandomProviderFactory.java @@ -0,0 +1,206 @@ +/* + * 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 org.apache.commons.rng.core.source32.IntProvider; +import org.apache.commons.rng.core.source64.LongProvider; +import org.apache.commons.rng.core.util.NumberFactory; + +/** + * Factory class for wrapping instances of {@link UniformRandomProvider} to cache + * values that can be reused to provision the interface methods. + * + *

The int values generated by an {@link IntProvider} can be cached to enable + * fast provision of {@link UniformRandomProvider#nextBoolean()}. + + *

The long values generated by a {@link LongProvider} can be cached to enable + * fast provision of {@link UniformRandomProvider#nextBoolean()}, + * {@link UniformRandomProvider#nextInt()}, and {@link UniformRandomProvider#nextInt(int)}. + */ +public final class CachedUniformRandomProviderFactory { + + /** + * Class contains only static methods. + */ + private CachedUniformRandomProviderFactory() {} + + /** + * Marker interface to avoid double wrapping a Cached provider. + */ + private interface CachedUniformRandomProvider { + // Marker interface + } + + /** + * Wrap an IntProvider instance to enable fast provision of + * {@link UniformRandomProvider#nextBoolean()}. + */ + static class CachedIntProvider + extends IntProvider + implements CachedUniformRandomProvider { + + /** The underlying source of randomness. */ + protected final UniformRandomProvider rng; + + /** + * The cached value from a call to random UniformRandomProvider#nextInt(). + * + *

Provides a bit source for booleans. + */ + private int booleanSource; // Initialised as 0 + + /** + * The bit mask of the boolean source to obtain the boolean bit. + * + *

The bit mask contains a single bit set. + * This begins at the most significant bit and is gradually shifted + * to zero. + */ + private int booleanBitMask; // Initialised as 0 + + /** + * Create a new instance. + * + * @param rng the source of randomness + */ + CachedIntProvider(IntProvider rng) { + this.rng = rng; + } + + @Override + public int next() { + // Delegate this + return rng.nextInt(); + } + + @Override + public boolean nextBoolean() { + if (booleanBitMask == 0) { + // Get the next value + booleanSource = nextInt(); + booleanBitMask = 1; + } + final boolean next = (booleanSource & booleanBitMask) == 0; + // Shift up. This will eventually overflow and become zero. + booleanBitMask <<= 1; + return next; + } + } + + /** + * Wrap an IntProvider instance to enable fast provision of + * {@link UniformRandomProvider#nextBoolean()}. + */ + static class CachedLongProvider + extends LongProvider + implements CachedUniformRandomProvider { + + /** The underlying source of randomness. */ + protected final UniformRandomProvider rng; + + /** + * The cached value from a call to random UniformRandomProvider#nextInt(). + * + *

Provides a bit source for booleans. + */ + private long booleanSource; // Initialised as 0 + + /** + * The bit mask of the boolean source to obtain the boolean bit. + * + *

The bit mask contains a single bit set. + * This begins at the most significant bit and is gradually shifted + * to zero. + */ + private long booleanBitMask; // Initialised as 0 + + /** The upper 32-bits from a call to UniformRandomProvider#nextLong(). */ + private int nextIntValue; + + /** Flag to indicate an int value has been cached. */ + private boolean cachedIntValue; // Initialised as false + + /** + * Create a new instance. + * + * @param rng the source of randomness + */ + CachedLongProvider(LongProvider rng) { + this.rng = rng; + } + + @Override + public long next() { + // Delegate this + return rng.nextLong(); + } + + @Override + public int nextInt() { + if (cachedIntValue) { + cachedIntValue = false; + return nextIntValue; + } + final long sample = rng.nextLong(); + nextIntValue = NumberFactory.extractLo(sample); + cachedIntValue = true; + return NumberFactory.extractHi(sample); + } + + @Override + public boolean nextBoolean() { + if (booleanBitMask == 0) { + // Get the next value + booleanSource = nextLong(); + booleanBitMask = 1; + } + final boolean next = (booleanSource & booleanBitMask) == 0; + // Shift up. This will eventually overflow and become zero. + booleanBitMask <<= 1; + return next; + } + } + + /** + * Wrap the source of randomness. + * + *

The returned provider will cache values from an {@link IntProvider} or + * {@link LongProvider} to enable fast provision of + * {@link UniformRandomProvider#nextBoolean()}, + * and in the case of a {@link LongProvider} also {@link UniformRandomProvider#nextInt()}. + * + *

If the source of randomness cannot be wrapped then it is returned unmodified. + * + * @param rng the source of randomness + * @return the wrapped uniform random provider + */ + public static UniformRandomProvider wrap(UniformRandomProvider rng) { + // Avoid double wrapping + if (rng instanceof CachedUniformRandomProvider) { + return rng; + } + if (rng instanceof LongProvider) { + return new CachedLongProvider((LongProvider)rng); + } + if (rng instanceof IntProvider) { + return new CachedIntProvider((IntProvider)rng); + } + // Unknown implementation + return rng; + } +} diff --git a/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/CachedUniformRandomProviderFactoryTest.java b/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/CachedUniformRandomProviderFactoryTest.java new file mode 100644 index 000000000..1b67599af --- /dev/null +++ b/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/CachedUniformRandomProviderFactoryTest.java @@ -0,0 +1,219 @@ +/* + * 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.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; + +import org.apache.commons.rng.UniformRandomProvider; +import org.apache.commons.rng.core.source32.IntProvider; +import org.apache.commons.rng.core.source64.LongProvider; +import org.apache.commons.rng.core.util.NumberFactory; +import org.junit.Assert; +import org.junit.Test; + +/** + * Tests the cached UniformRandomProvider correctly return values. + */ +public class CachedUniformRandomProviderFactoryTest { + + /** + * Provide an empty implementation of UniformRandomProvider. + */ + static class EmptyUniformRandomProvider implements UniformRandomProvider { + @Override + public void nextBytes(byte[] bytes) { /* Do nothing */ } + @Override + public void nextBytes(byte[] bytes, int start, int len) { /* Do nothing */ } + @Override + public int nextInt() { return 0; } + @Override + public int nextInt(int n) { return 0; } + @Override + public long nextLong() { return 0; } + @Override + public long nextLong(long n) { return 0; } + @Override + public boolean nextBoolean() { return false; } + @Override + public float nextFloat() { return 0; } + @Override + public double nextDouble() { return 0; } + } + + @Test + public void testUnsupportedUniformRandomProviderIsNotWrapped() { + // Create an unsupported implementation + final UniformRandomProvider rng = new EmptyUniformRandomProvider(); + final UniformRandomProvider rng2 = CachedUniformRandomProviderFactory.wrap(rng); + Assert.assertSame(rng, rng2); + } + + @Test + public void testWrappedUniformRandomProviderIsNotWrapped() { + // Create an supported implementation + final UniformRandomProvider rng = new IntProvider() { + @Override + public int next() { + return 0; + } + }; + // Wrap first time + final UniformRandomProvider rng2 = CachedUniformRandomProviderFactory.wrap(rng); + Assert.assertNotSame("Single wrapped", rng, rng2); + // Do not double wrap + final UniformRandomProvider rng3 = CachedUniformRandomProviderFactory.wrap(rng2); + Assert.assertSame("Double wrapped", rng2, rng3); + } + + @Test + public void testIntProviderNextBoolean() { + final Random random = new Random(); + final int size = 32; + // Test all possible number of bits + for (int bitsPerRepeat = 0; bitsPerRepeat <= size; bitsPerRepeat++) { + // Test an IntProvider that returns the given number of bits. + final int bits = bitsPerRepeat; + final UniformRandomProvider rng = new IntProvider() { + @Override + public int next() { + // Generate a value with the correct number of bits set + final List list = createList(bits, size); + Collections.shuffle(list, random); + int v = 0; + for (int i = 0; i < size; i++) { + // An unset bit corresponds to true + if (!list.get(i)) { + v |= (1 << i); + } + } + return v; + } + }; + // Zero bits will be counted as true the bitsPerRepeat + // starts at zero an increases + testNextBoolean(rng, bitsPerRepeat, size); + } + } + + @Test + public void testLongProviderNextBoolean() { + final Random random = new Random(); + final int size = 64; + // Test all possible number of bits + for (int bitsPerRepeat = 0; bitsPerRepeat <= size; bitsPerRepeat++) { + // Test an IntProvider that returns the given number of bits. + final int bits = bitsPerRepeat; + final UniformRandomProvider rng = new LongProvider() { + @Override + public long next() { + // Generate a value with the correct number of bits set + final List list = createList(bits, size); + Collections.shuffle(list, random); + long v = 0; + for (int i = 0; i < size; i++) { + // An unset bit corresponds to true + if (!list.get(i)) { + v |= (1L << i); + } + } + return v; + } + }; + // Zero bits will be counted as true the bitsPerRepeat + // starts at zero an increases + testNextBoolean(rng, bitsPerRepeat, size); + } + } + + /** + * Create a list of the given size with the specified number of bits set as + * 'true' values. + * + * @param bits the bits + * @param size the size + * @return the list + */ + private static List createList(int bits, + int size) { + final ArrayList list = new ArrayList(size); + for (int i = 0; i < bits; i++) { + list.add(true); + } + while (list.size() < size) { + list.add(false); + } + return list; + } + + /** + * Test nextBoolean() by calling repeatedly and checking the + * correct number of 'true' boolean bits were returned per repeat. + * + * @param source the source + * @param trueBitsPerRepeat the bits per repeat that are 'true' + * @param totalBitsPerRepeat the total bits in each repeat (must be a factor of 32) + */ + private static void testNextBoolean(UniformRandomProvider source, + int trueBitsPerRepeat, + int totalBitsPerRepeat) { + final UniformRandomProvider rng = CachedUniformRandomProviderFactory.wrap(source); + int count = 0; + final int total = 64 * 2; // Factor of 64 + for (int i = 0; i < total; i++) { + if (rng.nextBoolean()) { + count++; + } + } + final int repeats = total / totalBitsPerRepeat; + final int expected = trueBitsPerRepeat * repeats; + Assert.assertEquals("Incorrect number of 'true' bits", expected, count); + } + + @Test + public void testLongProviderNextInt() { + // Generate some random ints and pack them into longs + final int[] expected = new int[50]; + final long[] values = new long[expected.length / 2]; + final Random random = new Random(); + for (int i = 0, j = 0; i < expected.length; i += 2, j++) { + final int i1 = random.nextInt(); + final int i2 = random.nextInt(); + expected[i] = i1; + expected[i + 1] = i2; + values[j] = NumberFactory.makeLong(i1, i2); + } + + // Test a LongProvider that returns the given sequence values + final UniformRandomProvider source = new LongProvider() { + int count = 0; + @Override + public long next() { + return values[count++]; + } + }; + + final int[] actual = new int[expected.length]; + final UniformRandomProvider rng = CachedUniformRandomProviderFactory.wrap(source); + for (int i = 0; i < expected.length; i ++) { + actual[i] = rng.nextInt(); + } + Assert.assertArrayEquals("Invalid int sequence", expected, actual); + } +} From 7d321ca701ad24ebf61a55e4e15348222bc07a8b Mon Sep 17 00:00:00 2001 From: aherbert Date: Tue, 25 Sep 2018 13:06:12 +0100 Subject: [PATCH 2/6] RNG-57: Added JMH benchmark for the cached provider performance --- .../jmh/CachedGenerationPerformance.java | 202 ++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 commons-rng-examples/examples-jmh/src/main/java/org/apache/commons/rng/examples/jmh/CachedGenerationPerformance.java diff --git a/commons-rng-examples/examples-jmh/src/main/java/org/apache/commons/rng/examples/jmh/CachedGenerationPerformance.java b/commons-rng-examples/examples-jmh/src/main/java/org/apache/commons/rng/examples/jmh/CachedGenerationPerformance.java new file mode 100644 index 000000000..065ad3481 --- /dev/null +++ b/commons-rng-examples/examples-jmh/src/main/java/org/apache/commons/rng/examples/jmh/CachedGenerationPerformance.java @@ -0,0 +1,202 @@ +/* + * 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.examples.jmh; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.infra.Blackhole; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.rng.UniformRandomProvider; +import org.apache.commons.rng.simple.CachedUniformRandomProviderFactory; +import org.apache.commons.rng.simple.RandomSource; +import org.apache.commons.rng.core.source32.IntProvider; +import org.apache.commons.rng.core.source64.LongProvider; + +/** + * Executes benchmark to compare the speed of generation of random numbers + * from the various source providers if wrapped with a cached provider. + */ +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@State(Scope.Benchmark) +@Fork(value = 1, jvmArgs = {"-server", "-Xms128M", "-Xmx128M"}) +public class CachedGenerationPerformance { + /** Number of samples per run. */ + private static final int NUM_SAMPLES = 1000000; + + /** + * Retrieve the various "RandomSource"s for testing. + * + *

All source use an int source of randomness (see {@link IntProvider}). + */ + @State(Scope.Benchmark) + public static class SourcesInt { + /** + * RNG providers. + */ + @Param({"JDK", + "WELL_512_A", + "WELL_1024_A", + "WELL_19937_A", + "WELL_19937_C", + "WELL_44497_A", + "WELL_44497_B", + "MT", + "ISAAC", + "MWC_256", + "KISS"}) + private String randomSourceName; + + /** RNG. */ + private UniformRandomProvider provider; + + /** + * @return the RNG. + */ + public UniformRandomProvider getGenerator() { + return provider; + } + + /** Instantiates generator. */ + @Setup + public void setup() { + final RandomSource randomSource = RandomSource.valueOf(randomSourceName); + provider = RandomSource.create(randomSource); + } + } + + /** + * The benchmark state (retrieve the various "RandomSource"s). + * + *

All source use a long source of randomness (see {@link LongProvider}). + */ + @State(Scope.Benchmark) + public static class SourcesLong { + /** + * RNG providers. + */ + @Param({"SPLIT_MIX_64", + "XOR_SHIFT_1024_S", + "TWO_CMRES", + "MT_64"}) + private String randomSourceName; + + /** RNG. */ + private UniformRandomProvider provider; + + /** + * @return the RNG. + */ + public UniformRandomProvider getGenerator() { + return provider; + } + + /** Instantiates generator. */ + @Setup + public void setup() { + final RandomSource randomSource = RandomSource.valueOf(randomSourceName); + provider = RandomSource.create(randomSource); + } + } + + /** + * Flag to indicate the provider should be wrapped with a cache. + */ + @Param({"false", "true"}) + private boolean cache; + + /** + * Exercises {@link UniformRandomProvider#nextBoolean()}. + * + * @param sampler Sampler. + * @param bh Data sink. + */ + private static void runNextBoolean(UniformRandomProvider rng, + Blackhole bh) { + for (int i = 0; i < NUM_SAMPLES; i++) { + bh.consume(rng.nextBoolean()); + } + } + + /** + * Exercises {@link UniformRandomProvider#nextInt()}. + * + * @param sampler Sampler. + * @param bh Data sink. + */ + private static void runNextInt(UniformRandomProvider rng, + Blackhole bh) { + for (int i = 0; i < NUM_SAMPLES; i++) { + bh.consume(rng.nextInt()); + } + } + + /** + * @param sources Source of randomness. + * @param bh Data sink. + */ + @Benchmark + public void nextBooleanIntProvider(SourcesInt sources, + Blackhole bh) { + UniformRandomProvider rng = sources.getGenerator(); + if (cache) { + rng = CachedUniformRandomProviderFactory.wrap(rng); + } + runNextBoolean(rng, bh); + } + + /** + * @param sources Source of randomness. + * @param bh Data sink. + */ + @Benchmark + public void nextBooleanLongProvider(SourcesLong sources, + Blackhole bh) { + UniformRandomProvider rng = sources.getGenerator(); + if (cache) { + rng = CachedUniformRandomProviderFactory.wrap(rng); + } + runNextBoolean(rng, bh); + } + + /** + * @param sources Source of randomness. + * @param bh Data sink. + */ + @Benchmark + public void nextIntLongProvider(SourcesLong sources, + Blackhole bh) { + UniformRandomProvider rng = sources.getGenerator(); + if (cache) { + rng = CachedUniformRandomProviderFactory.wrap(rng); + } + runNextInt(rng, bh); + } +} From 0010f2fc73b3783d2cb772fe9a5a06a203788423 Mon Sep 17 00:00:00 2001 From: aherbert Date: Sun, 30 Sep 2018 14:38:55 +0100 Subject: [PATCH 3/6] RNG-57: Latest version of different cache method for testing --- .../jmh/CachedGenerationPerformance.java | 158 ++++- .../CachedUniformRandomProviderFactory.java | 613 ++++++++++++++++-- ...achedUniformRandomProviderFactoryTest.java | 8 +- 3 files changed, 722 insertions(+), 57 deletions(-) diff --git a/commons-rng-examples/examples-jmh/src/main/java/org/apache/commons/rng/examples/jmh/CachedGenerationPerformance.java b/commons-rng-examples/examples-jmh/src/main/java/org/apache/commons/rng/examples/jmh/CachedGenerationPerformance.java index 065ad3481..1e60648ae 100644 --- a/commons-rng-examples/examples-jmh/src/main/java/org/apache/commons/rng/examples/jmh/CachedGenerationPerformance.java +++ b/commons-rng-examples/examples-jmh/src/main/java/org/apache/commons/rng/examples/jmh/CachedGenerationPerformance.java @@ -51,6 +51,112 @@ public class CachedGenerationPerformance { /** Number of samples per run. */ private static final int NUM_SAMPLES = 1000000; + /** Used to create fixed values for the Flip providers. */ + private static final UniformRandomProvider random = + RandomSource.create(RandomSource.MWC_256); + + /** + * Name for the special test provider that flips bits. + */ + private static final String FLIP_NAME = "FLIP"; + + /** + * Name for the special test provider that swaps a value. + */ + private static final String SWAP_NAME = "SWAP"; + + /** + * Simple test class to flip the bits in an int and return it. + * + *

Flipping the bits ensures that repeat sampling booleans should be 50:50 + * true:false. + */ + private static final class IntFlip extends IntProvider { + + /** The value. This is flipped on each call to next(). */ + private int value; + + IntFlip(int seed) { + value = seed; + } + + @Override + public int next() { + // Flip the bits. + value = ~value; + return value; + } + } + + /** + * Simple test class to flip the bits in a long and return it. + * + *

Flipping the bits ensures that repeat sampling booleans should be 50:50 + * true:false. + */ + private static final class LongFlip extends LongProvider { + + /** The value. This is flipped on each call to next(). */ + private long value; + + LongFlip(long seed) { + value = seed; + } + + @Override + public long next() { + // Flip the bits. + value = ~value; + return value; + } + } + + /** + * Simple test class to swap the upper and lower bits in an int and return it. + * + *

Swapping the bits ensures that repeat sampling booleans should be 50:50 + * true:false. + */ + private static final class IntSwap extends IntProvider { + + /** The value. This is swapped on each call to next(). */ + private int value; + + IntSwap(int seed) { + value = seed; + } + + @Override + public int next() { + // Swap the bits. + value = (value << Short.SIZE) | (value >>> Short.SIZE); + return value; + } + } + + /** + * Simple test class to swap the upper and lower bits in a long and return it. + * + *

Swapping the bits ensures that repeat sampling booleans should be 50:50 + * true:false. + */ + private static final class LongSwap extends LongProvider { + + /** The value. This is swapped on each call to next(). */ + private long value; + + LongSwap(long seed) { + value = seed; + } + + @Override + public long next() { + // Swap the bits. + value = (value << Integer.SIZE) | (value >>> Integer.SIZE); + return value; + } + } + /** * Retrieve the various "RandomSource"s for testing. * @@ -61,7 +167,8 @@ public static class SourcesInt { /** * RNG providers. */ - @Param({"JDK", + @Param({ + "JDK", "WELL_512_A", "WELL_1024_A", "WELL_19937_A", @@ -71,7 +178,10 @@ public static class SourcesInt { "MT", "ISAAC", "MWC_256", - "KISS"}) + "KISS", + FLIP_NAME, + SWAP_NAME + }) private String randomSourceName; /** RNG. */ @@ -87,8 +197,14 @@ public UniformRandomProvider getGenerator() { /** Instantiates generator. */ @Setup public void setup() { - final RandomSource randomSource = RandomSource.valueOf(randomSourceName); - provider = RandomSource.create(randomSource); + if (randomSourceName.equals(FLIP_NAME)) { + provider = new IntFlip(random.nextInt()); + } else if (randomSourceName.equals(SWAP_NAME)) { + provider = new IntSwap(random.nextInt()); + } else { + final RandomSource randomSource = RandomSource.valueOf(randomSourceName); + provider = RandomSource.create(randomSource); + } } } @@ -102,10 +218,14 @@ public static class SourcesLong { /** * RNG providers. */ - @Param({"SPLIT_MIX_64", + @Param({ + "SPLIT_MIX_64", "XOR_SHIFT_1024_S", "TWO_CMRES", - "MT_64"}) + "MT_64", + FLIP_NAME, + SWAP_NAME + }) private String randomSourceName; /** RNG. */ @@ -121,16 +241,22 @@ public UniformRandomProvider getGenerator() { /** Instantiates generator. */ @Setup public void setup() { - final RandomSource randomSource = RandomSource.valueOf(randomSourceName); - provider = RandomSource.create(randomSource); + if (randomSourceName.equals(FLIP_NAME)) { + provider = new LongFlip(random.nextLong()); + } else if (randomSourceName.equals(SWAP_NAME)) { + provider = new LongSwap(random.nextLong()); + } else { + final RandomSource randomSource = RandomSource.valueOf(randomSourceName); + provider = RandomSource.create(randomSource); + } } } /** * Flag to indicate the provider should be wrapped with a cache. */ - @Param({"false", "true"}) - private boolean cache; + @Param({"0", "1", "2", "3", "4"}) + private int cacheMethod; /** * Exercises {@link UniformRandomProvider#nextBoolean()}. @@ -166,8 +292,8 @@ private static void runNextInt(UniformRandomProvider rng, public void nextBooleanIntProvider(SourcesInt sources, Blackhole bh) { UniformRandomProvider rng = sources.getGenerator(); - if (cache) { - rng = CachedUniformRandomProviderFactory.wrap(rng); + if (cacheMethod != 0) { + rng = CachedUniformRandomProviderFactory.wrap(rng, cacheMethod); } runNextBoolean(rng, bh); } @@ -180,8 +306,8 @@ public void nextBooleanIntProvider(SourcesInt sources, public void nextBooleanLongProvider(SourcesLong sources, Blackhole bh) { UniformRandomProvider rng = sources.getGenerator(); - if (cache) { - rng = CachedUniformRandomProviderFactory.wrap(rng); + if (cacheMethod != 0) { + rng = CachedUniformRandomProviderFactory.wrap(rng, cacheMethod); } runNextBoolean(rng, bh); } @@ -194,8 +320,8 @@ public void nextBooleanLongProvider(SourcesLong sources, public void nextIntLongProvider(SourcesLong sources, Blackhole bh) { UniformRandomProvider rng = sources.getGenerator(); - if (cache) { - rng = CachedUniformRandomProviderFactory.wrap(rng); + if (cacheMethod != 0) { + rng = CachedUniformRandomProviderFactory.wrap(rng, cacheMethod); } runNextInt(rng, bh); } diff --git a/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/CachedUniformRandomProviderFactory.java b/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/CachedUniformRandomProviderFactory.java index 7bad57d97..b9279e988 100644 --- a/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/CachedUniformRandomProviderFactory.java +++ b/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/CachedUniformRandomProviderFactory.java @@ -19,7 +19,6 @@ import org.apache.commons.rng.UniformRandomProvider; import org.apache.commons.rng.core.source32.IntProvider; import org.apache.commons.rng.core.source64.LongProvider; -import org.apache.commons.rng.core.util.NumberFactory; /** * Factory class for wrapping instances of {@link UniformRandomProvider} to cache @@ -29,8 +28,8 @@ * fast provision of {@link UniformRandomProvider#nextBoolean()}. *

The long values generated by a {@link LongProvider} can be cached to enable - * fast provision of {@link UniformRandomProvider#nextBoolean()}, - * {@link UniformRandomProvider#nextInt()}, and {@link UniformRandomProvider#nextInt(int)}. + * fast provision of {@link UniformRandomProvider#nextBoolean()} and + * {@link UniformRandomProvider#nextInt()}. */ public final class CachedUniformRandomProviderFactory { @@ -50,17 +49,17 @@ private interface CachedUniformRandomProvider { * Wrap an IntProvider instance to enable fast provision of * {@link UniformRandomProvider#nextBoolean()}. */ - static class CachedIntProvider + static final class CachedIntProvider extends IntProvider implements CachedUniformRandomProvider { /** The underlying source of randomness. */ - protected final UniformRandomProvider rng; + private final UniformRandomProvider rng; /** - * The cached value from a call to random UniformRandomProvider#nextInt(). + * Provides a bit source for booleans. * - *

Provides a bit source for booleans. + *

A cached value from a call to random {@link UniformRandomProvider#nextInt()}. */ private int booleanSource; // Initialised as 0 @@ -68,8 +67,11 @@ static class CachedIntProvider * The bit mask of the boolean source to obtain the boolean bit. * *

The bit mask contains a single bit set. - * This begins at the most significant bit and is gradually shifted - * to zero. + * This begins at the least significant bit and is gradually shifted + * upwards until overflow to zero. + * + *

When zero a new boolean source should be created and the mask + * set to the least significant bit (i.e. 1). */ private int booleanBitMask; // Initialised as 0 @@ -90,33 +92,212 @@ public int next() { @Override public boolean nextBoolean() { + // Shift up. This will eventually overflow and become zero. + booleanBitMask <<= 1; + // The mask will either contain a single bit or none. if (booleanBitMask == 0) { - // Get the next value - booleanSource = nextInt(); + // Set the least significant bit booleanBitMask = 1; + // Get the next value + booleanSource = rng.nextInt(); } - final boolean next = (booleanSource & booleanBitMask) == 0; - // Shift up. This will eventually overflow and become zero. - booleanBitMask <<= 1; - return next; + // Return if the bit is set + return (booleanSource & booleanBitMask) != 0; + } + } + + /** + * Wrap an IntProvider instance to enable fast provision of + * {@link UniformRandomProvider#nextBoolean()}. + */ + static final class CachedIntProvider2 + extends IntProvider + implements CachedUniformRandomProvider { + + /** The underlying source of randomness. */ + private final UniformRandomProvider rng; + + /** + * Provides a bit source for booleans. + * + *

A cached value from a call to random {@link UniformRandomProvider#nextInt()}. + */ + private int booleanSource; // Initialised as 0 + + /** + * The number of significant bits to discard. + * + *

The significant bits are discarded using a shift up. This is used + * with values from 31 to 1. When zero a new boolean source is generated. + */ + private int discardShift; // Initialised as 0 + + /** + * Create a new instance. + * + * @param rng the source of randomness + */ + CachedIntProvider2(IntProvider rng) { + this.rng = rng; + } + + @Override + public int next() { + // Delegate this + return rng.nextInt(); + } + + @Override + public boolean nextBoolean() { + // Check the current shift. + // In multi-thread use the decrement can pass zero + // so use <= and not ==. + // Note: Current RNG are not thread safe though. + if (--discardShift <= 0) { + // Set shift to the size of an int + discardShift = Integer.SIZE; + // Get the next value + booleanSource = rng.nextInt(); + // Check the most significant bit + return (booleanSource >>> 31) != 0; + } + // The discard shift is in the range 31 to 1 + return ((booleanSource << discardShift) >>> 31) != 0; } } + /** * Wrap an IntProvider instance to enable fast provision of * {@link UniformRandomProvider#nextBoolean()}. */ - static class CachedLongProvider + static final class CachedIntProvider3 + extends IntProvider + implements CachedUniformRandomProvider { + + /** The underlying source of randomness. */ + private final UniformRandomProvider rng; + + /** + * Provides a bit source for booleans. + * + *

A cached value from a call to random {@link UniformRandomProvider#nextInt()}. + */ + private int booleanSource; // Initialised as 0 + + /** + * The number of significant bits to discard. + * + *

The significant bits are discarded using a shift up. This is used + * with values from 31 to 1. When zero a new boolean source is generated. + */ + private int discardShift; // Initialised as 0 + + /** + * Create a new instance. + * + * @param rng the source of randomness + */ + CachedIntProvider3(IntProvider rng) { + this.rng = rng; + } + + @Override + public int next() { + // Delegate this + return rng.nextInt(); + } + + @Override + public boolean nextBoolean() { + // Check the current shift. + // In multi-thread use the decrement can pass zero + // so use <= and not ==. + // Note: Current RNG are not thread safe though. + if (discardShift <= 0) { + // Set to the bit size of an int + discardShift = Integer.SIZE; + // Get the next value + booleanSource = rng.nextInt(); + } + return ((booleanSource << --discardShift) >>> 31) != 0; + } + } + + /** + * Wrap an IntProvider instance to enable fast provision of + * {@link UniformRandomProvider#nextBoolean()}. + */ + static final class CachedIntProvider4 + extends IntProvider + implements CachedUniformRandomProvider { + + /** The underlying source of randomness. */ + private final UniformRandomProvider rng; + + /** + * Provides a bit source for booleans. + * + *

A cached value from a call to random {@link UniformRandomProvider#nextInt()}. + */ + private int booleanSource; // Initialised as 0 + + /** + * A count of the number of bits used in the boolean source. + * + *

This is initialised using the least significant bit and gradually + * shifted up until overflow resets it to zero. + */ + private int shiftCounter; // Initialised as 0 + + /** + * Create a new instance. + * + * @param rng the source of randomness + */ + CachedIntProvider4(IntProvider rng) { + this.rng = rng; + } + + @Override + public int next() { + // Delegate this + return rng.nextInt(); + } + + @Override + public boolean nextBoolean() { + // Shift up. This will eventually reset to zero. + shiftCounter <<= 1; + if (shiftCounter == 0) { + // Set the counter least significant bit + shiftCounter = 1; + // Get the next value + booleanSource = rng.nextInt(); + } else { + // Consume the last used most significant bit from the source + booleanSource <<= 1; + } + // Return the boolean using the most significant bit. + return (booleanSource >>> 31) != 0; + } + } + + /** + * Wrap a LongProvider instance to enable fast provision of + * {@link UniformRandomProvider#nextBoolean()}. + */ + static final class CachedLongProvider extends LongProvider implements CachedUniformRandomProvider { /** The underlying source of randomness. */ - protected final UniformRandomProvider rng; + private final UniformRandomProvider rng; /** - * The cached value from a call to random UniformRandomProvider#nextInt(). + * Provides a bit source for booleans. * - *

Provides a bit source for booleans. + *

A cached value from a call to random {@link UniformRandomProvider#nextLong()}. */ private long booleanSource; // Initialised as 0 @@ -124,12 +305,100 @@ static class CachedLongProvider * The bit mask of the boolean source to obtain the boolean bit. * *

The bit mask contains a single bit set. - * This begins at the most significant bit and is gradually shifted - * to zero. + * This begins at the least significant bit and is gradually shifted + * upwards until overflow to zero. + * + *

When zero a new boolean source should be created and the mask + * set to the least significant bit (i.e. 1). */ private long booleanBitMask; // Initialised as 0 - /** The upper 32-bits from a call to UniformRandomProvider#nextLong(). */ + /** + * Provides a source for ints. + * + *

A cached value from a call to random {@link UniformRandomProvider#nextLong()}. + */ + private long intSource; + + /** Flag to indicate an int source has been cached. */ + private boolean cachedIntSource; // Initialised as false + + /** + * Create a new instance. + * + * @param rng the source of randomness + */ + CachedLongProvider(LongProvider rng) { + this.rng = rng; + } + + @Override + public long next() { + // Delegate this + return rng.nextLong(); + } + + @Override + public boolean nextBoolean() { + // Shift up. This will eventually overflow and become zero. + booleanBitMask <<= 1; + // The mask will either contain a single bit or none. + if (booleanBitMask == 0) { + // Set the least significant bit + booleanBitMask = 1; + // Get the next value + booleanSource = rng.nextLong(); + } + // Return if the bit is set + return (booleanSource & booleanBitMask) != 0; + } + + @Override + public int nextInt() { + // Directly store and use the long value as a source for ints + if (cachedIntSource) { + // Consume the cache value + cachedIntSource = false; + // Return the lower 32 bits + return (int) intSource; + } + // Fill the cache + cachedIntSource = true; + intSource = rng.nextLong(); + // Return the upper 32 bits + return (int) (intSource >>> Integer.SIZE); + } + } + + /** + * Wrap a LongProvider instance to enable fast provision of + * {@link UniformRandomProvider#nextBoolean()}. + * + *

This class exists for testing the nextBoolean() using a cached nextInt(). + */ + static final class CachedLongProvider2 + extends LongProvider + implements CachedUniformRandomProvider { + + /** The underlying source of randomness. */ + private final UniformRandomProvider rng; + + /** + * Provides a bit source for booleans. + * + *

A cached value from a call to random {@link UniformRandomProvider#nextLong()}. + */ + private long booleanSource; // Initialised as 0 + + /** + * The number of significant bits to discard. + * + *

The significant bits are discarded using a shift up. This is used + * with values from 63 to 1. When zero a new boolean source is generated. + */ + private int discardShift; // Initialised as 0 + + /** The upper 32-bits from a call to {@link UniformRandomProvider#nextLong()}. */ private int nextIntValue; /** Flag to indicate an int value has been cached. */ @@ -140,7 +409,7 @@ static class CachedLongProvider * * @param rng the source of randomness */ - CachedLongProvider(LongProvider rng) { + CachedLongProvider2(LongProvider rng) { this.rng = rng; } @@ -150,29 +419,261 @@ public long next() { return rng.nextLong(); } + @Override + public boolean nextBoolean() { + // Check the current shift. + // In multi-thread use the decrement can pass zero + // so use <= and not ==. + // Note: Current RNG are not thread safe though. + if (--discardShift <= 0) { + // Set shift to the size of a long + discardShift = Long.SIZE; + // Get the next value + booleanSource = rng.nextLong(); + // Check the most significant bit + return (booleanSource >>> 63) != 0; + } + // The discard shift is in the range 63 to 1 + return ((booleanSource << discardShift) >>> 63) != 0; + } + @Override public int nextInt() { + // Cache a computed int value if (cachedIntValue) { + // Consume the cache value cachedIntValue = false; return nextIntValue; } - final long sample = rng.nextLong(); - nextIntValue = NumberFactory.extractLo(sample); cachedIntValue = true; - return NumberFactory.extractHi(sample); + // Split a 64-bit long into two 32-bit int values + final long sample = rng.nextLong(); + // Cache one value + nextIntValue = (int) sample; + // Return the other + return (int) (sample >>> Integer.SIZE); + } + } + + /** + * Wrap a LongProvider instance to enable fast provision of + * {@link UniformRandomProvider#nextBoolean()}. + */ + static final class CachedLongProvider3 + extends LongProvider + implements CachedUniformRandomProvider { + + /** The underlying source of randomness. */ + private final UniformRandomProvider rng; + + /** + * Provides a bit source for booleans. + * + *

A cached value from a call to random {@link UniformRandomProvider#nextLong()}. + */ + private long booleanSource; // Initialised as 0 + + /** + * The number of significant bits to discard. + * + *

The significant bits are discarded using a shift up. This is used + * with values from 63 to 1. When zero a new boolean source is generated. + */ + private int discardShift; // Initialised as 0 + + /** + * Provides a source for ints. + * + *

A cached value from a call to random {@link UniformRandomProvider#nextLong()}. + */ + private long intSource; + + /** A cyclic linked list for {@code int} generation. */ + private NextIntNode nextIntNode; + + /** + * Base node for constructing a cyclic linked list for {@code int} generation. + */ + private abstract class NextIntNode { + /** The next node in the cyclic linked-list. */ + private NextIntNode next; + /** + * Get the next int sample. + * + * @return the int + */ + abstract int nextInt(); + /** + * Gets the next node. + * + * @return the next + */ + NextIntNode getNext() { + return next; + } + /** + * Sets the next node. + * + * @param next the new next + */ + void setNext(NextIntNode next) { + this.next = next; + } + } + + /** + * The generator node that creates the {@code int} values. + */ + private final class GeneratorNextIntNode extends NextIntNode { + @Override + int nextInt() { + intSource = rng.nextLong(); + // Return the upper 32 bits + return (int) (intSource >>> Integer.SIZE); + } + } + + /** + * The cached node that uses the generated {@code int} values. + */ + private final class CachedNextIntNode extends NextIntNode { + @Override + int nextInt() { + // Return the upper 32 bits + return (int) (intSource >>> Integer.SIZE); + } + } + + /** + * Create a new instance. + * + * @param rng the source of randomness + */ + CachedLongProvider3(LongProvider rng) { + this.rng = rng; + // Create the cyclic linked list. + // This is only two nodes. + NextIntNode head = new GeneratorNextIntNode(); + NextIntNode tail = new CachedNextIntNode(); + head.setNext(tail); + tail.setNext(head); + // Ensure that the first move is to the head generator node + this.nextIntNode = tail; + } + + @Override + public long next() { + // Delegate this + return rng.nextLong(); } @Override public boolean nextBoolean() { - if (booleanBitMask == 0) { + // Check the current shift. + // In multi-thread use the decrement can pass zero + // so use <= and not ==. + // Note: Current RNG are not thread safe though. + if (discardShift <= 0) { + // Set to the bit size of an long + discardShift = Long.SIZE; // Get the next value - booleanSource = nextLong(); - booleanBitMask = 1; + booleanSource = rng.nextLong(); } - final boolean next = (booleanSource & booleanBitMask) == 0; - // Shift up. This will eventually overflow and become zero. - booleanBitMask <<= 1; - return next; + return ((booleanSource << --discardShift) >>> 63) != 0; + } + + @Override + public int nextInt() { + // Use the cyclic linked-list + nextIntNode = nextIntNode.getNext(); + return nextIntNode.nextInt(); + } + } + + /** + * Wrap a LongProvider instance to enable fast provision of + * {@link UniformRandomProvider#nextBoolean()}. + */ + static final class CachedLongProvider4 + extends LongProvider + implements CachedUniformRandomProvider { + + /** The underlying source of randomness. */ + private final UniformRandomProvider rng; + + /** + * Provides a bit source for booleans. + * + *

The cached value from a call to random UniformRandomProvider#nextLong(). + */ + private long booleanSource; // Initialised as 0 + + /** + * A count of the number of bits used in the boolean source. + * + *

This is initialised using the least significant bit and gradually + * shifted up until overflow resets it to zero. + */ + private long shiftCounter; // Initialised as 0 + + /** + * Provides a source for ints. + * + *

A cached value from a call to random {@link UniformRandomProvider#nextLong()}. + */ + private long intSource; + + /** Flag to indicate a long value has been cached. */ + private boolean cachedIntSource; // Initialised as false + + /** + * Create a new instance. + * + * @param rng the source of randomness + */ + CachedLongProvider4(LongProvider rng) { + this.rng = rng; + } + + @Override + public long next() { + // Delegate this + return rng.nextLong(); + } + + @Override + public boolean nextBoolean() { + // Shift up. This will eventually reset to zero. + shiftCounter <<= 1; + if (shiftCounter == 0) { + // Set the counter least significant bit + shiftCounter = 1; + // Get the next value + booleanSource = rng.nextInt(); + } else { + // Consume the last used most significant bit from the source + booleanSource <<= 1; + } + // Return the boolean using the most significant bit. + return (booleanSource >>> 63) != 0; + } + + @Override + public int nextInt() { + // Same as method 1 (consistency test) + + // Directly store and use the long value as a source for ints + if (cachedIntSource) { + // Consume the cache value + cachedIntSource = false; + // Return the lower 32 bits + return (int) intSource; + } + // Fill the cache + cachedIntSource = true; + intSource = rng.nextLong(); + // Return the upper 32 bits + return (int) (intSource >>> Integer.SIZE); } } @@ -180,8 +681,7 @@ public boolean nextBoolean() { * Wrap the source of randomness. * *

The returned provider will cache values from an {@link IntProvider} or - * {@link LongProvider} to enable fast provision of - * {@link UniformRandomProvider#nextBoolean()}, + * {@link LongProvider} to enable fast provision of {@link UniformRandomProvider#nextBoolean()}, * and in the case of a {@link LongProvider} also {@link UniformRandomProvider#nextInt()}. * *

If the source of randomness cannot be wrapped then it is returned unmodified. @@ -190,15 +690,54 @@ public boolean nextBoolean() { * @return the wrapped uniform random provider */ public static UniformRandomProvider wrap(UniformRandomProvider rng) { + return wrap(rng, 1); + } + + /** + * Wrap the source of randomness. + * + *

The returned provider will cache values from an {@link IntProvider} or + * {@link LongProvider} to enable fast provision of {@link UniformRandomProvider#nextBoolean()}, + * and in the case of a {@link LongProvider} also {@link UniformRandomProvider#nextInt()}. + * + *

If the source of randomness cannot be wrapped then it is returned unmodified. + * + * @param rng the source of randomness + * @param method the method + * @return the wrapped uniform random provider + */ + public static UniformRandomProvider wrap(UniformRandomProvider rng, int method) { // Avoid double wrapping if (rng instanceof CachedUniformRandomProvider) { return rng; } if (rng instanceof LongProvider) { - return new CachedLongProvider((LongProvider)rng); + switch (method) { + case 4: + return new CachedLongProvider4((LongProvider)rng); + case 3: + return new CachedLongProvider3((LongProvider)rng); + case 2: + return new CachedLongProvider2((LongProvider)rng); + case 1: + return new CachedLongProvider((LongProvider)rng); + default: + throw new IllegalStateException("not implemented"); + } } if (rng instanceof IntProvider) { - return new CachedIntProvider((IntProvider)rng); + switch (method) { + case 4: + return new CachedIntProvider4((IntProvider)rng); + case 3: + return new CachedIntProvider3((IntProvider)rng); + case 2: + return new CachedIntProvider2((IntProvider)rng); + case 1: + return new CachedIntProvider((IntProvider)rng); + default: + throw new IllegalStateException("not implemented"); + } } // Unknown implementation return rng; diff --git a/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/CachedUniformRandomProviderFactoryTest.java b/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/CachedUniformRandomProviderFactoryTest.java index 1b67599af..ee06c4e48 100644 --- a/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/CachedUniformRandomProviderFactoryTest.java +++ b/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/CachedUniformRandomProviderFactoryTest.java @@ -98,8 +98,8 @@ public int next() { Collections.shuffle(list, random); int v = 0; for (int i = 0; i < size; i++) { - // An unset bit corresponds to true - if (!list.get(i)) { + // An set bit corresponds to true + if (list.get(i)) { v |= (1 << i); } } @@ -128,8 +128,8 @@ public long next() { Collections.shuffle(list, random); long v = 0; for (int i = 0; i < size; i++) { - // An unset bit corresponds to true - if (!list.get(i)) { + // An set bit corresponds to true + if (list.get(i)) { v |= (1L << i); } } From d3b45398757c62fe8b2ca9653a599d5d9cea19a8 Mon Sep 17 00:00:00 2001 From: Alex Herbert Date: Sun, 30 Sep 2018 20:32:05 +0100 Subject: [PATCH 4/6] RNG-57: Switch to wrap RandomIntSource --- .../CachedUniformRandomProviderFactory.java | 100 +++++++++--------- 1 file changed, 51 insertions(+), 49 deletions(-) diff --git a/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/CachedUniformRandomProviderFactory.java b/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/CachedUniformRandomProviderFactory.java index b9279e988..b806f8abb 100644 --- a/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/CachedUniformRandomProviderFactory.java +++ b/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/CachedUniformRandomProviderFactory.java @@ -18,7 +18,9 @@ import org.apache.commons.rng.UniformRandomProvider; import org.apache.commons.rng.core.source32.IntProvider; +import org.apache.commons.rng.core.source32.RandomIntSource; import org.apache.commons.rng.core.source64.LongProvider; +import org.apache.commons.rng.core.source64.RandomLongSource; /** * Factory class for wrapping instances of {@link UniformRandomProvider} to cache @@ -54,7 +56,7 @@ static final class CachedIntProvider implements CachedUniformRandomProvider { /** The underlying source of randomness. */ - private final UniformRandomProvider rng; + private final RandomIntSource rng; /** * Provides a bit source for booleans. @@ -80,14 +82,14 @@ static final class CachedIntProvider * * @param rng the source of randomness */ - CachedIntProvider(IntProvider rng) { + CachedIntProvider(RandomIntSource rng) { this.rng = rng; } @Override public int next() { // Delegate this - return rng.nextInt(); + return rng.next(); } @Override @@ -99,7 +101,7 @@ public boolean nextBoolean() { // Set the least significant bit booleanBitMask = 1; // Get the next value - booleanSource = rng.nextInt(); + booleanSource = rng.next(); } // Return if the bit is set return (booleanSource & booleanBitMask) != 0; @@ -115,7 +117,7 @@ static final class CachedIntProvider2 implements CachedUniformRandomProvider { /** The underlying source of randomness. */ - private final UniformRandomProvider rng; + private final RandomIntSource rng; /** * Provides a bit source for booleans. @@ -137,14 +139,14 @@ static final class CachedIntProvider2 * * @param rng the source of randomness */ - CachedIntProvider2(IntProvider rng) { + CachedIntProvider2(RandomIntSource rng) { this.rng = rng; } @Override public int next() { // Delegate this - return rng.nextInt(); + return rng.next(); } @Override @@ -157,7 +159,7 @@ public boolean nextBoolean() { // Set shift to the size of an int discardShift = Integer.SIZE; // Get the next value - booleanSource = rng.nextInt(); + booleanSource = rng.next(); // Check the most significant bit return (booleanSource >>> 31) != 0; } @@ -176,7 +178,7 @@ static final class CachedIntProvider3 implements CachedUniformRandomProvider { /** The underlying source of randomness. */ - private final UniformRandomProvider rng; + private final RandomIntSource rng; /** * Provides a bit source for booleans. @@ -198,14 +200,14 @@ static final class CachedIntProvider3 * * @param rng the source of randomness */ - CachedIntProvider3(IntProvider rng) { + CachedIntProvider3(RandomIntSource rng) { this.rng = rng; } @Override public int next() { // Delegate this - return rng.nextInt(); + return rng.next(); } @Override @@ -218,7 +220,7 @@ public boolean nextBoolean() { // Set to the bit size of an int discardShift = Integer.SIZE; // Get the next value - booleanSource = rng.nextInt(); + booleanSource = rng.next(); } return ((booleanSource << --discardShift) >>> 31) != 0; } @@ -233,7 +235,7 @@ static final class CachedIntProvider4 implements CachedUniformRandomProvider { /** The underlying source of randomness. */ - private final UniformRandomProvider rng; + private final RandomIntSource rng; /** * Provides a bit source for booleans. @@ -255,14 +257,14 @@ static final class CachedIntProvider4 * * @param rng the source of randomness */ - CachedIntProvider4(IntProvider rng) { + CachedIntProvider4(RandomIntSource rng) { this.rng = rng; } @Override public int next() { // Delegate this - return rng.nextInt(); + return rng.next(); } @Override @@ -273,7 +275,7 @@ public boolean nextBoolean() { // Set the counter least significant bit shiftCounter = 1; // Get the next value - booleanSource = rng.nextInt(); + booleanSource = rng.next(); } else { // Consume the last used most significant bit from the source booleanSource <<= 1; @@ -292,7 +294,7 @@ static final class CachedLongProvider implements CachedUniformRandomProvider { /** The underlying source of randomness. */ - private final UniformRandomProvider rng; + private final RandomLongSource rng; /** * Provides a bit source for booleans. @@ -328,14 +330,14 @@ static final class CachedLongProvider * * @param rng the source of randomness */ - CachedLongProvider(LongProvider rng) { + CachedLongProvider(RandomLongSource rng) { this.rng = rng; } @Override public long next() { // Delegate this - return rng.nextLong(); + return rng.next(); } @Override @@ -347,7 +349,7 @@ public boolean nextBoolean() { // Set the least significant bit booleanBitMask = 1; // Get the next value - booleanSource = rng.nextLong(); + booleanSource = rng.next(); } // Return if the bit is set return (booleanSource & booleanBitMask) != 0; @@ -364,7 +366,7 @@ public int nextInt() { } // Fill the cache cachedIntSource = true; - intSource = rng.nextLong(); + intSource = rng.next(); // Return the upper 32 bits return (int) (intSource >>> Integer.SIZE); } @@ -381,7 +383,7 @@ static final class CachedLongProvider2 implements CachedUniformRandomProvider { /** The underlying source of randomness. */ - private final UniformRandomProvider rng; + private final RandomLongSource rng; /** * Provides a bit source for booleans. @@ -409,14 +411,14 @@ static final class CachedLongProvider2 * * @param rng the source of randomness */ - CachedLongProvider2(LongProvider rng) { + CachedLongProvider2(RandomLongSource rng) { this.rng = rng; } @Override public long next() { // Delegate this - return rng.nextLong(); + return rng.next(); } @Override @@ -429,7 +431,7 @@ public boolean nextBoolean() { // Set shift to the size of a long discardShift = Long.SIZE; // Get the next value - booleanSource = rng.nextLong(); + booleanSource = rng.next(); // Check the most significant bit return (booleanSource >>> 63) != 0; } @@ -447,7 +449,7 @@ public int nextInt() { } cachedIntValue = true; // Split a 64-bit long into two 32-bit int values - final long sample = rng.nextLong(); + final long sample = rng.next(); // Cache one value nextIntValue = (int) sample; // Return the other @@ -464,7 +466,7 @@ static final class CachedLongProvider3 implements CachedUniformRandomProvider { /** The underlying source of randomness. */ - private final UniformRandomProvider rng; + private final RandomLongSource rng; /** * Provides a bit source for booleans. @@ -527,7 +529,7 @@ void setNext(NextIntNode next) { private final class GeneratorNextIntNode extends NextIntNode { @Override int nextInt() { - intSource = rng.nextLong(); + intSource = rng.next(); // Return the upper 32 bits return (int) (intSource >>> Integer.SIZE); } @@ -549,7 +551,7 @@ int nextInt() { * * @param rng the source of randomness */ - CachedLongProvider3(LongProvider rng) { + CachedLongProvider3(RandomLongSource rng) { this.rng = rng; // Create the cyclic linked list. // This is only two nodes. @@ -564,7 +566,7 @@ int nextInt() { @Override public long next() { // Delegate this - return rng.nextLong(); + return rng.next(); } @Override @@ -577,7 +579,7 @@ public boolean nextBoolean() { // Set to the bit size of an long discardShift = Long.SIZE; // Get the next value - booleanSource = rng.nextLong(); + booleanSource = rng.next(); } return ((booleanSource << --discardShift) >>> 63) != 0; } @@ -599,7 +601,7 @@ static final class CachedLongProvider4 implements CachedUniformRandomProvider { /** The underlying source of randomness. */ - private final UniformRandomProvider rng; + private final RandomLongSource rng; /** * Provides a bit source for booleans. @@ -631,14 +633,14 @@ static final class CachedLongProvider4 * * @param rng the source of randomness */ - CachedLongProvider4(LongProvider rng) { + CachedLongProvider4(RandomLongSource rng) { this.rng = rng; } @Override public long next() { // Delegate this - return rng.nextLong(); + return rng.next(); } @Override @@ -649,7 +651,7 @@ public boolean nextBoolean() { // Set the counter least significant bit shiftCounter = 1; // Get the next value - booleanSource = rng.nextInt(); + booleanSource = rng.next(); } else { // Consume the last used most significant bit from the source booleanSource <<= 1; @@ -671,7 +673,7 @@ public int nextInt() { } // Fill the cache cachedIntSource = true; - intSource = rng.nextLong(); + intSource = rng.next(); // Return the upper 32 bits return (int) (intSource >>> Integer.SIZE); } @@ -696,9 +698,9 @@ public static UniformRandomProvider wrap(UniformRandomProvider rng) { /** * Wrap the source of randomness. * - *

The returned provider will cache values from an {@link IntProvider} or - * {@link LongProvider} to enable fast provision of {@link UniformRandomProvider#nextBoolean()}, - * and in the case of a {@link LongProvider} also {@link UniformRandomProvider#nextInt()}. + *

The returned provider will cache values from an {@link RandomIntSource} or + * {@link RandomLongSource} to enable fast provision of {@link UniformRandomProvider#nextBoolean()}, + * and in the case of a {@link RandomLongSource} also {@link UniformRandomProvider#nextInt()}. * *

If the source of randomness cannot be wrapped then it is returned unmodified. * @@ -711,30 +713,30 @@ public static UniformRandomProvider wrap(UniformRandomProvider rng, int method) if (rng instanceof CachedUniformRandomProvider) { return rng; } - if (rng instanceof LongProvider) { + if (rng instanceof RandomLongSource) { switch (method) { case 4: - return new CachedLongProvider4((LongProvider)rng); + return new CachedLongProvider4((RandomLongSource)rng); case 3: - return new CachedLongProvider3((LongProvider)rng); + return new CachedLongProvider3((RandomLongSource)rng); case 2: - return new CachedLongProvider2((LongProvider)rng); + return new CachedLongProvider2((RandomLongSource)rng); case 1: - return new CachedLongProvider((LongProvider)rng); + return new CachedLongProvider((RandomLongSource)rng); default: throw new IllegalStateException("not implemented"); } } - if (rng instanceof IntProvider) { + if (rng instanceof RandomIntSource) { switch (method) { case 4: - return new CachedIntProvider4((IntProvider)rng); + return new CachedIntProvider4((RandomIntSource)rng); case 3: - return new CachedIntProvider3((IntProvider)rng); + return new CachedIntProvider3((RandomIntSource)rng); case 2: - return new CachedIntProvider2((IntProvider)rng); + return new CachedIntProvider2((RandomIntSource)rng); case 1: - return new CachedIntProvider((IntProvider)rng); + return new CachedIntProvider((RandomIntSource)rng); default: throw new IllegalStateException("not implemented"); } From 58afa68ebef161fa938c06ceed6bc324e8367e4a Mon Sep 17 00:00:00 2001 From: aherbert Date: Wed, 3 Oct 2018 11:54:37 +0100 Subject: [PATCH 5/6] RNG-57: Cache values for nextBoolean() and nextInt() AbstractIntProvider has been updated to cache a value for nextBoolean(). AbstractLongProvider has been updated to cache a value for nextBoolean() and nextInt(). --- .../rng/core/source32/IntProvider.java | 36 +++++- .../rng/core/source64/LongProvider.java | 61 ++++++++- .../rng/core/source32/IntProviderTest.java | 78 ++++++++++++ .../rng/core/source64/LongProviderTest.java | 120 ++++++++++++++++++ 4 files changed, 292 insertions(+), 3 deletions(-) create mode 100644 commons-rng-core/src/test/java/org/apache/commons/rng/core/source32/IntProviderTest.java create mode 100644 commons-rng-core/src/test/java/org/apache/commons/rng/core/source64/LongProviderTest.java diff --git a/commons-rng-core/src/main/java/org/apache/commons/rng/core/source32/IntProvider.java b/commons-rng-core/src/main/java/org/apache/commons/rng/core/source32/IntProvider.java index f4a984685..33625ceb4 100644 --- a/commons-rng-core/src/main/java/org/apache/commons/rng/core/source32/IntProvider.java +++ b/commons-rng-core/src/main/java/org/apache/commons/rng/core/source32/IntProvider.java @@ -28,6 +28,30 @@ public abstract class IntProvider extends BaseProvider implements RandomIntSource { + /** + * Provides a bit source for booleans. + * + *

+ * A cached value from a call to {@link #nextInt()}. + *

+ */ + private int booleanSource; // Initialised as 0 + + /** + * The bit mask of the boolean source to obtain the boolean bit. + * + *

+ * The bit mask contains a single bit set. This begins at the least + * significant bit and is gradually shifted upwards until overflow to zero. + *

+ * + *

+ * When zero a new boolean source should be created and the mask set to the + * least significant bit (i.e. 1). + *

+ */ + private int booleanBitMask; // Initialised as 0 + /** {@inheritDoc} */ @Override public int nextInt() { @@ -37,7 +61,17 @@ public int nextInt() { /** {@inheritDoc} */ @Override public boolean nextBoolean() { - return NumberFactory.makeBoolean(nextInt()); + // Shift up. This will eventually overflow and become zero. + booleanBitMask <<= 1; + // The mask will either contain a single bit or none. + if (booleanBitMask == 0) { + // Set the least significant bit + booleanBitMask = 1; + // Get the next value + booleanSource = nextInt(); + } + // Return if the bit is set + return (booleanSource & booleanBitMask) != 0; } /** {@inheritDoc} */ diff --git a/commons-rng-core/src/main/java/org/apache/commons/rng/core/source64/LongProvider.java b/commons-rng-core/src/main/java/org/apache/commons/rng/core/source64/LongProvider.java index 93982e558..614c8eb41 100644 --- a/commons-rng-core/src/main/java/org/apache/commons/rng/core/source64/LongProvider.java +++ b/commons-rng-core/src/main/java/org/apache/commons/rng/core/source64/LongProvider.java @@ -28,6 +28,42 @@ public abstract class LongProvider extends BaseProvider implements RandomLongSource { + /** + * Provides a bit source for booleans. + * + *

+ * A cached value from a call to {@link #nextLong()}. + *

+ */ + private long booleanSource; // Initialised as 0 + + /** + * The bit mask of the boolean source to obtain the boolean bit. + * + *

+ * The bit mask contains a single bit set. This begins at the least + * significant bit and is gradually shifted upwards until overflow to zero. + *

+ * + *

+ * When zero a new boolean source should be created and the mask set to the + * least significant bit (i.e. 1). + *

+ */ + private long booleanBitMask; // Initialised as 0 + + /** + * Provides a source for ints. + * + *

+ * A cached value from a call to {@link #nextLong()}. + *

+ */ + private long intSource; + + /** Flag to indicate an int source has been cached. */ + private boolean cachedIntSource; // Initialised as false + /** {@inheritDoc} */ @Override public long nextLong() { @@ -37,7 +73,18 @@ public long nextLong() { /** {@inheritDoc} */ @Override public int nextInt() { - return NumberFactory.makeInt(nextLong()); + // Directly store and use the long value as a source for ints + if (cachedIntSource) { + // Consume the cache value + cachedIntSource = false; + // Return the lower 32 bits + return NumberFactory.extractLo(intSource); + } + // Fill the cache + cachedIntSource = true; + intSource = nextLong(); + // Return the upper 32 bits + return NumberFactory.extractHi(intSource); } /** {@inheritDoc} */ @@ -49,7 +96,17 @@ public double nextDouble() { /** {@inheritDoc} */ @Override public boolean nextBoolean() { - return NumberFactory.makeBoolean(nextLong()); + // Shift up. This will eventually overflow and become zero. + booleanBitMask <<= 1; + // The mask will either contain a single bit or none. + if (booleanBitMask == 0) { + // Set the least significant bit + booleanBitMask = 1; + // Get the next value + booleanSource = nextLong(); + } + // Return if the bit is set + return (booleanSource & booleanBitMask) != 0; } /** {@inheritDoc} */ diff --git a/commons-rng-core/src/test/java/org/apache/commons/rng/core/source32/IntProviderTest.java b/commons-rng-core/src/test/java/org/apache/commons/rng/core/source32/IntProviderTest.java new file mode 100644 index 000000000..41c97708c --- /dev/null +++ b/commons-rng-core/src/test/java/org/apache/commons/rng/core/source32/IntProviderTest.java @@ -0,0 +1,78 @@ +/* + * 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.core.source32; + +import org.junit.Assert; +import org.junit.Test; + +/** + * The tests the caching of calls to {@link IntProvider#nextInt()} are used + * as the source for {@link IntProvider#nextInt()} and + * {@link IntProvider#nextBoolean()}. + */ +public class IntProviderTest { + + /** + * A simple class to flip the bits in a number as the source for + * {@link IntProvider#next()}. + */ + static final class FlipIntProvider extends IntProvider { + + /** The value. */ + private int value; + + /** + * @param value the value + */ + public FlipIntProvider(int value) { + // Flip the bits so the first call to next() returns to the same state + this.value = ~value; + } + + @Override + public int next() { + // Flip the bits + value = ~value; + return value; + } + } + + /** + * This test ensures that the call to {@link IntProvider#nextBoolean()} returns + * each of the bits from a call to {@link IntProvider#nextInt()}. + * + *

The order should be from the least-significant bit. + */ + @Test + public void testNextBoolean() { + for (int i = 0; i < Integer.SIZE; i++) { + // Set only a single bit in the source + final int value = 1 << i; + final IntProvider provider = new FlipIntProvider(value); + // Test the result for a single pass over the long + for (int j = 0; j < Integer.SIZE; j++) { + final boolean expected = (i == j); + Assert.assertEquals("Pass 1, bit " + j, expected, provider.nextBoolean()); + } + // The second pass should use the opposite bits + for (int j = 0; j < Integer.SIZE; j++) { + final boolean expected = (i != j); + Assert.assertEquals("Pass 2, bit " + j, expected, provider.nextBoolean()); + } + } + } +} diff --git a/commons-rng-core/src/test/java/org/apache/commons/rng/core/source64/LongProviderTest.java b/commons-rng-core/src/test/java/org/apache/commons/rng/core/source64/LongProviderTest.java new file mode 100644 index 000000000..ade6af57a --- /dev/null +++ b/commons-rng-core/src/test/java/org/apache/commons/rng/core/source64/LongProviderTest.java @@ -0,0 +1,120 @@ +/* + * 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.core.source64; + +import org.junit.Assert; +import org.junit.Test; + +/** + * The tests the caching of calls to {@link LongProvider#nextLong()} are used + * as the source for {@link LongProvider#nextInt()} and + * {@link LongProvider#nextBoolean()}. + */ +public class LongProviderTest { + + /** + * A simple class to return a fixed value as the source for + * {@link LongProvider#next()}. + */ + static final class FixedLongProvider extends LongProvider { + + /** The value. */ + private long value; + + /** + * @param value the value + */ + public FixedLongProvider(long value) { + this.value = value; + } + + @Override + public long next() { + return value; + } + } + + /** + * A simple class to flip the bits in a number as the source for + * {@link LongProvider#next()}. + */ + static final class FlipLongProvider extends LongProvider { + + /** The value. */ + private long value; + + /** + * @param value the value + */ + public FlipLongProvider(long value) { + // Flip the bits so the first call to next() returns to the same state + this.value = ~value; + } + + @Override + public long next() { + // Flip the bits + value = ~value; + return value; + } + } + + /** + * This test ensures that the call to {@link LongProvider#nextInt()} returns + * the upper and then lower 32-bits from {@link LongProvider#nextLong()}. + */ + @Test + public void testNextInt() { + final int MAX = 5; + for (int i = 0; i < MAX; i++) { + for (int j = 0; j < MAX; j++) { + // Pack into upper then lower bits + final long value = (((long) i) << 32) | (j & 0xffffffffL); + final LongProvider provider = new FixedLongProvider(value); + Assert.assertEquals("1st call not the upper 32-bits", i, provider.nextInt()); + Assert.assertEquals("2nd call not the lower 32-bits", j, provider.nextInt()); + Assert.assertEquals("3rd call not the upper 32-bits", i, provider.nextInt()); + Assert.assertEquals("4th call not the lower 32-bits", j, provider.nextInt()); + } + } + } + + /** + * This test ensures that the call to {@link LongProvider#nextBoolean()} returns + * each of the bits from a call to {@link LongProvider#nextLong()}. + * + *

The order should be from the least-significant bit. + */ + @Test + public void testNextBoolean() { + for (int i = 0; i < Long.SIZE; i++) { + // Set only a single bit in the source + final long value = 1L << i; + final LongProvider provider = new FlipLongProvider(value); + // Test the result for a single pass over the long + for (int j = 0; j < Long.SIZE; j++) { + final boolean expected = (i == j); + Assert.assertEquals("Pass 1, bit " + j, expected, provider.nextBoolean()); + } + // The second pass should use the opposite bits + for (int j = 0; j < Long.SIZE; j++) { + final boolean expected = (i != j); + Assert.assertEquals("Pass 2, bit " + j, expected, provider.nextBoolean()); + } + } + } +} From de5df91ccb5f434bc3557e83aac0982248b83406 Mon Sep 17 00:00:00 2001 From: aherbert Date: Fri, 5 Oct 2018 17:00:59 +0100 Subject: [PATCH 6/6] RNG-57: Compose and split the state for the RandomProviderState --- .../apache/commons/rng/core/BaseProvider.java | 6 ++- .../rng/core/source32/IntProvider.java | 29 +++++++++----- .../rng/core/source64/LongProvider.java | 38 ++++++++++++------- 3 files changed, 48 insertions(+), 25 deletions(-) diff --git a/commons-rng-core/src/main/java/org/apache/commons/rng/core/BaseProvider.java b/commons-rng-core/src/main/java/org/apache/commons/rng/core/BaseProvider.java index 334c43dc7..098f39066 100644 --- a/commons-rng-core/src/main/java/org/apache/commons/rng/core/BaseProvider.java +++ b/commons-rng-core/src/main/java/org/apache/commons/rng/core/BaseProvider.java @@ -103,14 +103,15 @@ public String toString() { * Bytes that belong to the local state will be stored at the * beginning of the resulting array. */ - protected byte[] composeStateInternal(byte[] state, - byte[] parentState) { + protected byte[] composeStateInternal(byte[] parentState, + byte[] state) { if (parentState == null) { return state; } final int len = parentState.length + state.length; final byte[] c = new byte[len]; + // Store the local state first System.arraycopy(state, 0, c, 0, state.length); System.arraycopy(parentState, 0, c, state.length, parentState.length); return c; @@ -144,6 +145,7 @@ protected byte[][] splitStateInternal(byte[] state, int localStateLength) { checkStateSize(state, localStateLength); + // The local state is stored first final byte[] local = new byte[localStateLength]; System.arraycopy(state, 0, local, 0, localStateLength); final int parentLength = state.length - localStateLength; diff --git a/commons-rng-core/src/main/java/org/apache/commons/rng/core/source32/IntProvider.java b/commons-rng-core/src/main/java/org/apache/commons/rng/core/source32/IntProvider.java index 820bfaaf4..aad99b15d 100644 --- a/commons-rng-core/src/main/java/org/apache/commons/rng/core/source32/IntProvider.java +++ b/commons-rng-core/src/main/java/org/apache/commons/rng/core/source32/IntProvider.java @@ -18,6 +18,7 @@ package org.apache.commons.rng.core.source32; import org.apache.commons.rng.core.util.NumberFactory; + import org.apache.commons.rng.core.BaseProvider; /** @@ -31,32 +32,40 @@ public abstract class IntProvider /** * Provides a bit source for booleans. * - *

- * A cached value from a call to {@link #nextInt()}. - *

+ *

A cached value from a call to {@link #nextInt()}. */ private int booleanSource; // Initialised as 0 /** * The bit mask of the boolean source to obtain the boolean bit. * - *

- * The bit mask contains a single bit set. This begins at the least + *

The bit mask contains a single bit set. This begins at the least * significant bit and is gradually shifted upwards until overflow to zero. - *

* - *

- * When zero a new boolean source should be created and the mask set to the + *

When zero a new boolean source should be created and the mask set to the * least significant bit (i.e. 1). - *

*/ private int booleanBitMask; // Initialised as 0 /** {@inheritDoc} */ @Override protected byte[] getStateInternal() { + final int[] state = new int[] { booleanSource, + booleanBitMask }; return composeStateInternal(super.getStateInternal(), - new byte[0]); // No local state. + NumberFactory.makeByteArray(state)); + } + + /** {@inheritDoc} */ + @Override + protected void setStateInternal(byte[] s) { + final byte[][] c = splitStateInternal(s, 8); + + final int[] state = NumberFactory.makeIntArray(c[0]); + booleanSource = state[0]; + booleanBitMask = state[1]; + + super.setStateInternal(c[1]); } /** {@inheritDoc} */ diff --git a/commons-rng-core/src/main/java/org/apache/commons/rng/core/source64/LongProvider.java b/commons-rng-core/src/main/java/org/apache/commons/rng/core/source64/LongProvider.java index 514023a5b..279255b15 100644 --- a/commons-rng-core/src/main/java/org/apache/commons/rng/core/source64/LongProvider.java +++ b/commons-rng-core/src/main/java/org/apache/commons/rng/core/source64/LongProvider.java @@ -31,33 +31,25 @@ public abstract class LongProvider /** * Provides a bit source for booleans. * - *

- * A cached value from a call to {@link #nextLong()}. - *

+ *

A cached value from a call to {@link #nextLong()}. */ private long booleanSource; // Initialised as 0 /** * The bit mask of the boolean source to obtain the boolean bit. * - *

- * The bit mask contains a single bit set. This begins at the least + *

The bit mask contains a single bit set. This begins at the least * significant bit and is gradually shifted upwards until overflow to zero. - *

* - *

- * When zero a new boolean source should be created and the mask set to the + *

When zero a new boolean source should be created and the mask set to the * least significant bit (i.e. 1). - *

*/ private long booleanBitMask; // Initialised as 0 /** * Provides a source for ints. * - *

- * A cached value from a call to {@link #nextLong()}. - *

+ *

A cached value from a call to {@link #nextLong()}. */ private long intSource; @@ -67,8 +59,28 @@ public abstract class LongProvider /** {@inheritDoc} */ @Override protected byte[] getStateInternal() { + // Pack the boolean inefficiently as a long + final long[] state = new long[] { booleanSource, + booleanBitMask, + intSource, + cachedIntSource ? 1 : 0 }; return composeStateInternal(super.getStateInternal(), - new byte[0]); // No local state. + NumberFactory.makeByteArray(state)); + } + + /** {@inheritDoc} */ + @Override + protected void setStateInternal(byte[] s) { + final byte[][] c = splitStateInternal(s, 32); + + final long[] state = NumberFactory.makeLongArray(c[0]); + booleanSource = state[0]; + booleanBitMask = state[1]; + intSource = state[2]; + // Non-zero is true + cachedIntSource = state[3] != 0; + + super.setStateInternal(c[1]); } /** {@inheritDoc} */