You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by ah...@apache.org on 2022/05/16 20:54:22 UTC

[commons-rng] 02/05: RNG-176: Update anonymous implementations of UniformRandomProvider

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

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

commit d441d958581c09cef31d6a46d6b70faf1d029598
Author: aherbert <ah...@apache.org>
AuthorDate: Tue May 10 16:42:30 2022 +0100

    RNG-176: Update anonymous implementations of UniformRandomProvider
    
    Only one method is required to implement the interface. This simplifies
    creation of implementations for testing edge cases.
    
    For a delegate implementation then additional methods must be added.
    This applies to RandomSource.unrestorable.
---
 .../jmh/simple/ThreadLocalPerformance.java         |  23 +-
 .../DiscreteProbabilityCollectionSamplerTest.java  |  23 +-
 .../distribution/SmallMeanPoissonSamplerTest.java  |  32 +--
 .../ZigguratNormalizedGaussianSamplerTest.java     |  15 +-
 .../apache/commons/rng/simple/RandomSource.java    | 125 ++++++++++-
 .../apache/commons/rng/simple/RandomAssert.java    |  30 ++-
 .../commons/rng/simple/RandomSourceTest.java       | 244 +++++++++++++++++++++
 src/main/resources/pmd/pmd-ruleset.xml             |   8 +-
 8 files changed, 414 insertions(+), 86 deletions(-)

