RNG-117: RandomSource to support creating byte[] seed for implementing class.
Added methods:
public byte[] createSeed();
public byte[] createSeed(UniformRandomProvider);
diff --git a/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/RandomSource.java b/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/RandomSource.java
index 62b83b3..e8048a4 100644
--- a/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/RandomSource.java
+++ b/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/RandomSource.java
@@ -490,6 +490,53 @@
}
/**
+ * Creates a seed suitable for the implementing class represented by this random source.
+ *
+ * <p>The seed will be created as if passing a {@code null} seed to the factory method
+ * {@link #create(RandomSource, Object, Object...)}. It will satisfy the seed size and any
+ * other seed requirements for the implementing class. The seed is converted from the native
+ * type to a byte representation.</p>
+ *
+ * <p>Usage example:</p>
+ * <pre><code>
+ * RandomSource source = ...;
+ * byte[] seed = source.createSeed();
+ * UniformRandomProvider rng = RandomSource.create(source, seed);
+ * </code></pre>
+ *
+ * @return the seed
+ *
+ * @since 1.3
+ */
+ public byte[] createSeed() {
+ return internalIdentifier.createSeedBytes();
+ }
+
+ /**
+ * Creates a seed suitable for the implementing class represented by this random source
+ * using the supplied source of randomness.
+ *
+ * <p>The seed will satisfy the seed size and any other seed requirements for the
+ * implementing class.</p>
+ *
+ * <p>Usage example:</p>
+ * <pre><code>
+ * RandomSource source = ...;
+ * UniformRandomProvider seedRng = new JDKRandomWrapper(new SecureRandom());
+ * byte[] seed = source.createSeed(seedRng);
+ * UniformRandomProvider rng = RandomSource.create(source, seed);
+ * </code></pre>
+ *
+ * @param rng Source of randomness.
+ * @return the seed
+ *
+ * @since 1.3
+ */
+ public byte[] createSeed(UniformRandomProvider rng) {
+ return internalIdentifier.createSeedBytes(rng);
+ }
+
+ /**
* Checks whether the implementing class represented by this random source
* supports the {@link org.apache.commons.rng.JumpableUniformRandomProvider
* JumpableUniformRandomProvider} interface. If {@code true} the instance returned
diff --git a/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/internal/NativeSeedType.java b/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/internal/NativeSeedType.java
index 1db19ee..0fe9da5 100644
--- a/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/internal/NativeSeedType.java
+++ b/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/internal/NativeSeedType.java
@@ -16,6 +16,8 @@
*/
package org.apache.commons.rng.simple.internal;
+import org.apache.commons.rng.core.util.NumberFactory;
+
/**
* The native seed type. Contains values for all native seed types and methods
* to convert supported seed types to the native seed type.
@@ -41,7 +43,7 @@
*/
public enum NativeSeedType {
/** The seed type is {@code Integer}. */
- INT(Integer.class) {
+ INT(Integer.class, 4) {
@Override
public Integer createSeed(int size) {
return SeedFactory.createInt();
@@ -68,7 +70,7 @@
}
},
/** The seed type is {@code Long}. */
- LONG(Long.class) {
+ LONG(Long.class, 8) {
@Override
public Long createSeed(int size) {
return SeedFactory.createLong();
@@ -95,7 +97,7 @@
}
},
/** The seed type is {@code int[]}. */
- INT_ARRAY(int[].class) {
+ INT_ARRAY(int[].class, 4) {
@Override
public int[] createSeed(int size) {
// Limit the number of calls to the synchronized method. The generator
@@ -124,7 +126,7 @@
}
},
/** The seed type is {@code long[]}. */
- LONG_ARRAY(long[].class) {
+ LONG_ARRAY(long[].class, 8) {
@Override
public long[] createSeed(int size) {
// Limit the number of calls to the synchronized method. The generator
@@ -153,6 +155,8 @@
}
};
+ /** Error message for unrecognised seed types. */
+ private static final String UNRECOGNISED_SEED = "Unrecognized seed type: ";
/** Maximum length of the seed array (for creating array seeds). */
private static final int RANDOM_SEED_ARRAY_SIZE = 128;
/** Convert {@code Long} to {@code Integer}. */
@@ -180,10 +184,20 @@
private final Class<?> type;
/**
- * @param type Define the class type of the native seed.
+ * Define the number of bytes required to represent the native seed. If the type is
+ * an array then this represents the size of a single value of the type.
*/
- NativeSeedType(Class<?> type) {
+ private final int bytes;
+
+ /**
+ * Instantiates a new native seed type.
+ *
+ * @param type Define the class type of the native seed.
+ * @param bytes Define the number of bytes required to represent the native seed.
+ */
+ NativeSeedType(Class<?> type, int bytes) {
this.type = type;
+ this.bytes = bytes;
}
/**
@@ -196,6 +210,16 @@
}
/**
+ * Gets the number of bytes required to represent the native seed type. If the type is
+ * an array then this represents the size of a single value of the type.
+ *
+ * @return the number of bytes
+ */
+ public int getBytes() {
+ return bytes;
+ }
+
+ /**
* Creates the seed. The output seed type is determined by the native seed type. If the
* output is an array the required size of the array can be specified.
*
@@ -230,7 +254,7 @@
return convert((byte[]) seed, size);
}
- throw new UnsupportedOperationException("Unrecognized seed type: " + seed);
+ throw new UnsupportedOperationException(UNRECOGNISED_SEED + seed);
}
/**
@@ -277,4 +301,27 @@
* @return the native seed.
*/
protected abstract Object convert(byte[] seed, int size);
+
+ /**
+ * Converts the input seed from any of the supported seed types to bytes.
+ *
+ * @param seed Input seed.
+ * @return the seed bytes.
+ * @throws UnsupportedOperationException if the {@code seed} type is invalid.
+ */
+ public static byte[] convertSeedToBytes(Object seed) {
+ if (seed instanceof Integer) {
+ return NumberFactory.makeByteArray((Integer) seed);
+ } else if (seed instanceof Long) {
+ return NumberFactory.makeByteArray((Long) seed);
+ } else if (seed instanceof int[]) {
+ return NumberFactory.makeByteArray((int[]) seed);
+ } else if (seed instanceof long[]) {
+ return NumberFactory.makeByteArray((long[]) seed);
+ } else if (seed instanceof byte[]) {
+ return (byte[]) seed;
+ }
+
+ throw new UnsupportedOperationException(UNRECOGNISED_SEED + seed);
+ }
}
diff --git a/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/internal/ProviderBuilder.java b/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/internal/ProviderBuilder.java
index 59b9271..02af9fc 100644
--- a/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/internal/ProviderBuilder.java
+++ b/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/internal/ProviderBuilder.java
@@ -272,20 +272,35 @@
return super.convertSeed(seed);
}
+ @Override
+ protected byte[] createByteArraySeed(UniformRandomProvider source) {
+ return NativeSeedType.convertSeedToBytes(createMswsSeed(source));
+ }
+
/**
- * Creates the full length seed array from the input seed using the method
- * recommended for the generator. This is a high quality Weyl increment composed
- * of a hex character permutation.
+ * Creates the full length seed array from the input seed.
*
* @param seed the seed
* @return the seed array
*/
private long[] createMswsSeed(long seed) {
- final long increment = SeedUtils.createLongHexPermutation(new SplitMix64(seed));
+ return createMswsSeed(new SplitMix64(seed));
+ }
+
+ /**
+ * Creates the full length seed array from the input seed using the method
+ * recommended for the generator. This is a high quality Weyl increment composed
+ * of a hex character permutation.
+ *
+ * @param source Source of randomness.
+ * @return the seed array
+ */
+ private long[] createMswsSeed(UniformRandomProvider source) {
+ final long increment = SeedUtils.createLongHexPermutation(source);
// The initial state should not be low complexity but the Weyl
// state can be any number.
final long state = increment;
- final long weylState = seed;
+ final long weylState = source.nextLong();
return new long[] {state, weylState, increment};
}
},
@@ -383,6 +398,16 @@
}
/**
+ * Gets the number of seed bytes required to seed the implementing class represented by
+ * this random source.
+ *
+ * @return the number of seed bytes
+ */
+ private int getSeedByteSize() {
+ return nativeSeedSize * nativeSeedType.getBytes();
+ }
+
+ /**
* Creates a RNG instance.
*
* <p>This method can be over-ridden to allow fast construction of a generator
@@ -442,8 +467,10 @@
/**
* Creates a native seed.
*
- * <p>This method should be over-ridden to satisfy seed requirements for the generator,
- * for example if a seed must contain non-zero bits.</p>
+ * <p>The default implementation creates a seed of the native type and, for array seeds,
+ * ensures not all bits are zero.</p>
+ *
+ * <p>This method should be over-ridden to satisfy seed requirements for the generator.</p>
*
* @return the native seed
*/
@@ -452,6 +479,21 @@
}
/**
+ * Creates a {@code byte[]} seed using the provided source of randomness.
+ *
+ * <p>The default implementation creates a full-length seed and ensures not all bits
+ * are zero.</p>
+ *
+ * <p>This method should be over-ridden to satisfy seed requirements for the generator.</p>
+ *
+ * @param source Source of randomness.
+ * @return the byte[] seed
+ */
+ protected byte[] createByteArraySeed(UniformRandomProvider source) {
+ return SeedFactory.createByteArray(source, getSeedByteSize());
+ }
+
+ /**
* Converts a seed from any of the supported seed types to a native seed.
*
* @param seed Input seed (must not be null).
@@ -476,6 +518,39 @@
}
/**
+ * Creates a seed suitable for the implementing class represented by this random source.
+ *
+ * <p>It will satisfy the seed size and any other seed requirements for the
+ * implementing class. The seed is converted from the native type to bytes.</p>
+ *
+ * @return the seed bytes
+ *
+ * @since 1.3
+ */
+ public final byte[] createSeedBytes() {
+ // Custom implementations can override createSeed
+ final Object seed = createSeed();
+ return NativeSeedType.convertSeedToBytes(seed);
+ }
+
+ /**
+ * Creates a seed suitable for the implementing class represented by this random source
+ * using the supplied source of randomness.
+ *
+ * <p>It will satisfy the seed size and any other seed requirements for the
+ * implementing class. The seed is converted from the native type to bytes.</p>
+ *
+ * @param source Source of randomness.
+ * @return the seed bytes
+ *
+ * @since 1.3
+ */
+ public final byte[] createSeedBytes(UniformRandomProvider source) {
+ // Custom implementations can override createByteArraySeed
+ return createByteArraySeed(source);
+ }
+
+ /**
* Gets the constructor.
*
* @return the RNG constructor.
@@ -492,6 +567,7 @@
}
return constructor;
}
+
/**
* Creates a constructor.
*
diff --git a/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/internal/SeedFactory.java b/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/internal/SeedFactory.java
index 170c978..4b336ea 100644
--- a/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/internal/SeedFactory.java
+++ b/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/internal/SeedFactory.java
@@ -203,6 +203,25 @@
}
/**
+ * Creates an array of {@code byte} numbers for use as a seed using the supplied source of
+ * randomness. The result will not be all zeros.
+ *
+ * @param source Source of randomness.
+ * @param n Size of the array to create.
+ * @return an array of {@code n} random numbers.
+ */
+ static byte[] createByteArray(UniformRandomProvider source,
+ int n) {
+ final byte[] seed = new byte[n];
+ source.nextBytes(seed);
+ // If the seed is zero it is assumed the input source RNG is either broken
+ // or the seed is small and it was zero by chance. Revert to the built-in
+ // source of randomness to ensure it is non-zero.
+ ensureNonZero(seed);
+ return seed;
+ }
+
+ /**
* Ensure the seed is non-zero at the first position in the array.
*
* <p>This method will replace a zero at index 0 in the array with
@@ -243,6 +262,42 @@
}
/**
+ * Ensure the seed is not zero at all positions in the array.
+ *
+ * <p>This method will check all positions in the array and if all
+ * are zero it will replace index 0 in the array with
+ * a non-zero random number. The method ensures any length seed
+ * contains non-zero bits. The output seed is suitable for generators
+ * that cannot be seeded with all zeros.</p>
+ *
+ * @param seed Seed array (modified in place).
+ * @see #createInt()
+ */
+ private static void ensureNonZero(byte[] seed) {
+ // Since zero occurs 1 in 2^8 for a single byte this checks the entire array for zeros.
+ if (seed.length != 0 && isAllZero(seed)) {
+ do {
+ seed[0] = (byte) createInt();
+ } while (seed[0] == 0);
+ }
+ }
+
+ /**
+ * Test if each position in the array is zero.
+ *
+ * @param array Array data.
+ * @return true if all position are zero
+ */
+ private static boolean isAllZero(byte[] array) {
+ for (final byte value : array) {
+ if (value != 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
* Ensure the value is non-zero.
*
* <p>This method will replace a zero with a non-zero random number from the random source.</p>
diff --git a/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/ProvidersCommonParametricTest.java b/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/ProvidersCommonParametricTest.java
index 4641937..aa403da 100644
--- a/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/ProvidersCommonParametricTest.java
+++ b/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/ProvidersCommonParametricTest.java
@@ -39,6 +39,7 @@
import org.apache.commons.rng.RandomProviderState;
import org.apache.commons.rng.RestorableUniformRandomProvider;
import org.apache.commons.rng.core.RandomProviderDefaultState;
+import org.apache.commons.rng.core.source64.SplitMix64;
/**
* Tests which all generators must pass.
@@ -155,6 +156,20 @@
checkNextIntegerInRange(rng, 10, 10000);
}
+ @Test
+ public void testRandomSourceCreateSeed() {
+ final byte[] seed = originalSource.createSeed();
+ final UniformRandomProvider rng = RandomSource.create(originalSource, seed, originalArgs);
+ checkNextIntegerInRange(rng, 10, 10000);
+ }
+
+ @Test
+ public void testRandomSourceCreateSeedFromRNG() {
+ final byte[] seed = originalSource.createSeed(new SplitMix64(RandomSource.createLong()));
+ final UniformRandomProvider rng = RandomSource.create(originalSource, seed, originalArgs);
+ checkNextIntegerInRange(rng, 10, 10000);
+ }
+
// State save and restore tests.
@Test
diff --git a/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/internal/NativeSeedTypeParametricTest.java b/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/internal/NativeSeedTypeParametricTest.java
index 9a2ebf3..3913341 100644
--- a/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/internal/NativeSeedTypeParametricTest.java
+++ b/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/internal/NativeSeedTypeParametricTest.java
@@ -112,6 +112,28 @@
}
/**
+ * Test the seed can be created, converted to a byte[] and then back to the native type.
+ */
+ @Test
+ public void testConvertSeedToBytes() {
+ final int size = 3;
+ final Object seed = nativeSeedType.createSeed(size);
+ Assert.assertNotNull("Null seed", seed);
+
+ final byte[] bytes = NativeSeedType.convertSeedToBytes(seed);
+ Assert.assertNotNull("Null byte[] seed", bytes);
+
+ final Object seed2 = nativeSeedType.convertSeed(bytes, size);
+ if (type.isArray()) {
+ // This handles nested primitive arrays
+ Assert.assertArrayEquals("byte[] seed was not converted back",
+ new Object[] {seed}, new Object[] {seed2});
+ } else {
+ Assert.assertEquals("byte[] seed was not converted back", seed, seed2);
+ }
+ }
+
+ /**
* Test the seed can be converted to the correct type from any of the supported input types.
*/
@Test
diff --git a/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/internal/NativeSeedTypeTest.java b/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/internal/NativeSeedTypeTest.java
new file mode 100644
index 0000000..be699ae
--- /dev/null
+++ b/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/internal/NativeSeedTypeTest.java
@@ -0,0 +1,44 @@
+/*
+ * 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.internal;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Tests for the {@link NativeSeedType} factory seed conversions.
+ */
+public class NativeSeedTypeTest {
+ /**
+ * Test the conversion throws for an unsupported type. All supported types are
+ * tested in the {@link NativeSeedTypeParametricTest}.
+ */
+ @Test(expected = UnsupportedOperationException.class)
+ public void testConvertSeedToBytesUsingNullThrows() {
+ NativeSeedType.convertSeedToBytes(null);
+ }
+
+ /**
+ * Test the conversion passes through a byte[]. This hits the edge case of a seed
+ * that can be converted that is not a native type.
+ */
+ @Test
+ public void testConvertSeedToBytesUsingByteArray() {
+ final byte[] seed = {42, 78, 99};
+ Assert.assertSame(seed, NativeSeedType.convertSeedToBytes(seed));
+ }
+}
diff --git a/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/internal/RandomSourceInternalParametricTest.java b/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/internal/RandomSourceInternalParametricTest.java
index 69736f2..e58f4ff 100644
--- a/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/internal/RandomSourceInternalParametricTest.java
+++ b/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/internal/RandomSourceInternalParametricTest.java
@@ -16,6 +16,7 @@
*/
package org.apache.commons.rng.simple.internal;
+import org.apache.commons.rng.core.source64.SplitMix64;
import org.apache.commons.rng.simple.internal.ProviderBuilder.RandomSourceInternal;
import org.junit.Assert;
import org.junit.Test;
@@ -23,6 +24,8 @@
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
+import java.util.EnumMap;
+
/**
* Tests for the {@link ProviderBuilder.RandomSourceInternal} seed conversions. This test
* ensures that all random sources can create a seed or convert any supported seed to the
@@ -43,6 +46,53 @@
null,
Double.valueOf(Math.PI),
};
+ /** The expected byte size of the seed for each RandomSource. */
+ private static final EnumMap<RandomSourceInternal, Integer> EXPECTED_SEED_BYTES =
+ new EnumMap<RandomSourceInternal, Integer>(RandomSourceInternal.class);
+
+ static {
+ final int intBytes = 4;
+ final int longBytes = 8;
+ EXPECTED_SEED_BYTES.put(RandomSourceInternal.JDK, longBytes * 1);
+ EXPECTED_SEED_BYTES.put(RandomSourceInternal.WELL_512_A, intBytes * 16);
+ EXPECTED_SEED_BYTES.put(RandomSourceInternal.WELL_1024_A, intBytes * 32);
+ EXPECTED_SEED_BYTES.put(RandomSourceInternal.WELL_19937_A, intBytes * 624);
+ EXPECTED_SEED_BYTES.put(RandomSourceInternal.WELL_19937_C, intBytes * 624);
+ EXPECTED_SEED_BYTES.put(RandomSourceInternal.WELL_44497_A, intBytes * 1391);
+ EXPECTED_SEED_BYTES.put(RandomSourceInternal.WELL_44497_B, intBytes * 1391);
+ EXPECTED_SEED_BYTES.put(RandomSourceInternal.MT, intBytes * 624);
+ EXPECTED_SEED_BYTES.put(RandomSourceInternal.ISAAC, intBytes * 256);
+ EXPECTED_SEED_BYTES.put(RandomSourceInternal.SPLIT_MIX_64, longBytes * 1);
+ EXPECTED_SEED_BYTES.put(RandomSourceInternal.XOR_SHIFT_1024_S, longBytes * 16);
+ EXPECTED_SEED_BYTES.put(RandomSourceInternal.TWO_CMRES, intBytes * 1);
+ EXPECTED_SEED_BYTES.put(RandomSourceInternal.TWO_CMRES_SELECT, intBytes * 1);
+ EXPECTED_SEED_BYTES.put(RandomSourceInternal.MT_64, longBytes * 312);
+ EXPECTED_SEED_BYTES.put(RandomSourceInternal.MWC_256, intBytes * 257);
+ EXPECTED_SEED_BYTES.put(RandomSourceInternal.KISS, intBytes * 4);
+ EXPECTED_SEED_BYTES.put(RandomSourceInternal.XOR_SHIFT_1024_S_PHI, longBytes * 16);
+ EXPECTED_SEED_BYTES.put(RandomSourceInternal.XO_RO_SHI_RO_64_S, intBytes * 2);
+ EXPECTED_SEED_BYTES.put(RandomSourceInternal.XO_RO_SHI_RO_64_SS, intBytes * 2);
+ EXPECTED_SEED_BYTES.put(RandomSourceInternal.XO_SHI_RO_128_PLUS, intBytes * 4);
+ EXPECTED_SEED_BYTES.put(RandomSourceInternal.XO_SHI_RO_128_SS, intBytes * 4);
+ EXPECTED_SEED_BYTES.put(RandomSourceInternal.XO_RO_SHI_RO_128_PLUS, longBytes * 2);
+ EXPECTED_SEED_BYTES.put(RandomSourceInternal.XO_RO_SHI_RO_128_SS, longBytes * 2);
+ EXPECTED_SEED_BYTES.put(RandomSourceInternal.XO_SHI_RO_256_PLUS, longBytes * 4);
+ EXPECTED_SEED_BYTES.put(RandomSourceInternal.XO_SHI_RO_256_SS, longBytes * 4);
+ EXPECTED_SEED_BYTES.put(RandomSourceInternal.XO_SHI_RO_512_PLUS, longBytes * 8);
+ EXPECTED_SEED_BYTES.put(RandomSourceInternal.XO_SHI_RO_512_SS, longBytes * 8);
+ EXPECTED_SEED_BYTES.put(RandomSourceInternal.PCG_XSH_RR_32, longBytes * 2);
+ EXPECTED_SEED_BYTES.put(RandomSourceInternal.PCG_XSH_RS_32, longBytes * 2);
+ EXPECTED_SEED_BYTES.put(RandomSourceInternal.PCG_RXS_M_XS_64, longBytes * 2);
+ EXPECTED_SEED_BYTES.put(RandomSourceInternal.PCG_MCG_XSH_RR_32, longBytes * 1);
+ EXPECTED_SEED_BYTES.put(RandomSourceInternal.PCG_MCG_XSH_RS_32, longBytes * 1);
+ EXPECTED_SEED_BYTES.put(RandomSourceInternal.MSWS, longBytes * 3);
+ EXPECTED_SEED_BYTES.put(RandomSourceInternal.SFC_32, intBytes * 3);
+ EXPECTED_SEED_BYTES.put(RandomSourceInternal.SFC_64, longBytes * 3);
+ EXPECTED_SEED_BYTES.put(RandomSourceInternal.JSF_32, intBytes * 1);
+ EXPECTED_SEED_BYTES.put(RandomSourceInternal.JSF_64, longBytes * 1);
+ // ... add more here.
+ // Verify the seed byte size is reflected in the enum javadoc for RandomSource.
+ }
/** Internal identifier for the random source. */
private final RandomSourceInternal randomSourceInternal;
@@ -109,4 +159,49 @@
}
}
}
+
+ /**
+ * Test the seed byte size is reported as the size of a int/long primitive for Int/Long
+ * seed types and a multiple of it for int[]/long[] types.
+ */
+ @Test
+ public void testCreateSeedBytesSizeIsPositiveAndMultipleOf4Or8() {
+ // This should be the full length seed
+ final byte[] seed = randomSourceInternal.createSeedBytes(new SplitMix64(12345L));
+
+ final int size = seed.length;
+ Assert.assertNotEquals("Seed is empty", 0, size);
+
+ if (randomSourceInternal.isNativeSeed(Integer.valueOf(0))) {
+ Assert.assertEquals("Expect 4 bytes for Integer", 4, size);
+ } else if (randomSourceInternal.isNativeSeed(Long.valueOf(0))) {
+ Assert.assertEquals("Expect 8 bytes for Long", 8, size);
+ } else if (randomSourceInternal.isNativeSeed(new int[0])) {
+ Assert.assertEquals("Expect 4n bytes for int[]", 0, size % 4);
+ } else if (randomSourceInternal.isNativeSeed(new long[0])) {
+ Assert.assertEquals("Expect 8n bytes for long[]", 0, size % 8);
+ } else {
+ Assert.fail("Unknown native seed type");
+ }
+ }
+
+ /**
+ * Test the seed byte size against the expected value.
+ *
+ * <p>The expected values are maintained in a table and must be manually updated
+ * for new generators. This test forms an additional cross-reference check that the
+ * seed size in RandomSourceInternal has been correctly set and the size should map to
+ * the array size in the RandomSource javadoc (if applicable).
+ */
+ @Test
+ public void testCreateSeedBytes() {
+ // This should be the full length seed
+ final byte[] seed = randomSourceInternal.createSeedBytes(new SplitMix64(12345L));
+ final int size = seed.length;
+
+ final Integer expected = EXPECTED_SEED_BYTES.get(randomSourceInternal);
+ Assert.assertNotNull("Missing expected seed byte size: " + randomSourceInternal, expected);
+ Assert.assertEquals(randomSourceInternal.toString(),
+ expected.intValue(), size);
+ }
}
diff --git a/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/internal/SeedFactoryTest.java b/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/internal/SeedFactoryTest.java
index 842bdaa..b936d81 100644
--- a/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/internal/SeedFactoryTest.java
+++ b/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/internal/SeedFactoryTest.java
@@ -20,6 +20,8 @@
import java.util.HashMap;
import org.junit.Assert;
import org.junit.Test;
+import org.apache.commons.rng.UniformRandomProvider;
+import org.apache.commons.rng.core.source32.IntProvider;
import org.apache.commons.rng.core.source64.RandomLongSource;
import org.apache.commons.rng.core.util.NumberFactory;
@@ -206,6 +208,63 @@
}
@Test
+ public void testCreateByteArrayWithSizeZero() {
+ assertCreateByteArray(new byte[0]);
+ }
+
+ @Test
+ public void testCreateByteArrayIgnoresNonZeroPositions() {
+ final byte position = 123;
+ int n = 3;
+ for (int i = 0; i < n; i++) {
+ final byte[] expected = new byte[n];
+ expected[i] = position;
+ assertCreateByteArray(expected);
+ }
+ }
+
+ /**
+ * Assert that the SeedFactory uses the bytes exactly as generated by the
+ * {@link UniformRandomProvider#nextBytes(byte[])} method (assuming they are not all zero).
+ *
+ * @param expected the expected
+ */
+ private static void assertCreateByteArray(final byte[] expected) {
+ final UniformRandomProvider rng = new IntProvider() {
+ @Override
+ public int next() {
+ Assert.fail("This method should not be used");
+ return 0;
+ }
+
+ @Override
+ public void nextBytes(byte[] bytes) {
+ System.arraycopy(expected, 0, bytes, 0, Math.min(expected.length, bytes.length));
+ }
+ };
+
+ final byte[] seed = SeedFactory.createByteArray(rng, expected.length);
+ Assert.assertArrayEquals(expected, seed);
+ }
+
+ @Test
+ public void testCreateByteArrayWithAllZeroBytesUpdatesPosition0() {
+ final UniformRandomProvider rng = new IntProvider() {
+ @Override
+ public int next() {
+ // Deliberately produce zero
+ return 0;
+ }
+ };
+ // Test the method only replaces position 0
+ final byte[] seed = SeedFactory.createByteArray(rng, 4);
+ Assert.assertNotEquals("Zero at position 0 should be modified", 0, seed[0]);
+ for (int i = 1; i < seed.length; i++) {
+ Assert.assertEquals("Position above 0 should be unmodified", 0, seed[i]);
+ }
+ }
+
+ @Test
public void testEnsureNonZeroIntArrayIgnoresEmptySeed() {
final int[] seed = new int[0];
SeedFactory.ensureNonZero(seed);