This is an automated email from the ASF dual-hosted git repository.

aherbert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-rng.git


The following commit(s) were added to refs/heads/master by this push:
     new ab6050d9 RNG-174: Improve support for non-zero seeds
ab6050d9 is described below

commit ab6050d992dd9e8057e0d3e3e29d534839c51440
Author: aherbert <[email protected]>
AuthorDate: Fri Apr 1 17:46:12 2022 +0100

    RNG-174: Improve support for non-zero seeds
    
    Allow specification of a range for array seeds that must be non-zero.
    
    The values for the range are based on the result of
    o.a.c.rng.simple.ProvidersCommonParametricTest.testZeroIntArraySeed.
    
    See also the core module for tests using:
    - RandomAssert.assertNextIntZeroOutput
    - RandomAssert.assertIntArrayConstructorWithSingleBitInPoolIsFunctional
    
    Any generator that fails these tests requires a non-zero seed. In most
    cases this was set as the full seed length, or one less for generators
    that do not use all the bits of the seed array (WELL_19937_x,
    WELL_44497_x).
    
    Notable exceptions:
    
    The KISS generator is reduced to a simple LCG when positions [0, 3) are
    all zero. Added a test to demonstrate this. With a zero seed the KISS
    LCG passes testZeroIntArraySeed. To avoid a poor generator the seed will
    be checked to be non-zero in [0, 3).
    
    The MSWS generator is sensitive to the initial state. Added a test to
    show that a zero seed creates zero output. Updating RandomAssert to add
    an assertLongArrayConstructorWithSingleBitInPoolIsFunctional test shows
    the MSWS fails with single bit seeds. The sub-range has been set to [2,
    3) to ensure a non-zero Weyl increment which is the best way to escape a
    bad seed. This is a functionally breaking change.
---
 .../org/apache/commons/rng/core/RandomAssert.java  |  30 ++-
 .../commons/rng/core/source32/KISSRandomTest.java  |  17 ++
 .../source32/MiddleSquareWeylSequenceTest.java     |   8 +
 .../commons/rng/simple/internal/Conversions.java   |  28 +-
 .../commons/rng/simple/internal/MixFunctions.java  |  73 ++++++
 .../rng/simple/internal/NativeSeedType.java        |  33 ++-
 .../rng/simple/internal/ProviderBuilder.java       | 115 ++++++---
 .../commons/rng/simple/internal/SeedFactory.java   | 208 +++++++++++----
 .../rng/simple/ProvidersCommonParametricTest.java  |  28 +-
 .../rng/simple/internal/MixFunctionsTest.java      | 119 +++++++++
 .../internal/NativeSeedTypeParametricTest.java     |  25 ++
 .../rng/simple/internal/SeedFactoryTest.java       | 281 +++++++++++++++++----
 src/changes/changes.xml                            |   8 +-
 src/main/resources/pmd/pmd-ruleset.xml             |   2 +-
 14 files changed, 800 insertions(+), 175 deletions(-)