diff --git a/commons-rng-examples/examples-jmh/src/main/java/org/apache/commons/rng/examples/jmh/simple/ThreadLocalPerformance.java b/commons-rng-examples/examples-jmh/src/main/java/org/apache/commons/rng/examples/jmh/simple/ThreadLocalPerformance.java
index 8b62fe0d..c2f6ccaf 100644
--- a/commons-rng-examples/examples-jmh/src/main/java/org/apache/commons/rng/examples/jmh/simple/ThreadLocalPerformance.java
+++ b/commons-rng-examples/examples-jmh/src/main/java/org/apache/commons/rng/examples/jmh/simple/ThreadLocalPerformance.java
@@ -133,28 +133,7 @@ public class ThreadLocalPerformance {
     @Threads(4)
     public long threadLocalRandomWrapped() {
         final ThreadLocalRandom rand = ThreadLocalRandom.current();
-        final UniformRandomProvider rng = new UniformRandomProvider() {
-            // CHECKSTYLE: stop all
-            @Override
-            public void nextBytes(byte[] bytes) { /* Ignore this. */ }
-            @Override
-            public void nextBytes(byte[] bytes, int start, int len) { /* Ignore this. */ }
-            @Override
-            public int nextInt() { return rand.nextInt(); }
-            @Override
-            public int nextInt(int n) { return rand.nextInt(n); }
-            @Override
-            public long nextLong() { return rand.nextLong(); }
-            @Override
-            public long nextLong(long n) { return rand.nextLong(n); }
-            @Override
-            public boolean nextBoolean() { return rand.nextBoolean(); }
-            @Override
-            public float nextFloat() { return rand.nextFloat(); }
-            @Override
-            public double nextDouble() { return rand.nextDouble(); }
-            // CHECKSTYLE: resume all
-        };
+        final UniformRandomProvider rng = rand::nextLong;
         long result = 0;
         for (int i = 0; i < numValues; i++) {
             result = result ^ rng.nextLong();
diff --git a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/DiscreteProbabilityCollectionSamplerTest.java b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/DiscreteProbabilityCollectionSamplerTest.java
index d6424d95..f41d7b32 100644
--- a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/DiscreteProbabilityCollectionSamplerTest.java
+++ b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/DiscreteProbabilityCollectionSamplerTest.java
@@ -168,18 +168,17 @@ class DiscreteProbabilityCollectionSamplerTest {
         // a probability (for the second item) that hits an edge case.
         final UniformRandomProvider dummyRng = new UniformRandomProvider() {
             private int count;
-            // CHECKSTYLE: stop all
-            public long nextLong(long n) { return 0; }
-            public long nextLong() { return 0; }
-            public int nextInt(int n) { return 0; }
-            public int nextInt() { return 0; }
-            public float nextFloat() { return 0; }
-            // Return 0 then the given probability
-            public double nextDouble() { return (count++ == 0) ? 0 : 1.0; }
-            public void nextBytes(byte[] bytes, int start, int len) {}
-            public void nextBytes(byte[] bytes) {}
-            public boolean nextBoolean() { return false; }
-            // CHECKSTYLE: resume all
+
+            @Override
+            public long nextLong() {
+                return 0;
+            }
+
+            @Override
+            public double nextDouble() {
+                // Return 0 then the 1.0 for the probability
+                return (count++ == 0) ? 0 : 1.0;
+            }
         };
 
         final List<Double> items = Arrays.asList(1d, 2d);
diff --git a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/SmallMeanPoissonSamplerTest.java b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/SmallMeanPoissonSamplerTest.java
index 06670474..6a55e69e 100644
--- a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/SmallMeanPoissonSamplerTest.java
+++ b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/SmallMeanPoissonSamplerTest.java
@@ -21,6 +21,8 @@ import org.apache.commons.rng.sampling.RandomAssert;
 import org.apache.commons.rng.simple.RandomSource;
 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;
 
 /**
  * Test for the {@link SmallMeanPoissonSampler}. The tests hit edge cases for the sampler.
@@ -56,27 +58,15 @@ class SmallMeanPoissonSamplerTest {
     /**
      * Test the sample is bounded to 1000 * mean.
      */
-    @Test
-    void testSampleUpperBounds() {
-        // If the nextDouble() is always 1 then the sample will hit the upper bounds
-        final UniformRandomProvider rng = new UniformRandomProvider() {
-            // CHECKSTYLE: stop all
-            public long nextLong(long n) { return 0; }
-            public long nextLong() { return 0; }
-            public int nextInt(int n) { return 0; }
-            public int nextInt() { return 0; }
-            public float nextFloat() { return 0; }
-            public double nextDouble() { return 1;}
-            public void nextBytes(byte[] bytes, int start, int len) {}
-            public void nextBytes(byte[] bytes) {}
-            public boolean nextBoolean() { return false; }
-            // CHECKSTYLE: resume all
-        };
-        for (double mean : new double[] {0.5, 1, 1.5, 2.2}) {
-            final SharedStateDiscreteSampler sampler = SmallMeanPoissonSampler.of(rng, mean);
-            final int expected = (int) Math.ceil(1000 * mean);
-            Assertions.assertEquals(expected, sampler.sample());
-        }
+    @ParameterizedTest
+    @ValueSource(doubles = {0.5, 1, 1.5, 2.2})
+    void testSampleUpperBounds(double mean) {
+        // If the nextDouble() is always ~1 then the sample will hit the upper bounds.
+        // nextLong() returns -1; nextDouble returns Math.nextDown(1.0).
+        final UniformRandomProvider rng = () -> -1;
+        final SharedStateDiscreteSampler sampler = SmallMeanPoissonSampler.of(rng, mean);
+        final int expected = (int) Math.ceil(1000 * mean);
+        Assertions.assertEquals(expected, sampler.sample());
     }
 
     /**
diff --git a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/ZigguratNormalizedGaussianSamplerTest.java b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/ZigguratNormalizedGaussianSamplerTest.java
index 8f8667f4..430681cf 100644
--- a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/ZigguratNormalizedGaussianSamplerTest.java
+++ b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/ZigguratNormalizedGaussianSamplerTest.java
@@ -31,19 +31,8 @@ class ZigguratNormalizedGaussianSamplerTest {
     void testInfiniteLoop() {
         // A bad implementation whose only purpose is to force access
         // to the rarest branch.
-        final UniformRandomProvider bad = new UniformRandomProvider() {
-                // CHECKSTYLE: stop all
-                public long nextLong(long n) { return 0; }
-                public long nextLong() { return Long.MAX_VALUE; }
-                public int nextInt(int n) { return 0; }
-                public int nextInt() { return Integer.MAX_VALUE; }
-                public float nextFloat() { return 1; }
-                public double nextDouble() { return 1;}
-                public void nextBytes(byte[] bytes, int start, int len) {}
-                public void nextBytes(byte[] bytes) {}
-                public boolean nextBoolean() { return false; }
-                // CHECKSTYLE: resume all
-            };
+        // nextLong() returns Long.MAX_VALUE
+        final UniformRandomProvider bad = () -> Long.MAX_VALUE;
 
         // Infinite loop (in v1.1).
         final ZigguratNormalizedGaussianSampler sampler = new ZigguratNormalizedGaussianSampler(bad);
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 28b49755..2432631f 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
@@ -16,6 +16,9 @@
  */
 package org.apache.commons.rng.simple;
 
+import java.util.stream.DoubleStream;
+import java.util.stream.IntStream;
+import java.util.stream.LongStream;
 import org.apache.commons.rng.UniformRandomProvider;
 import org.apache.commons.rng.RestorableUniformRandomProvider;
 import org.apache.commons.rng.simple.internal.ProviderBuilder;
@@ -1015,20 +1018,36 @@ public enum RandomSource {
 
     /**
      * Wraps the given {@code delegate} generator in a new instance that
-     * does not allow access to the "save/restore" functionality.
+     * only provides access to the {@link UniformRandomProvider} methods.
+     *
+     * <p>This method can be used to prevent access to any methods of the {@code delegate}
+     * that are not defined in the {@link UniformRandomProvider} interface.
+     * For example this will prevent access to the "save/restore" functionality of
+     * any {@link RestorableUniformRandomProvider} {@link #create() created}
+     * by the {@link RandomSource} factory methods, or will prevent access to the jump
+     * functionality of generators.
+     *
+     * <p>Since the method applies to more than the {@link RestorableUniformRandomProvider}
+     * interface it is left to the caller to determine if any methods require hiding,
+     * for example:
+     *
+     * <pre><code>
+     * UniformRandomProvider rng = ...;
+     * if (rng instanceof JumpableUniformRandomProvider) {
+     *    rng = RandomSource.unrestorable(rng);
+     * }
+     * </code></pre>
      *
      * @param delegate Generator to which calls will be delegated.
-     * @return a new instance whose state cannot be saved or restored.
+     * @return a new instance
      */
     public static UniformRandomProvider unrestorable(final UniformRandomProvider delegate) {
         return new UniformRandomProvider() {
-            /** {@inheritDoc} */
             @Override
             public void nextBytes(byte[] bytes) {
                 delegate.nextBytes(bytes);
             }
 
-            /** {@inheritDoc} */
             @Override
             public void nextBytes(byte[] bytes,
                                   int start,
@@ -1036,49 +1055,131 @@ public enum RandomSource {
                 delegate.nextBytes(bytes, start, len);
             }
 
-            /** {@inheritDoc} */
             @Override
             public int nextInt() {
                 return delegate.nextInt();
             }
 
-            /** {@inheritDoc} */
             @Override
             public int nextInt(int n) {
                 return delegate.nextInt(n);
             }
 
-            /** {@inheritDoc} */
+            @Override
+            public int nextInt(int origin, int bound) {
+                return delegate.nextInt(origin, bound);
+            }
+
             @Override
             public long nextLong() {
                 return delegate.nextLong();
             }
 
-            /** {@inheritDoc} */
             @Override
             public long nextLong(long n) {
                 return delegate.nextLong(n);
             }
 
-            /** {@inheritDoc} */
+            @Override
+            public long nextLong(long origin, long bound) {
+                return delegate.nextLong(origin, bound);
+            }
+
             @Override
             public boolean nextBoolean() {
                 return delegate.nextBoolean();
             }
 
-            /** {@inheritDoc} */
             @Override
             public float nextFloat() {
                 return delegate.nextFloat();
             }
 
-            /** {@inheritDoc} */
+            @Override
+            public float nextFloat(float bound) {
+                return delegate.nextFloat(bound);
+            }
+
+            @Override
+            public float nextFloat(float origin, float bound) {
+                return delegate.nextFloat(origin, bound);
+            }
+
             @Override
             public double nextDouble() {
                 return delegate.nextDouble();
             }
 
-            /** {@inheritDoc} */
+            @Override
+            public double nextDouble(double bound) {
+                return delegate.nextDouble(bound);
+            }
+
+            @Override
+            public double nextDouble(double origin, double bound) {
+                return delegate.nextDouble(origin, bound);
+            }
+
+            @Override
+            public IntStream ints() {
+                return delegate.ints();
+            }
+
+            @Override
+            public IntStream ints(int origin, int bound) {
+                return delegate.ints(origin, bound);
+            }
+
+            @Override
+            public IntStream ints(long streamSize) {
+                return delegate.ints(streamSize);
+            }
+
+            @Override
+            public IntStream ints(long streamSize, int origin, int bound) {
+                return delegate.ints(streamSize, origin, bound);
+            }
+
+            @Override
+            public LongStream longs() {
+                return delegate.longs();
+            }
+
+            @Override
+            public LongStream longs(long origin, long bound) {
+                return delegate.longs(origin, bound);
+            }
+
+            @Override
+            public LongStream longs(long streamSize) {
+                return delegate.longs(streamSize);
+            }
+
+            @Override
+            public LongStream longs(long streamSize, long origin, long bound) {
+                return delegate.longs(streamSize, origin, bound);
+            }
+
+            @Override
+            public DoubleStream doubles() {
+                return delegate.doubles();
+            }
+
+            @Override
+            public DoubleStream doubles(double origin, double bound) {
+                return delegate.doubles(origin, bound);
+            }
+
+            @Override
+            public DoubleStream doubles(long streamSize) {
+                return delegate.doubles(streamSize);
+            }
+
+            @Override
+            public DoubleStream doubles(long streamSize, double origin, double bound) {
+                return delegate.doubles(streamSize, origin, bound);
+            }
+
             @Override
             public String toString() {
                 return delegate.toString();
diff --git a/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/RandomAssert.java b/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/RandomAssert.java
index d2955282..6b44dac8 100644
--- a/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/RandomAssert.java
+++ b/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/RandomAssert.java
@@ -46,25 +46,33 @@ public final class RandomAssert {
             Assertions.assertEquals(rng1.nextInt(), rng2.nextInt());
         }
         for (int i = 0; i < 4; i++) {
+            final int min = 31 * i + 3;
             for (int j = 0; j < 5; j++) {
                 final int max = 107 * i + 374 * j + 11;
                 Assertions.assertEquals(rng1.nextInt(max), rng2.nextInt(max));
+                Assertions.assertEquals(rng1.nextInt(min, max), rng2.nextInt(min, max));
             }
         }
         for (int i = 0; i < 23; i++) {
             Assertions.assertEquals(rng1.nextLong(), rng2.nextLong());
         }
         for (int i = 0; i < 4; i++) {
+            final int min = 31 * i + 3;
             for (int j = 0; j < 5; j++) {
-                final long max = (Long.MAX_VALUE << 2) + 107 * i + 374 * j + 11;
+                final long max = (Long.MAX_VALUE >> 2) + 107 * i + 374 * j + 11;
                 Assertions.assertEquals(rng1.nextLong(max), rng2.nextLong(max));
+                Assertions.assertEquals(rng1.nextLong(min, max), rng2.nextLong(min, max));
             }
         }
-        for (int i = 0; i < 103; i++) {
+        for (int i = 0; i < 35; i++) {
             Assertions.assertEquals(rng1.nextFloat(), rng2.nextFloat());
+            Assertions.assertEquals(rng1.nextFloat(12.34f), rng2.nextFloat(12.34f));
+            Assertions.assertEquals(rng1.nextFloat(1.23f, 12.34f), rng2.nextFloat(1.23f, 12.34f));
         }
-        for (int i = 0; i < 79; i++) {
+        for (int i = 0; i < 27; i++) {
             Assertions.assertEquals(rng1.nextDouble(), rng2.nextDouble());
+            Assertions.assertEquals(rng1.nextDouble(12.34), rng2.nextDouble(12.34));
+            Assertions.assertEquals(rng1.nextDouble(1.23, 12.34), rng2.nextDouble(1.23, 12.34));
         }
 
         final int size = 345;
@@ -84,5 +92,21 @@ public final class RandomAssert {
             rng2.nextBytes(a2, offset, n);
             Assertions.assertArrayEquals(a1, a2);
         }
+
+        // Streams
+        Assertions.assertArrayEquals(rng1.ints().limit(4).toArray(), rng2.ints().limit(4).toArray());
+        Assertions.assertArrayEquals(rng1.ints(5).toArray(), rng2.ints(5).toArray());
+        Assertions.assertArrayEquals(rng1.ints(-3, 12).limit(4).toArray(), rng2.ints(-3, 12).limit(4).toArray());
+        Assertions.assertArrayEquals(rng1.ints(5, -13, 2).toArray(), rng2.ints(5, -13, 2).toArray());
+
+        Assertions.assertArrayEquals(rng1.longs().limit(4).toArray(), rng2.longs().limit(4).toArray());
+        Assertions.assertArrayEquals(rng1.longs(5).toArray(), rng2.longs(5).toArray());
+        Assertions.assertArrayEquals(rng1.longs(-3, 12).limit(4).toArray(), rng2.longs(-3, 12).limit(4).toArray());
+        Assertions.assertArrayEquals(rng1.longs(5, -13, 2).toArray(), rng2.longs(5, -13, 2).toArray());
+
+        Assertions.assertArrayEquals(rng1.doubles().limit(4).toArray(), rng2.doubles().limit(4).toArray());
+        Assertions.assertArrayEquals(rng1.doubles(5).toArray(), rng2.doubles(5).toArray());
+        Assertions.assertArrayEquals(rng1.doubles(-3, 12).limit(4).toArray(), rng2.doubles(-3, 12).limit(4).toArray());
+        Assertions.assertArrayEquals(rng1.doubles(5, -13, 2).toArray(), rng2.doubles(5, -13, 2).toArray());
     }
 }
diff --git a/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/RandomSourceTest.java b/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/RandomSourceTest.java
index 5bb0c8ef..49bd9635 100644
--- a/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/RandomSourceTest.java
+++ b/commons-rng-simple/src/test/java/org/apache/commons/rng/simple/RandomSourceTest.java
@@ -16,7 +16,16 @@
  */
 package org.apache.commons.rng.simple;
 
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
 import java.time.Duration;
+import java.util.SplittableRandom;
+import java.util.stream.DoubleStream;
+import java.util.stream.IntStream;
+import java.util.stream.LongStream;
+import org.apache.commons.rng.RandomProviderState;
+import org.apache.commons.rng.RestorableUniformRandomProvider;
+import org.apache.commons.rng.UniformRandomProvider;
 import org.apache.commons.rng.core.source64.LongProvider;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
@@ -99,4 +108,239 @@ class RandomSourceTest {
             RandomSource.MSWS.createSeed(broken);
         });
     }
+
+    /**
+     * Test the unrestorable method correctly delegates all methods.
+     * This includes the methods with default implementations in UniformRandomProvider, i.e.
+     * the default methods should not be used.
+     */
+    @Test
+    void testUnrestorable() {
+        // The test class should override all default methods
+        assertNoDefaultMethods(RestorableRNG.class);
+
+        final UniformRandomProvider rng1 = new RestorableRNG();
+        final RestorableRNG r = new RestorableRNG();
+        final UniformRandomProvider rng2 = RandomSource.unrestorable(r);
+        Assertions.assertNotSame(rng2, r);
+
+        // The unrestorable instance should override all default methods
+        assertNoDefaultMethods(rng2.getClass());
+
+        // Despite the newly created RNG not being a RestorableUniformRandomProvider,
+        // the method still wraps it in a new object. This is the behaviour since version 1.0.
+        // It allows the method to wrap objects that implement UniformRandomProvider (such
+        // as sub-interfaces or concrete classes) to prevent access to any additional methods.
+        Assertions.assertNotSame(rng2, RandomSource.unrestorable(rng2));
+
+        // Ensure that they generate the same values.
+        RandomAssert.assertProduceSameSequence(rng1, rng2);
+
+        // Cast must work.
+        @SuppressWarnings("unused")
+        final RestorableUniformRandomProvider restorable = (RestorableUniformRandomProvider) rng1;
+        // Cast must fail.
+        Assertions.assertThrows(ClassCastException.class, () -> {
+            @SuppressWarnings("unused")
+            RestorableUniformRandomProvider dummy = (RestorableUniformRandomProvider) rng2;
+        });
+    }
+
+    /**
+     * Assert the class has overridden all default public interface methods.
+     *
+     * @param cls the class
+     */
+    private static void assertNoDefaultMethods(Class<?> cls) {
+        for (final Method method : cls.getMethods()) {
+            if ((method.getModifiers() & Modifier.PUBLIC) != 0) {
+                Assertions.assertTrue(!method.isDefault(),
+                    () -> cls.getName() + " should override method: " + method.toGenericString());
+            }
+        }
+    }
+
+    /**
+     * Class to provide a complete implementation of the {@link UniformRandomProvider} interface.
+     * This must return a different result than the default implementations in the interface.
+     */
+    private static class RestorableRNG implements RestorableUniformRandomProvider {
+        /** The source of randomness. */
+        private final SplittableRandom rng = new SplittableRandom(123);
+
+        @Override
+        public void nextBytes(byte[] bytes) {
+            nextBytes(bytes, 0, bytes.length);
+        }
+
+        @Override
+        public void nextBytes(byte[] bytes, int start, int len) {
+            RestorableUniformRandomProvider.super.nextBytes(bytes, start, len);
+            // Rotate
+            for (int i = start + len; i-- > start;) {
+                bytes[i] += 1;
+            }
+        }
+
+        @Override
+        public int nextInt() {
+            return RestorableUniformRandomProvider.super.nextInt() + 1;
+        }
+
+        @Override
+        public int nextInt(int n) {
+            final int v = RestorableUniformRandomProvider.super.nextInt(n) + 1;
+            return v == n ? 0 : v;
+        }
+
+        @Override
+        public int nextInt(int origin, int bound) {
+            final int v = RestorableUniformRandomProvider.super.nextInt(origin, bound) + 1;
+            return v == bound ? origin : v;
+        }
+
+        @Override
+        public long nextLong() {
+            // Source of randomness for all derived methods
+            return rng.nextLong();
+        }
+
+        @Override
+        public long nextLong(long n) {
+            final long v = RestorableUniformRandomProvider.super.nextLong(n) + 1;
+            return v == n ? 0 : v;
+        }
+
+        @Override
+        public long nextLong(long origin, long bound) {
+            final long v = RestorableUniformRandomProvider.super.nextLong(origin, bound) + 1;
+            return v == bound ? origin : v;
+        }
+
+        @Override
+        public boolean nextBoolean() {
+            return !RestorableUniformRandomProvider.super.nextBoolean();
+        }
+
+        @Override
+        public float nextFloat() {
+            final float v = 1 - RestorableUniformRandomProvider.super.nextFloat();
+            return v == 1 ? 0 : v;
+        }
+
+        @Override
+        public float nextFloat(float bound) {
+            final float v = Math.nextUp(RestorableUniformRandomProvider.super.nextFloat(bound));
+            return v == bound ? 0 : v;
+        }
+
+        @Override
+        public float nextFloat(float origin, float bound) {
+            final float v = Math.nextUp(RestorableUniformRandomProvider.super.nextFloat(origin, bound));
+            return v == bound ? 0 : v;
+        }
+
+        @Override
+        public double nextDouble() {
+            final double v = 1 - RestorableUniformRandomProvider.super.nextDouble();
+            return v == 1 ? 0 : v;
+        }
+
+        @Override
+        public double nextDouble(double bound) {
+            final double v = Math.nextUp(RestorableUniformRandomProvider.super.nextDouble(bound));
+            return v == bound ? 0 : v;
+        }
+
+        @Override
+        public double nextDouble(double origin, double bound) {
+            final double v = Math.nextUp(RestorableUniformRandomProvider.super.nextDouble(origin, bound));
+            return v == bound ? 0 : v;
+        }
+
+        // Stream methods must return different values than the default so we reimplement them
+
+        @Override
+        public IntStream ints() {
+            return IntStream.generate(() -> nextInt() + 1).sequential();
+        }
+
+        @Override
+        public IntStream ints(int origin, int bound) {
+            return IntStream.generate(() -> {
+                final int v = nextInt(origin, bound) + 1;
+                return v == bound ? origin : v;
+            }).sequential();
+        }
+
+        @Override
+        public IntStream ints(long streamSize) {
+            return ints().limit(streamSize);
+        }
+
+        @Override
+        public IntStream ints(long streamSize, int origin, int bound) {
+            return ints(origin, bound).limit(streamSize);
+        }
+
+        @Override
+        public LongStream longs() {
+            return LongStream.generate(() -> nextLong() + 1).sequential();
+        }
+
+        @Override
+        public LongStream longs(long origin, long bound) {
+            return LongStream.generate(() -> {
+                final long v = nextLong(origin, bound) + 1;
+                return v == bound ? origin : v;
+            }).sequential();
+        }
+
+        @Override
+        public LongStream longs(long streamSize) {
+            return longs().limit(streamSize);
+        }
+
+        @Override
+        public LongStream longs(long streamSize, long origin, long bound) {
+            return longs(origin, bound).limit(streamSize);
+        }
+
+        @Override
+        public DoubleStream doubles() {
+            return DoubleStream.generate(() -> {
+                final double v = Math.nextUp(nextDouble());
+                return v == 1 ? 0 : v;
+            }).sequential();
+        }
+
+        @Override
+        public DoubleStream doubles(double origin, double bound) {
+            return DoubleStream.generate(() -> {
+                final double v = Math.nextUp(nextDouble(origin, bound));
+                return v == bound ? origin : v;
+            }).sequential();
+        }
+
+        @Override
+        public DoubleStream doubles(long streamSize) {
+            return doubles().limit(streamSize);
+        }
+
+        @Override
+        public DoubleStream doubles(long streamSize, double origin, double bound) {
+            return doubles(origin, bound).limit(streamSize);
+        }
+
+        @Override
+        public RandomProviderState saveState() {
+            // Do nothing
+            return null;
+        }
+
+        @Override
+        public void restoreState(RandomProviderState state) {
+            // Do nothing
+        }
+    }
 }
diff --git a/src/main/resources/pmd/pmd-ruleset.xml b/src/main/resources/pmd/pmd-ruleset.xml
index ae95f65f..288690f7 100644
--- a/src/main/resources/pmd/pmd-ruleset.xml
+++ b/src/main/resources/pmd/pmd-ruleset.xml
@@ -171,16 +171,18 @@
   </rule>
   <rule ref="category/java/design.xml/ExcessiveMethodLength">
     <properties>
-      <!-- The length is due to comments -->
+      <!-- The length is due to comments, or for RandomSource the wrapping of a delegate UniformRandomProvider -->
       <property name="violationSuppressXPath" value="//ClassOrInterfaceDeclaration[@SimpleName='AliasMethodDiscreteSampler'
-        or @SimpleName='ProbabilityDensityApproximationCommand']"/>
+        or @SimpleName='ProbabilityDensityApproximationCommand']
+        or ./ancestor::EnumDeclaration[@SimpleName='RandomSource']"/>
     </properties>
   </rule>
   <rule ref="category/java/design.xml/ExcessiveClassLength">
     <properties>
       <!-- The length is due to multiple implementations as inner classes -->
       <property name="violationSuppressXPath" value="//ClassOrInterfaceDeclaration[@SimpleName='MarsagliaTsangWangDiscreteSampler'
-        or @SimpleName='CompositeSamplers' or @SimpleName='StableSampler' or @SimpleName='ZigguratSampler']"/>
+        or @SimpleName='CompositeSamplers' or @SimpleName='StableSampler' or @SimpleName='ZigguratSampler']
+        or ./ancestor::EnumDeclaration[@SimpleName='RandomSource']"/>
     </properties>
   </rule>
   <rule ref="category/java/design.xml/ExcessiveParameterList">