diff --git 
a/commons-rng-core/src/test/java/org/apache/commons/rng/core/RandomAssert.java 
b/commons-rng-core/src/test/java/org/apache/commons/rng/core/RandomAssert.java
index e72b225a..a2aec6ca 100644
--- 
a/commons-rng-core/src/test/java/org/apache/commons/rng/core/RandomAssert.java
+++ 
b/commons-rng-core/src/test/java/org/apache/commons/rng/core/RandomAssert.java
@@ -353,7 +353,7 @@ public final class RandomAssert {
             final int[] seed = new int[size];
             int remaining = k;
             for (int i = 0; i < seed.length; i++) {
-                seed[i] = 0x80000000;
+                seed[i] = Integer.MIN_VALUE;
                 for (int j = 0; j < 32; j++) {
                     final UniformRandomProvider rng = 
constructor.newInstance(seed);
                     
RandomAssert.assertNextLongNonZeroOutputFromSingleBitSeed(rng, 2 * size, 2 * 
size, i, 31 - j);
@@ -383,17 +383,41 @@ public final class RandomAssert {
      */
     public static <T extends UniformRandomProvider> void
         assertLongArrayConstructorWithSingleBitSeedIsFunctional(Class<T> type, 
int size) {
+        assertLongArrayConstructorWithSingleBitInPoolIsFunctional(type, 64 * 
size);
+    }
+
+    /**
+     * Assert that the random generator created using an {@code long[]} seed 
with a
+     * single bit set is functional. This is tested using the
+     * {@link #assertNextLongNonZeroOutput(UniformRandomProvider, int, int)} 
using
+     * two times the seed size as the warm-up and test cycles.
+     *
+     * <p>The seed size is determined from the size of the bit pool. Bits are 
set for every position
+     * in the pool from most significant first. If the pool size is not a 
multiple of 64 then the
+     * remaining lower significant bits of the last seed position are not 
tested.</p>
+     *
+     * @param type Class of the generator.
+     * @param k Number of bits in the pool (not necessarily a multiple of 64).
+     */
+    public static <T extends UniformRandomProvider> void
+        assertLongArrayConstructorWithSingleBitInPoolIsFunctional(Class<T> 
type, int k) {
         try {
             // Find the long[] constructor
             final Constructor<T> constructor = 
type.getConstructor(long[].class);
+            final int size = (k + 63) / 64;
             final long[] seed = new long[size];
-            for (int i = 0; i < size; i++) {
-                seed[i] = 0x8000000000000000L;
+            int remaining = k;
+            for (int i = 0; i < seed.length; i++) {
+                seed[i] = Long.MIN_VALUE;
                 for (int j = 0; j < 64; j++) {
                     final UniformRandomProvider rng = 
constructor.newInstance(seed);
                     
RandomAssert.assertNextLongNonZeroOutputFromSingleBitSeed(rng, 2 * size, 2 * 
size, i, 63 - j);
                     // Eventually reset to zero
                     seed[i] >>>= 1;
+                    // Terminate when the entire bit pool has been tested
+                    if (--remaining == 0) {
+                        return;
+                    }
                 }
                 Assertions.assertEquals(0L, seed[i], "Seed element was not 
reset");
             }
diff --git 
a/commons-rng-core/src/test/java/org/apache/commons/rng/core/source32/KISSRandomTest.java
 
b/commons-rng-core/src/test/java/org/apache/commons/rng/core/source32/KISSRandomTest.java
index b26fcafb..5af35069 100644
--- 
a/commons-rng-core/src/test/java/org/apache/commons/rng/core/source32/KISSRandomTest.java
+++ 
b/commons-rng-core/src/test/java/org/apache/commons/rng/core/source32/KISSRandomTest.java
@@ -17,6 +17,7 @@
 package org.apache.commons.rng.core.source32;
 
 import org.apache.commons.rng.core.RandomAssert;
+import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
 class KISSRandomTest {
@@ -162,4 +163,20 @@ class KISSRandomTest {
 
         RandomAssert.assertEquals(expectedSequence, new KISSRandom(seed));
     }
+
+    @Test
+    void testConstructorWithZeroSeedInSubRangeIsPartiallyFunctional() {
+        // If the first 3 positions are zero then the output is a simple LCG.
+        // The seeding routine in commons-rng-simple should ensure the first 3 
seed
+        // positions are not all zero.
+        final int m = 69069;
+        final int c = 1234567;
+        int s = 42;
+        final int[] seed = new int[] {0, 0, 0, s};
+        final KISSRandom rng = new KISSRandom(seed);
+        for (int i = 0; i < 200; i++) {
+            s = m * s + c;
+            Assertions.assertEquals(s, rng.nextInt());
+        }
+    }
 }
diff --git 
a/commons-rng-core/src/test/java/org/apache/commons/rng/core/source32/MiddleSquareWeylSequenceTest.java
 
b/commons-rng-core/src/test/java/org/apache/commons/rng/core/source32/MiddleSquareWeylSequenceTest.java
index 23235698..2b7490dd 100644
--- 
a/commons-rng-core/src/test/java/org/apache/commons/rng/core/source32/MiddleSquareWeylSequenceTest.java
+++ 
b/commons-rng-core/src/test/java/org/apache/commons/rng/core/source32/MiddleSquareWeylSequenceTest.java
@@ -22,6 +22,9 @@ import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
 class MiddleSquareWeylSequenceTest {
+    /** The size of the array seed. */
+    private static final int SEED_SIZE = 3;
+
     @Test
     void testReferenceCode() {
         /*
@@ -97,4 +100,9 @@ class MiddleSquareWeylSequenceTest {
                                 rng2.nextLong());
         }
     }
+
+    @Test
+    void testConstructorWithZeroSeedIsNonFunctional() {
+        RandomAssert.assertNextIntZeroOutput(new MiddleSquareWeylSequence(new 
long[SEED_SIZE]), 2 * SEED_SIZE);
+    }
 }
diff --git 
a/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/internal/Conversions.java
 
b/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/internal/Conversions.java
index 2eff8c08..e107c92f 100644
--- 
a/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/internal/Conversions.java
+++ 
b/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/internal/Conversions.java
@@ -34,7 +34,7 @@ final class Conversions {
      * </pre>
      * @see <a href="https://en.wikipedia.org/wiki/Golden_ratio";>Golden 
ratio</a>
      */
-    private static final long GOLDEN_RATIO = 0x9e3779b97f4a7c15L;
+    private static final long GOLDEN_RATIO = MixFunctions.GOLDEN_RATIO_64;
 
     /** No instances. */
     private Conversions() {}
@@ -100,24 +100,6 @@ final class Conversions {
         return (size + 1) >>> 1;
     }
 
-    /**
-     * Perform variant 13 of David Stafford's 64-bit mix function.
-     * This is the mix function used in the {@link SplitMix64} RNG.
-     *
-     * <p>This is ranked first of the top 14 Stafford mixers.
-     *
-     * @param z the input value
-     * @return the output value
-     * @see <a 
href="http://zimbry.blogspot.com/2011/09/better-bit-mixing-improving-on.html";>Better
-     *      Bit Mixing - Improving on MurmurHash3&#39;s 64-bit Finalizer.</a>
-     */
-    private static long stafford13(long z) {
-        long x = z;
-        x = (x ^ (x >>> 30)) * 0xbf58476d1ce4e5b9L;
-        x = (x ^ (x >>> 27)) * 0x94d049bb133111ebL;
-        return x ^ (x >>> 31);
-    }
-
     /**
      * Creates a {@code long} value from an {@code int}. The conversion
      * is made as if seeding a SplitMix64 RNG and calling nextLong().
@@ -126,7 +108,7 @@ final class Conversions {
      * @return a {@code long}.
      */
     static long int2Long(int input) {
-        return stafford13(input + GOLDEN_RATIO);
+        return MixFunctions.stafford13(input + GOLDEN_RATIO);
     }
 
     /**
@@ -189,13 +171,13 @@ final class Conversions {
         // Process pairs
         final int n = length & ~0x1;
         for (int i = 0; i < n; i += 2) {
-            final long x = stafford13(v += GOLDEN_RATIO);
+            final long x = MixFunctions.stafford13(v += GOLDEN_RATIO);
             output[i] = (int) x;
             output[i + 1] = (int) (x >>> 32);
         }
         // Final value
         if (n < length) {
-            output[n] = (int) stafford13(v + GOLDEN_RATIO);
+            output[n] = (int) MixFunctions.stafford13(v + GOLDEN_RATIO);
         }
         return output;
     }
@@ -213,7 +195,7 @@ final class Conversions {
         long v = input;
         final long[] output = new long[length];
         for (int i = 0; i < length; i++) {
-            output[i] = stafford13(v += GOLDEN_RATIO);
+            output[i] = MixFunctions.stafford13(v += GOLDEN_RATIO);
         }
         return output;
     }
diff --git 
a/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/internal/MixFunctions.java
 
b/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/internal/MixFunctions.java
new file mode 100644
index 00000000..eb8f4fc7
--- /dev/null
+++ 
b/commons-rng-simple/src/main/java/org/apache/commons/rng/simple/internal/MixFunctions.java
@@ -0,0 +1,73 @@
+/*
+ * 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;
+
+/**
+ * Performs mixing of bits.
+ *
+ * @since 1.5
+ */
+final class MixFunctions {
+    /**
+     * The fractional part of the the golden ratio, phi, scaled to 64-bits and 
rounded to odd.
+     * This can be used as an increment for a Weyl sequence.
+     *
+     * @see <a href="https://en.wikipedia.org/wiki/Golden_ratio";>Golden 
ratio</a>
+     */
+    static final long GOLDEN_RATIO_64 = 0x9e3779b97f4a7c15L;
+    /**
+     * The fractional part of the the golden ratio, phi, scaled to 32-bits and 
rounded to odd.
+     * This can be used as an increment for a Weyl sequence.
+     *
+     * @see <a href="https://en.wikipedia.org/wiki/Golden_ratio";>Golden 
ratio</a>
+     */
+    static final int GOLDEN_RATIO_32 = 0x9e3779b9;
+
+    /** No instances. */
+    private MixFunctions() {}
+
+    /**
+     * Perform variant 13 of David Stafford's 64-bit mix function.
+     * This is the mix function used in the
+     * {@link org.apache.commons.rng.core.source64.SplitMix64 SplitMix64} RNG.
+     *
+     * <p>This is ranked first of the top 14 Stafford mixers.
+     *
+     * @param x the input value
+     * @return the output value
+     * @see <a 
href="http://zimbry.blogspot.com/2011/09/better-bit-mixing-improving-on.html";>Better
+     *      Bit Mixing - Improving on MurmurHash3&#39;s 64-bit Finalizer.</a>
+     */
+    static long stafford13(long x) {
+        x = (x ^ (x >>> 30)) * 0xbf58476d1ce4e5b9L;
+        x = (x ^ (x >>> 27)) * 0x94d049bb133111ebL;
+        return x ^ (x >>> 31);
+    }
+
+    /**
+     * Perform the finalising 32-bit mix function of Austin Appleby's 
MurmurHash3.
+     *
+     * @param x the input value
+     * @return the output value
+     * @see <a href="https://github.com/aappleby/smhasher";>SMHasher</a>
+     */
+    static int murmur3(int x) {
+        x = (x ^ (x >>> 16)) * 0x85ebca6b;
+        x = (x ^ (x >>> 13)) * 0xc2b2ae35;
+        return x ^ (x >>> 16);
+    }
+}
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 8639b4da..231e2db0 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
@@ -45,7 +45,7 @@ public enum NativeSeedType {
     /** The seed type is {@code Integer}. */
     INT(Integer.class, 4) {
         @Override
-        public Integer createSeed(int size) {
+        public Integer createSeed(int size, int from, int to) {
             return SeedFactory.createInt();
         }
         @Override
@@ -72,7 +72,7 @@ public enum NativeSeedType {
     /** The seed type is {@code Long}. */
     LONG(Long.class, 8) {
         @Override
-        public Long createSeed(int size) {
+        public Long createSeed(int size, int from, int to) {
             return SeedFactory.createLong();
         }
         @Override
@@ -99,10 +99,11 @@ public enum NativeSeedType {
     /** The seed type is {@code int[]}. */
     INT_ARRAY(int[].class, 4) {
         @Override
-        public int[] createSeed(int size) {
+        public int[] createSeed(int size, int from, int to) {
             // Limit the number of calls to the synchronized method. The 
generator
             // will support self-seeding.
-            return SeedFactory.createIntArray(Math.min(size, 
RANDOM_SEED_ARRAY_SIZE));
+            return SeedFactory.createIntArray(Math.min(size, 
RANDOM_SEED_ARRAY_SIZE),
+                                              from, to);
         }
         @Override
         protected int[] convert(Integer seed, int size) {
@@ -132,10 +133,11 @@ public enum NativeSeedType {
     /** The seed type is {@code long[]}. */
     LONG_ARRAY(long[].class, 8) {
         @Override
-        public long[] createSeed(int size) {
+        public long[] createSeed(int size, int from, int to) {
             // Limit the number of calls to the synchronized method. The 
generator
             // will support self-seeding.
-            return SeedFactory.createLongArray(Math.min(size, 
RANDOM_SEED_ARRAY_SIZE));
+            return SeedFactory.createLongArray(Math.min(size, 
RANDOM_SEED_ARRAY_SIZE),
+                                               from, to);
         }
         @Override
         protected long[] convert(Integer seed, int size) {
@@ -214,7 +216,24 @@ public enum NativeSeedType {
      * @param size The size of the seed (array types only).
      * @return the seed
      */
-    public abstract Object createSeed(int size);
+    public Object createSeed(int size) {
+        // Maintain behaviour since 1.3 to ensure position [0] of array seeds 
is non-zero.
+        return createSeed(size, 0, Math.min(size, 1));
+    }
+
+    /**
+     * 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 
and a
+     * sub-range that must not be all-zero.
+     *
+     * @param size The size of the seed (array types only).
+     * @param from The start of the not all-zero sub-range (inclusive; array 
types only).
+     * @param to The end of the not all-zero sub-range (exclusive; array types 
only).
+     * @return the seed
+     * @throws IndexOutOfBoundsException if the sub-range is out of bounds
+     * @since 1.5
+     */
+    public abstract Object createSeed(int size, int from, int to);
 
     /**
      * Converts the input seed from any of the supported seed types to the 
native seed type.
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 78e76548..cd3c437b 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
@@ -132,27 +132,27 @@ public final class ProviderBuilder {
             NativeSeedType.LONG),
         /** Source of randomness is {@link Well512a}. */
         WELL_512_A(Well512a.class,
-                   16,
+                   16, 0, 16,
                    NativeSeedType.INT_ARRAY),
         /** Source of randomness is {@link Well1024a}. */
         WELL_1024_A(Well1024a.class,
-                    32,
+                    32, 0, 32,
                     NativeSeedType.INT_ARRAY),
         /** Source of randomness is {@link Well19937a}. */
         WELL_19937_A(Well19937a.class,
-                     624,
+                     624, 0, 623,
                      NativeSeedType.INT_ARRAY),
         /** Source of randomness is {@link Well19937c}. */
         WELL_19937_C(Well19937c.class,
-                     624,
+                     624, 0, 623,
                      NativeSeedType.INT_ARRAY),
         /** Source of randomness is {@link Well44497a}. */
         WELL_44497_A(Well44497a.class,
-                     1391,
+                     1391, 0, 1390,
                      NativeSeedType.INT_ARRAY),
         /** Source of randomness is {@link Well44497b}. */
         WELL_44497_B(Well44497b.class,
-                     1391,
+                     1391, 0, 1390,
                      NativeSeedType.INT_ARRAY),
         /** Source of randomness is {@link MersenneTwister}. */
         MT(MersenneTwister.class,
@@ -168,7 +168,7 @@ public final class ProviderBuilder {
                      NativeSeedType.LONG),
         /** Source of randomness is {@link XorShift1024Star}. */
         XOR_SHIFT_1024_S(XorShift1024Star.class,
-                         16,
+                         16, 0, 16,
                          NativeSeedType.LONG_ARRAY),
         /** Source of randomness is {@link TwoCmres}. */
         TWO_CMRES(TwoCmres.class,
@@ -189,55 +189,56 @@ public final class ProviderBuilder {
               NativeSeedType.LONG_ARRAY),
         /** Source of randomness is {@link MultiplyWithCarry256}. */
         MWC_256(MultiplyWithCarry256.class,
-                257,
+                257, 0, 257,
                 NativeSeedType.INT_ARRAY),
         /** Source of randomness is {@link KISSRandom}. */
         KISS(KISSRandom.class,
-             4,
+             // If zero in initial 3 positions the output is a simple LCG
+             4, 0, 3,
              NativeSeedType.INT_ARRAY),
         /** Source of randomness is {@link XorShift1024StarPhi}. */
         XOR_SHIFT_1024_S_PHI(XorShift1024StarPhi.class,
-                             16,
+                             16, 0, 16,
                              NativeSeedType.LONG_ARRAY),
         /** Source of randomness is {@link XoRoShiRo64Star}. */
         XO_RO_SHI_RO_64_S(XoRoShiRo64Star.class,
-                          2,
+                          2, 0, 2,
                           NativeSeedType.INT_ARRAY),
         /** Source of randomness is {@link XoRoShiRo64StarStar}. */
         XO_RO_SHI_RO_64_SS(XoRoShiRo64StarStar.class,
-                           2,
+                           2, 0, 2,
                            NativeSeedType.INT_ARRAY),
         /** Source of randomness is {@link XoShiRo128Plus}. */
         XO_SHI_RO_128_PLUS(XoShiRo128Plus.class,
-                           4,
+                           4, 0, 4,
                            NativeSeedType.INT_ARRAY),
         /** Source of randomness is {@link XoShiRo128StarStar}. */
         XO_SHI_RO_128_SS(XoShiRo128StarStar.class,
-                         4,
+                         4, 0, 4,
                          NativeSeedType.INT_ARRAY),
         /** Source of randomness is {@link XoRoShiRo128Plus}. */
         XO_RO_SHI_RO_128_PLUS(XoRoShiRo128Plus.class,
-                              2,
+                              2, 0, 2,
                               NativeSeedType.LONG_ARRAY),
         /** Source of randomness is {@link XoRoShiRo128StarStar}. */
         XO_RO_SHI_RO_128_SS(XoRoShiRo128StarStar.class,
-                            2,
+                            2, 0, 2,
                             NativeSeedType.LONG_ARRAY),
         /** Source of randomness is {@link XoShiRo256Plus}. */
         XO_SHI_RO_256_PLUS(XoShiRo256Plus.class,
-                           4,
+                           4, 0, 4,
                            NativeSeedType.LONG_ARRAY),
         /** Source of randomness is {@link XoShiRo256StarStar}. */
         XO_SHI_RO_256_SS(XoShiRo256StarStar.class,
-                         4,
+                         4, 0, 4,
                          NativeSeedType.LONG_ARRAY),
         /** Source of randomness is {@link XoShiRo512Plus}. */
         XO_SHI_RO_512_PLUS(XoShiRo512Plus.class,
-                           8,
+                           8, 0, 8,
                            NativeSeedType.LONG_ARRAY),
         /** Source of randomness is {@link XoShiRo512StarStar}. */
         XO_SHI_RO_512_SS(XoShiRo512StarStar.class,
-                         8,
+                         8, 0, 8,
                          NativeSeedType.LONG_ARRAY),
         /** Source of randomness is {@link PcgXshRr32}. */
         PCG_XSH_RR_32(PcgXshRr32.class,
@@ -261,7 +262,10 @@ public final class ProviderBuilder {
                 NativeSeedType.LONG),
         /** Source of randomness is {@link MiddleSquareWeylSequence}. */
         MSWS(MiddleSquareWeylSequence.class,
-             3,
+             // Many partially zero seeds can create low quality initial 
output.
+             // The Weyl increment cascades bits into the random state so 
ideally it
+             // has a high number of bit transitions. Minimally ensure it is 
non-zero.
+             3, 2, 3,
              NativeSeedType.LONG_ARRAY) {
             @Override
             protected Object createSeed() {
@@ -356,31 +360,31 @@ public final class ProviderBuilder {
                NativeSeedType.LONG),
         /** Source of randomness is {@link XoShiRo128PlusPlus}. */
         XO_SHI_RO_128_PP(XoShiRo128PlusPlus.class,
-                         4,
+                         4, 0, 4,
                          NativeSeedType.INT_ARRAY),
         /** Source of randomness is {@link XoRoShiRo128PlusPlus}. */
         XO_RO_SHI_RO_128_PP(XoRoShiRo128PlusPlus.class,
-                            2,
+                            2, 0, 2,
                             NativeSeedType.LONG_ARRAY),
         /** Source of randomness is {@link XoShiRo256PlusPlus}. */
         XO_SHI_RO_256_PP(XoShiRo256PlusPlus.class,
-                         4,
+                         4, 0, 4,
                          NativeSeedType.LONG_ARRAY),
         /** Source of randomness is {@link XoShiRo512PlusPlus}. */
         XO_SHI_RO_512_PP(XoShiRo512PlusPlus.class,
-                         8,
+                         8, 0, 8,
                          NativeSeedType.LONG_ARRAY),
         /** Source of randomness is {@link XoRoShiRo1024PlusPlus}. */
         XO_RO_SHI_RO_1024_PP(XoRoShiRo1024PlusPlus.class,
-                             16,
+                             16, 0, 16,
                              NativeSeedType.LONG_ARRAY),
         /** Source of randomness is {@link XoRoShiRo1024Star}. */
         XO_RO_SHI_RO_1024_S(XoRoShiRo1024Star.class,
-                            16,
+                            16, 0, 16,
                             NativeSeedType.LONG_ARRAY),
         /** Source of randomness is {@link XoRoShiRo1024StarStar}. */
         XO_RO_SHI_RO_1024_SS(XoRoShiRo1024StarStar.class,
-                             16,
+                             16, 0, 16,
                              NativeSeedType.LONG_ARRAY),
         /** Source of randomness is {@link PcgXshRr32}. */
         PCG_XSH_RR_32_OS(PcgXshRr32.class,
@@ -399,6 +403,10 @@ public final class ProviderBuilder {
         private final Class<? extends UniformRandomProvider> rng;
         /** Native seed size. Used for array seeds. */
         private final int nativeSeedSize;
+        /** Start of the not all-zero sub-range for array seeds (inclusive). */
+        private final int notAllZeroFrom;
+        /** End of the not all-zero sub-range for array seeds (exclusive). */
+        private final int notAllZeroTo;
         /** Define the parameter types of the data needed to build the 
generator. */
         private final Class<?>[] args;
         /** Native seed type. Used to create a seed or convert input seeds. */
@@ -412,6 +420,8 @@ public final class ProviderBuilder {
         /**
          * Create a new instance.
          *
+         * <p>Used when the seed array has no requirement for a not all-zero 
sub-range.
+         *
          * @param rng Source type.
          * @param nativeSeedSize Native seed size (array types only).
          * @param nativeSeedType Native seed type.
@@ -421,8 +431,36 @@ public final class ProviderBuilder {
                              int nativeSeedSize,
                              NativeSeedType nativeSeedType,
                              Class<?>... args) {
+            this(rng, nativeSeedSize, 0, 0, nativeSeedType, args);
+        }
+
+        /**
+         * Create a new instance.
+         *
+         * <p>Note: The sub-range of an array seed that is not all-zero can be 
specified.
+         * If the native seed array is used to represent a number of bits
+         * that is not an exact multiple of the number of bytes in the seed, 
then a
+         * safe approach is to specify the sub-range using a smaller size than 
the
+         * full length seed. For example a {@link Well19937a} generator uses 
19937
+         * bits and has a seed bit length of 19968. A safe range is [0, 19937 
/ 32).
+         *
+         * @param rng Source type.
+         * @param nativeSeedSize Native seed size (array types only).
+         * @param notAllZeroFrom The start of the not all-zero sub-range 
(inclusive).
+         * @param notAllZeroTo The end of the not all-zero sub-range 
(exclusive).
+         * @param nativeSeedType Native seed type.
+         * @param args Additional data needed to create a generator instance.
+         */
+        RandomSourceInternal(Class<? extends UniformRandomProvider> rng,
+                             int nativeSeedSize,
+                             int notAllZeroFrom,
+                             int notAllZeroTo,
+                             NativeSeedType nativeSeedType,
+                             Class<?>... args) {
             this.rng = rng;
             this.nativeSeedSize = nativeSeedSize;
+            this.notAllZeroFrom = notAllZeroFrom;
+            this.notAllZeroTo = notAllZeroTo;
             this.nativeSeedType = nativeSeedType;
             // Build the complete list of class types for the constructor
             this.args = (Class<?>[]) 
Array.newInstance(args.getClass().getComponentType(), 1 + args.length);
@@ -471,16 +509,6 @@ public final class ProviderBuilder {
             return seed != null && getSeed().equals(seed.getClass());
         }
 
-        /**
-         * 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.
          *
@@ -550,7 +578,8 @@ public final class ProviderBuilder {
          * @since 1.3
          */
         protected Object createSeed() {
-            return nativeSeedType.createSeed(nativeSeedSize);
+            // Ensure the seed is not all-zero in the sub-range
+            return nativeSeedType.createSeed(nativeSeedSize, notAllZeroFrom, 
notAllZeroTo);
         }
 
         /**
@@ -566,7 +595,13 @@ public final class ProviderBuilder {
          * @since 1.3
          */
         protected byte[] createByteArraySeed(UniformRandomProvider source) {
-            return SeedFactory.createByteArray(source, getSeedByteSize());
+            // Ensure the seed is not all-zero in the sub-range.
+            // Note: Convert the native seed array size/positions to byte 
size/positions.
+            final int bytes = nativeSeedType.getBytes();
+            return SeedFactory.createByteArray(source,
+                bytes * nativeSeedSize,
+                bytes * notAllZeroFrom,
+                bytes * notAllZeroTo);
         }
 
         /**
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 ed92cc91..5139125f 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
@@ -123,6 +123,24 @@ public final class SeedFactory {
      * @return an array of {@code n} random numbers.
      */
     public static int[] createIntArray(int n) {
+        // Behaviour from v1.3 is to ensure the first position is non-zero
+        return createIntArray(n, 0, Math.min(n, 1));
+    }
+
+    /**
+     * Creates an array of {@code int} numbers for use as a seed.
+     * Optionally ensure a sub-range of the array is not all-zero.
+     *
+     * <p>This method is package-private for use by {@link NativeSeedType}.
+     *
+     * @param n Size of the array to create.
+     * @param from The start of the not all-zero sub-range (inclusive).
+     * @param to The end of the not all-zero sub-range (exclusive).
+     * @return an array of {@code n} random numbers.
+     * @throws IndexOutOfBoundsException if the sub-range is invalid
+     * @since 1.5
+     */
+    static int[] createIntArray(int n, int from, int to) {
         final int[] seed = new int[n];
         // Compute the size that can be filled with complete blocks
         final int blockSize = INT_ARRAY_BLOCK_SIZE * (n / 
INT_ARRAY_BLOCK_SIZE);
@@ -136,7 +154,7 @@ public final class SeedFactory {
         if (i != n) {
             fillIntArray(seed, i, n);
         }
-        ensureNonZero(seed);
+        ensureNonZero(seed, from, to);
         return seed;
     }
 
@@ -147,6 +165,24 @@ public final class SeedFactory {
      * @return an array of {@code n} random numbers.
      */
     public static long[] createLongArray(int n) {
+        // Behaviour from v1.3 is to ensure the first position is non-zero
+        return createLongArray(n, 0, Math.min(n, 1));
+    }
+
+    /**
+     * Creates an array of {@code long} numbers for use as a seed.
+     * Optionally ensure a sub-range of the array is not all-zero.
+     *
+     * <p>This method is package-private for use by {@link NativeSeedType}.
+     *
+     * @param n Size of the array to create.
+     * @param from The start of the not all-zero sub-range (inclusive).
+     * @param to The end of the not all-zero sub-range (exclusive).
+     * @return an array of {@code n} random numbers.
+     * @throws IndexOutOfBoundsException if the sub-range is invalid
+     * @since 1.5
+     */
+    static long[] createLongArray(int n, int from, int to) {
         final long[] seed = new long[n];
         // Compute the size that can be filled with complete blocks
         final int blockSize = LONG_ARRAY_BLOCK_SIZE * (n / 
LONG_ARRAY_BLOCK_SIZE);
@@ -160,7 +196,7 @@ public final class SeedFactory {
         if (i != n) {
             fillLongArray(seed, i, n);
         }
-        ensureNonZero(seed);
+        ensureNonZero(seed, from, to);
         return seed;
     }
 
@@ -204,97 +240,159 @@ public final class SeedFactory {
 
     /**
      * 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.
+     * randomness. A sub-range can be specified that must not contain all 
zeros.
      *
      * @param source Source of randomness.
      * @param n Size of the array to create.
+     * @param from The start of the not all-zero sub-range (inclusive).
+     * @param to The end of the not all-zero sub-range (exclusive).
      * @return an array of {@code n} random numbers.
      */
     static byte[] createByteArray(UniformRandomProvider source,
-                                  int n) {
+                                  int n,
+                                  int from,
+                                  int to) {
         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);
+        ensureNonZero(seed, from, to, source);
         return seed;
     }
 
     /**
-     * Ensure the seed is non-zero at the first position in the array.
+     * Ensure the seed is not all-zero within the specified sub-range.
      *
-     * <p>This method will replace a zero at 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>
+     * <p>This method will check the sub-range and if all are zero it will 
fill the range
+     * with a random sequence seeded from the default source of random int 
values. The
+     * fill ensures position {@code from} has a non-zero value; and the entire 
sub-range
+     * has a maximum of one zero. The method ensures any length sub-range 
contains
+     * non-zero bits. The output seed is suitable for generators that cannot 
be seeded
+     * with all zeros in the specified sub-range.</p>
      *
      * @param seed Seed array (modified in place).
+     * @param from The start of the not all-zero sub-range (inclusive).
+     * @param to The end of the not all-zero sub-range (exclusive).
+     * @throws IndexOutOfBoundsException if the sub-range is invalid
      * @see #createInt()
      */
-    static void ensureNonZero(int[] seed) {
-        // Zero occurs 1 in 2^32
-        if (seed.length != 0 && seed[0] == 0) {
-            do {
-                seed[0] = createInt();
-            } while (seed[0] == 0);
+    static void ensureNonZero(int[] seed, int from, int to) {
+        if (from >= to) {
+            return;
+        }
+        // No check on the range so an IndexOutOfBoundsException will occur if 
invalid
+        for (int i = from; i < to; i++) {
+            if (seed[i] != 0) {
+                return;
+            }
+        }
+        // Fill with non-zero values using a SplitMix-style PRNG.
+        // The range is at least 1.
+        // To ensure the first value is not zero requires the input to the mix 
function
+        // to be non-zero. This is ensured if the start is even since the 
increment is odd.
+        int x = createInt() << 1;
+        for (int i = from; i < to; i++) {
+            seed[i] = MixFunctions.murmur3(x += MixFunctions.GOLDEN_RATIO_32);
         }
     }
 
     /**
-     * Ensure the seed is non-zero at the first position in the array.
+     * Ensure the seed is not all-zero within the specified sub-range.
      *
-     * <p>This method will replace a zero at 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>
+     * <p>This method will check the sub-range and if all are zero it will 
fill the range
+     * with a random sequence seeded from the default source of random long 
values. The
+     * fill ensures position {@code from} has a non-zero value; and the entire 
sub-range
+     * has a maximum of one zero. The method ensures any length sub-range 
contains
+     * non-zero bits. The output seed is suitable for generators that cannot 
be seeded
+     * with all zeros in the specified sub-range.</p>
      *
      * @param seed Seed array (modified in place).
+     * @param from The start of the not all-zero sub-range (inclusive).
+     * @param to The end of the not all-zero sub-range (exclusive).
+     * @throws IndexOutOfBoundsException if the sub-range is invalid
      * @see #createLong()
      */
-    static void ensureNonZero(long[] seed) {
-        // Zero occurs 1 in 2^64
-        if (seed.length != 0 && seed[0] == 0) {
-            do {
-                seed[0] = createLong();
-            } while (seed[0] == 0);
+    static void ensureNonZero(long[] seed, int from, int to) {
+        if (from >= to) {
+            return;
+        }
+        // No check on the range so an IndexOutOfBoundsException will occur if 
invalid
+        for (int i = from; i < to; i++) {
+            if (seed[i] != 0) {
+                return;
+            }
+        }
+        // Fill with non-zero values using a SplitMix-style PRNG.
+        // The range is at least 1.
+        // To ensure the first value is not zero requires the input to the mix 
function
+        // to be non-zero. This is ensured if the start is even since the 
increment is odd.
+        long x = createLong() << 1;
+        for (int i = from; i < to; i++) {
+            seed[i] = MixFunctions.stafford13(x += 
MixFunctions.GOLDEN_RATIO_64);
         }
     }
 
     /**
-     * Ensure the seed is not zero at all positions in the array.
+     * Ensure the seed is not all-zero within the specified sub-range.
      *
-     * <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>
+     * <p>This method will check the sub-range and if all are zero it will 
fill the range
+     * with a random sequence seeded from the provided source of randomness. 
If the range
+     * length is above 8 then the first 8 bytes in the range are ensured to 
not all be
+     * zero. If the range length is below 8 then the first byte in the range 
is ensured to
+     * be non-zero. The method ensures any length sub-range contains non-zero 
bits. The
+     * output seed is suitable for generators that cannot be seeded with all 
zeros in the
+     * specified sub-range.</p>
      *
      * @param seed Seed array (modified in place).
-     * @see #createInt()
+     * @param from The start of the not all-zero sub-range (inclusive).
+     * @param to The end of the not all-zero sub-range (exclusive).
+     * @param source Source of randomness.
+     * @throws IndexOutOfBoundsException if the sub-range is invalid
      */
-    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);
+    static void ensureNonZero(byte[] seed, int from, int to, 
UniformRandomProvider source) {
+        if (from >= to) {
+            return;
         }
-    }
+        // No check on the range so an IndexOutOfBoundsException will occur if 
invalid
+        for (int i = from; i < to; i++) {
+            if (seed[i] != 0) {
+                return;
+            }
+        }
+        // Defend against a faulty source of randomness (which supplied all 
zero bytes)
+        // by filling with non-zero values using a SplitMix-style PRNG seeded 
from the source.
+        // The range is at least 1.
+        // To ensure the first value is not zero requires the input to the mix 
function
+        // to be non-zero. This is ensured if the start is even since the 
increment is odd.
+        long x = source.nextLong() << 1;
 
-    /**
-     * 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;
+        // Process in blocks of 8.
+        // Get the length without the final 3 bits set for a multiple of 8.
+        final int len = (to - from) & ~0x7;
+        final int end = from + len;
+        int i = from;
+        while (i < end) {
+            long v = MixFunctions.stafford13(x += 
MixFunctions.GOLDEN_RATIO_64);
+            for (int j = 0; j < 8; j++) {
+                seed[i++] = (byte) v;
+                v >>>= 8;
+            }
+        }
+
+        if (i < to) {
+            // The final bytes.
+            long v = MixFunctions.stafford13(x + MixFunctions.GOLDEN_RATIO_64);
+            // Note the special case where no blocks have been processed 
requires these
+            // bytes to be non-zero, i.e. (to - from) < 8. In this case the 
value 'v' will
+            // be non-zero due to the initialisation of 'x' as even.
+            // Rotate the value so the least significant byte is non-zero. The 
rotation
+            // in bits is rounded down to the nearest 8-bit block to ensure a 
byte rotation.
+            if (len == 0) {
+                v = Long.rotateRight(v, Long.numberOfTrailingZeros(v) & ~0x7);
+            }
+            while (i < to) {
+                seed[i++] = (byte) v;
+                v >>>= 8;
             }
         }
-        return true;
     }
 
     /**
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 6a0ef5fe..4fc5e20a 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
@@ -38,6 +38,7 @@ import 
org.apache.commons.rng.LongJumpableUniformRandomProvider;
 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.LongProvider;
 import org.apache.commons.rng.core.source64.SplitMix64;
 
 /**
@@ -190,8 +191,6 @@ class ProvidersCommonParametricTest {
         final Object[] originalArgs = data.getArgs();
         final long[] empty = new long[0];
         Assumptions.assumeTrue(originalSource.isNativeSeed(empty));
-        // The Middle-Square Weyl Sequence generator cannot self-seed
-        Assumptions.assumeFalse(originalSource == RandomSource.MSWS);
 
         // Exercise the default seeding procedure.
         final UniformRandomProvider rng = originalSource.create(empty, 
originalArgs);
@@ -244,6 +243,31 @@ class ProvidersCommonParametricTest {
         checkNextIntegerInRange(rng, 10, 10000);
     }
 
+    @ParameterizedTest
+    @MethodSource("getProvidersTestData")
+    void testRandomSourceCreateSeedFromInvalidRNG(ProvidersList.Data data) {
+        // Create a RNG that will fill a byte[] seed with all zeros.
+        // The ensure non-zero range checks in the RandomSource should
+        // create a good seed and a functional RNG.
+        final LongProvider badRng = new LongProvider() {
+            @Override
+            public long next() {
+                return 0;
+            }
+        };
+
+        final RandomSource originalSource = data.getSource();
+        final byte[] seed = originalSource.createSeed(badRng);
+
+        // The seed generation should be repeatable
+        Assertions.assertArrayEquals(seed, originalSource.createSeed(badRng));
+
+        // The RNG seed will create a functional generator
+        final Object[] originalArgs = data.getArgs();
+        final UniformRandomProvider rng = originalSource.create(seed, 
originalArgs);
+        checkNextIntegerInRange(rng, 10, 10000);
+    }
+
     // State save and restore tests.
 
     @SuppressWarnings("unused")
diff --git 
a/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/internal/MixFunctionsTest.java
 
b/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/internal/MixFunctionsTest.java
new file mode 100644
index 00000000..c291c077
--- /dev/null
+++ 
b/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/internal/MixFunctionsTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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 java.util.SplittableRandom;
+import org.apache.commons.rng.core.source64.SplitMix64;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+/**
+ * Tests for {@link MixFunctions}.
+ */
+class MixFunctionsTest {
+    @ParameterizedTest
+    @ValueSource(longs = {0, -1, 1, 63812881278371L, -236812734617872L})
+    void testStafford13(long seed) {
+        // Reference output is from a SplitMix64 generator
+        final SplitMix64 rng = new SplitMix64(seed);
+
+        long x = seed;
+        for (int i = 0; i < 20; i++) {
+            Assertions.assertEquals(rng.nextLong(), MixFunctions.stafford13(x 
+= MixFunctions.GOLDEN_RATIO_64));
+        }
+    }
+
+    @Test
+    void testMurmur3() {
+        // Reference output from the c++ function fmix32(uint32_t) in smhasher.
+        // https://github.com/aappleby/smhasher/blob/master/src/MurmurHash3.cpp
+        final int seedA = 0x012de1ba;
+        final int[] valuesA = {
+            0x2f66c8b6, 0x256c0269, 0x054ef409, 0x402425ba, 0x78ebf590, 
0x76bea1db,
+            0x8bf5dcbe, 0x104ecdd4, 0x43cfc87e, 0xa33c7643, 0x4d210f56, 
0xfa12093d,
+        };
+
+        int x = seedA;
+        for (int z : valuesA) {
+            Assertions.assertEquals(z, MixFunctions.murmur3(x += 
MixFunctions.GOLDEN_RATIO_32));
+        }
+
+        // Values from a seed of zero
+        final int[] values0 = {
+            0x92ca2f0e, 0x3cd6e3f3, 0x1b147dcc, 0x4c081dbf, 0x487981ab, 
0xdb408c9d,
+            0x78bc1b8f, 0xd83072e5, 0x65cbdd54, 0x1f4b8cef, 0x91783bb0, 
0x0231739b,
+        };
+
+        x = 0;
+        for (int z : values0) {
+            Assertions.assertEquals(z, MixFunctions.murmur3(x += 
MixFunctions.GOLDEN_RATIO_32));
+        }
+    }
+
+    /**
+     * Test the reverse of the Stafford 13 mix function.
+     */
+    @Test
+    void testUnmixStafford13() {
+        final SplittableRandom rng = new SplittableRandom();
+        for (int i = 0; i < 100; i++) {
+            final long x = rng.nextLong();
+            final long y = MixFunctions.stafford13(x);
+            Assertions.assertEquals(x, unmixStafford13(y));
+        }
+    }
+
+    /**
+     * Reverse the Stafford 13 mix function.
+     *
+     * <p>This can be used to generate specific seed values for a 
SplitMix-style RNG using
+     * the Stafford 13 mix constants, for example in the SeedFactoryTest. It 
is left in
+     * the test source code as a reference to allow computation of test seeds.
+     *
+     * @param x Argument
+     * @return the result
+     */
+    private static long unmixStafford13(long x) {
+        // Multiplicative inverse:
+        // 
https://lemire.me/blog/2017/09/18/computing-the-inverse-of-odd-integers/
+        // Invert 0xbf58476d1ce4e5b9L
+        final long u1 = 0x96de1b173f119089L;
+        // Invert 0x94d049bb133111ebL
+        final long u2 = 0x319642b2d24d8ec3L;
+        // Inversion of xor right-shift operations exploits the facts:
+        // 1. A xor operation can be used to recover itself:
+        //   x ^ y = z
+        //   z ^ y = x
+        //   z ^ x = y
+        // 2. During xor right-shift of size n the top n-bits are unchanged.
+        // 3. Known values from the top bits can be used to recover the next 
set of n-bits.
+        // Recovery is done by xoring with the shifted argument, then doubling 
the right shift.
+        // This is iterated until all bits are recovered. With initial large 
shifts only one
+        // doubling is required.
+        x = x ^ (x >>> 31);
+        x ^= x >>> 62;
+        x *= u2;
+        x = x ^ (x >>> 27);
+        x ^= x >>> 54;
+        x *= u1;
+        x = x ^ (x >>> 30);
+        x ^= x >>> 60;
+        return x;
+    }
+}
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 1022d3c2..c314303a 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
@@ -17,6 +17,7 @@
 package org.apache.commons.rng.simple.internal;
 
 import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Assumptions;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.EnumSource;
 import org.junit.jupiter.api.Test;
@@ -93,6 +94,30 @@ class NativeSeedTypeParametricTest {
         }
     }
 
+    /**
+     * Test the seed can be checked as non-zero in a sub-range. This uses a 
bad range to
+     * generate an expected exception. The non-zero requirement is not tested 
as random
+     * seeding is very unlikely to create even a single 32-bit integer that is 
zero and
+     * requires correction. This test at least verifies the (from, to) 
sub-range arguments
+     * are used.
+     *
+     * @param nativeSeedType Native seed type.
+     */
+    @ParameterizedTest
+    @EnumSource
+    void testCreateArraySeedWithNonZeroRangeThrows(NativeSeedType 
nativeSeedType) {
+        Assumptions.assumeTrue(nativeSeedType.getType().isArray(), "Not an 
array type");
+        // Arguments: (size, from, to)
+        // Since the seed is created randomly it is very likely non-zero.
+        // Use a sub-range that will throw on the first array access; otherwise
+        // if the array location is valid it will likely be non-zero and no 
further
+        // checks are made.
+        Assertions.assertThrows(IndexOutOfBoundsException.class, () -> 
nativeSeedType.createSeed(0, 0, 1));
+        Assertions.assertThrows(IndexOutOfBoundsException.class, () -> 
nativeSeedType.createSeed(1, -1, 1));
+        Assertions.assertThrows(IndexOutOfBoundsException.class, () -> 
nativeSeedType.createSeed(1, 1, 2));
+        Assertions.assertThrows(IndexOutOfBoundsException.class, () -> 
nativeSeedType.createSeed(1, 2, 3));
+    }
+
     /**
      * Test the seed can be created, converted to a byte[] and then back to 
the native type.
      *
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 02526675..5ed3eb7a 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
@@ -17,12 +17,18 @@
 package org.apache.commons.rng.simple.internal;
 
 import java.util.Map;
+import java.util.Arrays;
 import java.util.HashMap;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.junit.jupiter.params.provider.ValueSource;
 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.source64.RandomLongSource;
+import org.apache.commons.rng.core.source64.SplitMix64;
 import org.apache.commons.rng.core.util.NumberFactory;
 
 /**
@@ -113,6 +119,11 @@ class SeedFactoryTest {
         }
     }
 
+    @Test
+    void testCreateIntArrayWithZeroSize() {
+        Assertions.assertArrayEquals(new int[0], 
SeedFactory.createIntArray(0));
+    }
+
     @Test
     void testCreateIntArrayWithCompleteBlockSize() {
         // Block size is 8 for int
@@ -144,6 +155,11 @@ class SeedFactoryTest {
         assertMonobit(bitCount, numberOfBits);
     }
 
+    @Test
+    void testCreateLongArrayWithZeroSize() {
+        Assertions.assertArrayEquals(new long[0], 
SeedFactory.createLongArray(0));
+    }
+
     @Test
     void testCreateLongArrayWithCompleteBlockSize() {
         // Block size is 4 for long
@@ -243,12 +259,12 @@ class SeedFactoryTest {
             }
         };
 
-        final byte[] seed = SeedFactory.createByteArray(rng, expected.length);
+        final byte[] seed = SeedFactory.createByteArray(rng, expected.length, 
0, expected.length);
         Assertions.assertArrayEquals(expected, seed);
     }
 
     @Test
-    void testCreateByteArrayWithAllZeroBytesUpdatesPosition0() {
+    void testCreateByteArrayWithAllZeroBytesUpdatesFromTo() {
         final UniformRandomProvider rng = new IntProvider() {
             @Override
             public int next() {
@@ -256,75 +272,254 @@ class SeedFactoryTest {
                 return 0;
             }
         };
-        // Test the method only replaces position 0
-        final byte[] seed = SeedFactory.createByteArray(rng, 4);
-        Assertions.assertNotEquals(0, seed[0], "Zero at position 0 should be 
modified");
-        for (int i = 1; i < seed.length; i++) {
-            Assertions.assertEquals(0, seed[i], "Position above 0 should be 
unmodified");
+        // Test the method only replaces the target sub-range.
+        // Test all sub-ranges.
+        final int size = 4;
+        for (int start = 0; start < size; start++) {
+            final int from = start;
+            for (int end = start; end < size; end++) {
+                final int to = end;
+                final byte[] seed = SeedFactory.createByteArray(rng, 4, from, 
to);
+
+                // Validate
+                for (int i = 0; i < from; i++) {
+                    final int index = i;
+                    Assertions.assertEquals(0, seed[i],
+                        () -> String.format("[%d, %d) zero at position %d 
should not be modified",
+                            from, to, index));
+                }
+                if (to > from) {
+                    byte allBits = 0;
+                    for (int i = from; i < to; i++) {
+                        allBits |= seed[i];
+                    }
+                    Assertions.assertNotEquals(0, allBits,
+                        () -> String.format("[%d, %d) should not be all zero", 
from, to));
+                }
+                for (int i = to; i < size; i++) {
+                    final int index = i;
+                    Assertions.assertEquals(0, seed[i],
+                        () -> String.format("[%d, %d) zero at position %d 
should not be modified",
+                            from, to, index));
+                }
+            }
         }
     }
 
     @Test
     void testEnsureNonZeroIntArrayIgnoresEmptySeed() {
         final int[] seed = new int[0];
-        SeedFactory.ensureNonZero(seed);
+        SeedFactory.ensureNonZero(seed, 0, 0);
         // Note: Nothing to assert.
         // This tests an ArrayIndexOutOfBoundsException does not occur.
     }
 
-    @Test
-    void testEnsureNonZeroIntArrayIgnoresNonZeroPosition0() {
-        final int position0 = 123;
-        final int[] seed = new int[] {position0, 0, 0, 0};
-        final int[] before = seed.clone();
-        SeedFactory.ensureNonZero(seed);
-        Assertions.assertEquals(position0, seed[0], "Non-zero at position 0 
should be unmodified");
-        for (int i = 1; i < seed.length; i++) {
-            Assertions.assertEquals(before[i], seed[i], "Position above 0 
should be unmodified");
-        }
+    @ParameterizedTest
+    @CsvSource({
+        "0, 0, 1",
+        "1, -1, 1",
+        "1, 0, 2",
+        "1, 1, 2",
+    })
+    void testEnsureNonZeroIntArrayThrowsWithInvalidRange(int n, int from, int 
to) {
+        final int[] seed = new int[n];
+        Assertions.assertThrows(IndexOutOfBoundsException.class,
+            () -> SeedFactory.ensureNonZero(seed, from, to));
     }
 
+
     @Test
-    void testEnsureNonZeroIntArrayUpdatesZeroPosition0() {
-        // Test the method replaces position 0 even if the rest of the array 
is non-zero
-        final int[] seed = new int[] {0, 123, 456, 789};
-        final int[] before = seed.clone();
-        SeedFactory.ensureNonZero(seed);
-        Assertions.assertNotEquals(0, seed[0], "Zero at position 0 should be 
modified");
-        for (int i = 1; i < seed.length; i++) {
-            Assertions.assertEquals(before[i], seed[i], "Position above 0 
should be unmodified");
+    void testEnsureNonZeroIntArrayWithAllZeroBytesUpdatesFromTo() {
+        // Test the method only replaces the target sub-range.
+        // Test all sub-ranges.
+        final int size = 4;
+        final int[] seed = new int[size];
+        for (int start = 0; start < size; start++) {
+            final int from = start;
+            for (int end = start; end < size; end++) {
+                final int to = end;
+                Arrays.fill(seed, 0);
+                SeedFactory.ensureNonZero(seed, from, to);
+
+                // Validate
+                for (int i = 0; i < from; i++) {
+                    final int index = i;
+                    Assertions.assertEquals(0, seed[i],
+                        () -> String.format("[%d, %d) zero at position %d 
should not be modified",
+                            from, to, index));
+                }
+                if (to > from) {
+                    int allBits = 0;
+                    for (int i = from; i < to; i++) {
+                        allBits |= seed[i];
+                    }
+                    Assertions.assertNotEquals(0, allBits,
+                        () -> String.format("[%d, %d) should not be all zero", 
from, to));
+                }
+                for (int i = to; i < size; i++) {
+                    final int index = i;
+                    Assertions.assertEquals(0, seed[i],
+                        () -> String.format("[%d, %d) zero at position %d 
should not be modified",
+                            from, to, index));
+                }
+            }
         }
     }
 
     @Test
     void testEnsureNonZeroLongArrayIgnoresEmptySeed() {
         final long[] seed = new long[0];
-        SeedFactory.ensureNonZero(seed);
+        SeedFactory.ensureNonZero(seed, 0, 0);
         // Note: Nothing to assert.
         // This tests an ArrayIndexOutOfBoundsException does not occur.
     }
 
+    @ParameterizedTest
+    @CsvSource({
+        "0, 0, 1",
+        "1, -1, 1",
+        "1, 0, 2",
+        "1, 1, 2",
+    })
+    void testEnsureNonZeroLongArrayThrowsWithInvalidRange(int n, int from, int 
to) {
+        final long[] seed = new long[n];
+        Assertions.assertThrows(IndexOutOfBoundsException.class,
+            () -> SeedFactory.ensureNonZero(seed, from, to));
+    }
+
+
     @Test
-    void testEnsureNonZeroLongArrayIgnoresNonZeroPosition0() {
-        final long position0 = 123;
-        final long[] seed = new long[] {position0, 0, 0, 0};
-        final long[] before = seed.clone();
-        SeedFactory.ensureNonZero(seed);
-        Assertions.assertEquals(position0, seed[0], "Non-zero at position 0 
should be unmodified");
-        for (int i = 1; i < seed.length; i++) {
-            Assertions.assertEquals(before[i], seed[i], "Position above 0 
should be unmodified");
+    void testEnsureNonZeroLongArrayWithAllZeroBytesUpdatesFromTo() {
+        // Test the method only replaces the target sub-range.
+        // Test all sub-ranges.
+        final int size = 4;
+        final long[] seed = new long[size];
+        for (int start = 0; start < size; start++) {
+            final int from = start;
+            for (int end = start; end < size; end++) {
+                final int to = end;
+                Arrays.fill(seed, 0);
+                SeedFactory.ensureNonZero(seed, from, to);
+
+                // Validate
+                for (int i = 0; i < from; i++) {
+                    final int index = i;
+                    Assertions.assertEquals(0, seed[i],
+                        () -> String.format("[%d, %d) zero at position %d 
should not be modified",
+                            from, to, index));
+                }
+                if (to > from) {
+                    long allBits = 0;
+                    for (int i = from; i < to; i++) {
+                        allBits |= seed[i];
+                    }
+                    Assertions.assertNotEquals(0, allBits,
+                        () -> String.format("[%d, %d) should not be all zero", 
from, to));
+                }
+                for (int i = to; i < size; i++) {
+                    final int index = i;
+                    Assertions.assertEquals(0, seed[i],
+                        () -> String.format("[%d, %d) zero at position %d 
should not be modified",
+                            from, to, index));
+                }
+            }
         }
     }
 
     @Test
-    void testEnsureNonZeroLongArrayUpdatesZeroPosition0() {
-        // Test the method replaces position 0 even if the rest of the array 
is non-zero
-        final long[] seed = new long[] {0, 123, 456, 789};
-        final long[] before = seed.clone();
-        SeedFactory.ensureNonZero(seed);
-        Assertions.assertNotEquals(0, seed[0], "Zero at position 0 should be 
modified");
-        for (int i = 1; i < seed.length; i++) {
-            Assertions.assertEquals(before[i], seed[i], "Position above 0 
should be unmodified");
+    void testEnsureNonZeroByteArrayIgnoresEmptySeed() {
+        final byte[] seed = new byte[0];
+        final UniformRandomProvider rng = new SplitMix64(123);
+        SeedFactory.ensureNonZero(seed, 0, 0, rng);
+        // Note: Nothing to assert.
+        // This tests an ArrayIndexOutOfBoundsException does not occur.
+    }
+
+    @ParameterizedTest
+    @CsvSource({
+        "0, 0, 1",
+        "1, -1, 1",
+        "1, 0, 2",
+        "1, 1, 2",
+    })
+    void testEnsureNonZeroByteArrayThrowsWithInvalidRange(int n, int from, int 
to) {
+        final byte[] seed = new byte[n];
+        final UniformRandomProvider rng = new SplitMix64(123);
+        Assertions.assertThrows(IndexOutOfBoundsException.class,
+            () -> SeedFactory.ensureNonZero(seed, from, to, rng));
+    }
+
+    /**
+     * Test the fixed values returned for the zero byte array source of 
randomness
+     * have specific properties.
+     */
+    @Test
+    void testZeroByteArraySourceOfRandomness() {
+        Assertions.assertEquals(0, 
MixFunctions.stafford13(-MixFunctions.GOLDEN_RATIO_64 + 
MixFunctions.GOLDEN_RATIO_64));
+        Assertions.assertEquals(0, 
MixFunctions.stafford13((1463436497261722119L << 1) + 
MixFunctions.GOLDEN_RATIO_64) & (-1L >>> 56));
+        Assertions.assertEquals(0, 
MixFunctions.stafford13((4949471497809997598L << 1) + 
MixFunctions.GOLDEN_RATIO_64) & (-1L >>> 48));
+        // Note:
+        // Finding a value x where MixFunctions.stafford13((x << 1) + 
MixFunctions.GOLDEN_RATIO_64)
+        // is zero for the least significant 7 bytes used inversion of the mix 
function.
+        // See the MixFunctionsTest for a routine to perform the unmixing.
+        Assertions.assertEquals(0, 
MixFunctions.stafford13((953042962641938212L << 1) + 
MixFunctions.GOLDEN_RATIO_64) & (-1L >>> 8));
+    }
+
+    @ParameterizedTest
+    @ValueSource(longs = {0, -MixFunctions.GOLDEN_RATIO_64, 
1463436497261722119L, 4949471497809997598L, 953042962641938212L})
+    void testEnsureNonZeroByteArrayWithAllZeroBytesUpdatesFromTo(long next) {
+        // Use a fixed source of randomness to demonstrate the method is 
robust.
+        // This should only be used when the sub-range is non-zero length.
+        int[] calls = {0};
+        final UniformRandomProvider rng = new LongProvider() {
+            @Override
+            public long next() {
+                calls[0]++;
+                return next;
+            }
+        };
+
+        // Test the method only replaces the target sub-range.
+        // Test all sub-ranges up to size.
+        final int size = 2 * Long.BYTES;
+        final byte[] seed = new byte[size];
+        for (int start = 0; start < size; start++) {
+            final int from = start;
+            for (int end = start; end < size; end++) {
+                final int to = end;
+                Arrays.fill(seed, (byte) 0);
+                final int before = calls[0];
+                SeedFactory.ensureNonZero(seed, from, to, rng);
+
+                // Validate
+                for (int i = 0; i < from; i++) {
+                    final int index = i;
+                    Assertions.assertEquals(0, seed[i],
+                        () -> String.format("[%d, %d) zero at position %d 
should not be modified",
+                            from, to, index));
+                }
+                if (to > from) {
+                    byte allBits = 0;
+                    for (int i = from; i < to; i++) {
+                        allBits |= seed[i];
+                    }
+                    Assertions.assertNotEquals(0, allBits,
+                        () -> String.format("[%d, %d) should not be all zero", 
from, to));
+                    // Check the source was used to seed the sequence
+                    Assertions.assertNotEquals(before, calls[0],
+                        () -> String.format("[%d, %d) should use the random 
source", from, to));
+                } else {
+                    // Check the source was not used
+                    Assertions.assertEquals(before, calls[0],
+                        () -> String.format("[%d, %d) should not use the 
random source", from, to));
+                }
+                for (int i = to; i < size; i++) {
+                    final int index = i;
+                    Assertions.assertEquals(0, seed[i],
+                        () -> String.format("[%d, %d) zero at position %d 
should not be modified",
+                            from, to, index));
+                }
+            }
         }
     }
 
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index f986aae4..3d4e6874 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -86,6 +86,12 @@ behavioural compatibility between releases; derived types
 may break behavioural compatibility. Any functional changes
 will be recorded in the release notes.
 ">
+      <action dev="aherbert" type="add" issue="174">
+        "RandomSource": Improve support for non-zero seeds. Seeding has been 
changed to specify
+        a sub-range of the seed that must not be all zero. Introduces a 
functional change where
+        byte[] seeds generated by RandomSource with a fixed 
UniformRandomProvider may be different.
+        Seeds are now reproducible across calls using an input random source 
in an identical state.
+      </action>
       <action dev="aherbert" type="fix" issue="175">
         "RandomSource.MSWS": createSeed(UniformRandomProvider) to handle a bad 
RNG.
         This fixes an infinite loop when the RNG output is not suitably random 
to create a seed.
@@ -95,7 +101,7 @@ will be recorded in the release notes.
         minimum length.
       </action>
       <action dev="aherbert" type="update" issue="171">
-        Recuce the memory footprint of the cached boolean and int source for 
the IntProvider
+        Reduce the memory footprint of the cached boolean and int source for 
the IntProvider
         and LongProvider. This change has a performance improvement on some 
JDKs.
         Note: This introduces a functional compatibility change to the output 
from the
         nextInt method of any LongProvider; the output is now little-endian as
diff --git a/src/main/resources/pmd/pmd-ruleset.xml 
b/src/main/resources/pmd/pmd-ruleset.xml
index 95a23d12..697be610 100644
--- a/src/main/resources/pmd/pmd-ruleset.xml
+++ b/src/main/resources/pmd/pmd-ruleset.xml
@@ -116,7 +116,7 @@
         value="//ClassOrInterfaceDeclaration[@SimpleName='ListSampler' or 
@SimpleName='ProviderBuilder'
           or @SimpleName='ThreadLocalRandomSource' or @SimpleName='SeedFactory'
           or @SimpleName='Coordinates' or @SimpleName='Hex' or 
@SimpleName='SpecialMath'
-          or @SimpleName='Conversions']"/>
+          or @SimpleName='Conversions' or @SimpleName='MixFunctions']"/>
       <!-- Allow samplers to have only factory constructors -->
       <property name="utilityClassPattern" 
value="[A-Z][a-zA-Z0-9]+(Utils?|Helper|Sampler)" />
     </properties>

Reply via email to