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 fffcf4c1664aa7a4db66686db29312346d5a7ace
Author: Alex Herbert <aherb...@apache.org>
AuthorDate: Thu Jul 15 21:24:26 2021 +0100

    RNG-140: Add LongSampler interface and UniformLongSampler
    
    This is replacement for RandomUtils.nextLong(long lo, long hi) from
    Commons Math.
---
 .../rng/sampling/distribution/LongSampler.java     |  31 ++
 .../distribution/SharedStateLongSampler.java       |  30 ++
 .../sampling/distribution/UniformLongSampler.java  | 334 +++++++++++++++++++++
 .../apache/commons/rng/sampling/RandomAssert.java  |  15 +
 .../distribution/UniformLongSamplerTest.java       | 324 ++++++++++++++++++++
 5 files changed, 734 insertions(+)

diff --git 
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/LongSampler.java
 
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/LongSampler.java
new file mode 100644
index 0000000..a3e2fac
--- /dev/null
+++ 
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/LongSampler.java
@@ -0,0 +1,31 @@
+/*
+ * 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.sampling.distribution;
+
+/**
+ * Sampler that generates values of type {@code long}.
+ *
+ * @since 1.4
+ */
+public interface LongSampler {
+    /**
+     * Creates a sample.
+     *
+     * @return a sample.
+     */
+    long sample();
+}
diff --git 
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/SharedStateLongSampler.java
 
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/SharedStateLongSampler.java
new file mode 100644
index 0000000..9e052c2
--- /dev/null
+++ 
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/SharedStateLongSampler.java
@@ -0,0 +1,30 @@
+/*
+ * 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.sampling.distribution;
+
+import org.apache.commons.rng.sampling.SharedStateSampler;
+
+/**
+ * Sampler that generates values of type {@code long} and can create new 
instances to sample
+ * from the same state with a given source of randomness.
+ *
+ * @since 1.4
+ */
+public interface SharedStateLongSampler
+    extends LongSampler, SharedStateSampler<SharedStateLongSampler> {
+    // Composite interface
+}
diff --git 
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/UniformLongSampler.java
 
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/UniformLongSampler.java
new file mode 100644
index 0000000..2eb05a1
--- /dev/null
+++ 
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/UniformLongSampler.java
@@ -0,0 +1,334 @@
+/*
+ * 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.sampling.distribution;
+
+import org.apache.commons.rng.UniformRandomProvider;
+
+/**
+ * Discrete uniform distribution sampler generating values of type {@code 
long}.
+ *
+ * <p>Sampling uses {@link UniformRandomProvider#nextLong}.</p>
+ *
+ * <p>When the range is a power of two the number of calls is 1 per sample.
+ * Otherwise a rejection algorithm is used to ensure uniformity. In the worst
+ * case scenario where the range spans half the range of a {@code long}
+ * (2<sup>63</sup> + 1) the expected number of calls is 2 per sample.</p>
+ *
+ * @since 1.4
+ */
+public abstract class UniformLongSampler implements SharedStateLongSampler {
+    /** Underlying source of randomness. */
+    protected final UniformRandomProvider rng;
+
+    /**
+     * Discrete uniform distribution sampler when the sample value is fixed.
+     */
+    private static class FixedUniformLongSampler extends UniformLongSampler {
+        /** The value. */
+        private final long value;
+
+        /**
+         * @param value The value.
+         */
+        FixedUniformLongSampler(long value) {
+            // No requirement for the RNG
+            super(null);
+            this.value = value;
+        }
+
+        @Override
+        public long sample() {
+            return value;
+        }
+
+        @Override
+        public String toString() {
+            // No RNG to include in the string
+            return "Uniform deviate [X=" + value + "]";
+        }
+
+        @Override
+        public UniformLongSampler 
withUniformRandomProvider(UniformRandomProvider rng) {
+            // No requirement for the RNG
+            return this;
+        }
+    }
+
+    /**
+     * Discrete uniform distribution sampler when the range is a power of 2 
and greater than 1.
+     * This sampler assumes the lower bound of the range is 0.
+     *
+     * <p>Note: This cannot be used when the range is 1 (2^0) as the shift 
would be 64-bits
+     * which is ignored by the shift operator.</p>
+     */
+    private static class PowerOf2RangeUniformLongSampler extends 
UniformLongSampler {
+        /** Bit shift to apply to the long sample. */
+        private final int shift;
+
+        /**
+         * @param rng Generator of uniformly distributed random numbers.
+         * @param range Maximum range of the sample (exclusive).
+         * Must be a power of 2 greater than 2^0.
+         */
+        PowerOf2RangeUniformLongSampler(UniformRandomProvider rng,
+                                        long range) {
+            super(rng);
+            this.shift = Long.numberOfLeadingZeros(range) + 1;
+        }
+
+        /**
+         * @param rng Generator of uniformly distributed random numbers.
+         * @param source Source to copy.
+         */
+        PowerOf2RangeUniformLongSampler(UniformRandomProvider rng,
+                                        PowerOf2RangeUniformLongSampler 
source) {
+            super(rng);
+            this.shift = source.shift;
+        }
+
+        @Override
+        public long sample() {
+            // Use a bit shift to favour the most significant bits.
+            return rng.nextLong() >>> shift;
+        }
+
+        @Override
+        public UniformLongSampler 
withUniformRandomProvider(UniformRandomProvider rng) {
+            return new PowerOf2RangeUniformLongSampler(rng, this);
+        }
+    }
+
+    /**
+     * Discrete uniform distribution sampler when the range is small
+     * enough to fit in a positive long.
+     * This sampler assumes the lower bound of the range is 0.
+     */
+    private static class SmallRangeUniformLongSampler extends 
UniformLongSampler {
+        /** Maximum range of the sample (exclusive). */
+        private final long n;
+
+        /**
+         * @param rng Generator of uniformly distributed random numbers.
+         * @param range Maximum range of the sample (exclusive).
+         */
+        SmallRangeUniformLongSampler(UniformRandomProvider rng,
+                                     long range) {
+            super(rng);
+            this.n = range;
+        }
+
+        @Override
+        public long sample() {
+            // Rejection algorithm copied from o.a.c.rng.core.BaseProvider
+            // to avoid the (n <= 0) conditional.
+            // See the JDK javadoc for java.util.Random.nextInt(int) for
+            // a description of the algorithm.
+            long bits;
+            long val;
+            do {
+                bits = rng.nextLong() >>> 1;
+                val  = bits % n;
+            } while (bits - val + (n - 1) < 0);
+            return val;
+        }
+
+        @Override
+        public UniformLongSampler 
withUniformRandomProvider(UniformRandomProvider rng) {
+            return new SmallRangeUniformLongSampler(rng, n);
+        }
+    }
+
+    /**
+     * Discrete uniform distribution sampler when the range between lower and 
upper is too large
+     * to fit in a positive long.
+     */
+    private static class LargeRangeUniformLongSampler extends 
UniformLongSampler {
+        /** Lower bound. */
+        private final long lower;
+        /** Upper bound. */
+        private final long upper;
+
+        /**
+         * @param rng Generator of uniformly distributed random numbers.
+         * @param lower Lower bound (inclusive) of the distribution.
+         * @param upper Upper bound (inclusive) of the distribution.
+         */
+        LargeRangeUniformLongSampler(UniformRandomProvider rng,
+                                     long lower,
+                                     long upper) {
+            super(rng);
+            this.lower = lower;
+            this.upper = upper;
+        }
+
+        @Override
+        public long sample() {
+            // Use a simple rejection method.
+            // This is used when (upper-lower) >= Long.MAX_VALUE.
+            // This will loop on average 2 times in the worst case scenario
+            // when (upper-lower) == Long.MAX_VALUE.
+            while (true) {
+                final long r = rng.nextLong();
+                if (r >= lower &&
+                    r <= upper) {
+                    return r;
+                }
+            }
+        }
+
+        @Override
+        public UniformLongSampler 
withUniformRandomProvider(UniformRandomProvider rng) {
+            return new LargeRangeUniformLongSampler(rng, lower, upper);
+        }
+    }
+
+    /**
+     * Adds an offset to an underlying discrete sampler.
+     */
+    private static class OffsetUniformLongSampler extends UniformLongSampler {
+        /** The offset. */
+        private final long offset;
+        /** The long sampler. */
+        private final UniformLongSampler sampler;
+
+        /**
+         * @param offset The offset for the sample.
+         * @param sampler The discrete sampler.
+         */
+        OffsetUniformLongSampler(long offset,
+                                 UniformLongSampler sampler) {
+            // No requirement for the RNG
+            super(null);
+            this.offset = offset;
+            this.sampler = sampler;
+        }
+
+        @Override
+        public long sample() {
+            return offset + sampler.sample();
+        }
+
+        @Override
+        public String toString() {
+            return sampler.toString();
+        }
+
+        @Override
+        public UniformLongSampler 
withUniformRandomProvider(UniformRandomProvider rng) {
+            return new OffsetUniformLongSampler(offset, 
sampler.withUniformRandomProvider(rng));
+        }
+    }
+
+    /**
+     * @param rng Generator of uniformly distributed random numbers.
+     */
+    private UniformLongSampler(UniformRandomProvider rng) {
+        this.rng = rng;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return "Uniform deviate [" + rng.toString() + "]";
+    }
+
+    /** {@inheritDoc} */
+    // Redeclare the signature to return a UniformLongSampler not a 
SharedStateLongSampler
+    @Override
+    public abstract UniformLongSampler 
withUniformRandomProvider(UniformRandomProvider rng);
+
+    /**
+     * Creates a new discrete uniform distribution sampler.
+     *
+     * @param rng Generator of uniformly distributed random numbers.
+     * @param lower Lower bound (inclusive) of the distribution.
+     * @param upper Upper bound (inclusive) of the distribution.
+     * @return the sampler
+     * @throws IllegalArgumentException if {@code lower > upper}.
+     */
+    public static UniformLongSampler of(UniformRandomProvider rng,
+                                        long lower,
+                                        long upper) {
+        if (lower > upper) {
+            throw new IllegalArgumentException(lower  + " > " + upper);
+        }
+
+        // Choose the algorithm depending on the range
+
+        // Edge case for no range.
+        // This must be done first as the methods to handle lower == 0
+        // do not handle upper == 0.
+        if (upper == lower) {
+            return new FixedUniformLongSampler(lower);
+        }
+
+        // Algorithms to ignore the lower bound if it is zero.
+        if (lower == 0) {
+            return createZeroBoundedSampler(rng, upper);
+        }
+
+        final long range = (upper - lower) + 1;
+        // Check power of 2 first to handle range == 2^63.
+        if (isPowerOf2(range)) {
+            return new OffsetUniformLongSampler(lower,
+                                                new 
PowerOf2RangeUniformLongSampler(rng, range));
+        }
+        if (range <= 0) {
+            // The range is too wide to fit in a positive long (larger
+            // than 2^63); use a simple rejection method.
+            // Note: if range == 0 then the input is [Long.MIN_VALUE, 
Long.MAX_VALUE].
+            // No specialisation exists for this and it is handled as a large 
range.
+            return new LargeRangeUniformLongSampler(rng, lower, upper);
+        }
+        // Use a sample from the range added to the lower bound.
+        return new OffsetUniformLongSampler(lower,
+                                            new 
SmallRangeUniformLongSampler(rng, range));
+    }
+
+    /**
+     * Create a new sampler for the range {@code 0} inclusive to {@code upper} 
inclusive.
+     *
+     * <p>This can handle any positive {@code upper}.
+     *
+     * @param rng Generator of uniformly distributed random numbers.
+     * @param upper Upper bound (inclusive) of the distribution. Must be 
positive.
+     * @return the sampler
+     */
+    private static UniformLongSampler 
createZeroBoundedSampler(UniformRandomProvider rng,
+                                                               long upper) {
+        // Note: Handle any range up to 2^63 (which is negative as a signed
+        // 64-bit long but handled as a power of 2)
+        final long range = upper + 1;
+        return isPowerOf2(range) ?
+            new PowerOf2RangeUniformLongSampler(rng, range) :
+            new SmallRangeUniformLongSampler(rng, range);
+    }
+
+    /**
+     * Checks if the value is a power of 2.
+     *
+     * <p>This returns {@code true} for the value {@code Long.MIN_VALUE} which 
can be
+     * handled as an unsigned long of 2^63.</p>
+     *
+     * @param value Value.
+     * @return {@code true} if a power of 2
+     */
+    private static boolean isPowerOf2(final long value) {
+        return value != 0 && (value & (value - 1)) == 0;
+    }
+}
diff --git 
a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/RandomAssert.java
 
b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/RandomAssert.java
index 5860501..d977764 100644
--- 
a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/RandomAssert.java
+++ 
b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/RandomAssert.java
@@ -21,6 +21,7 @@ import org.junit.Assert;
 
 import org.apache.commons.rng.sampling.distribution.ContinuousSampler;
 import org.apache.commons.rng.sampling.distribution.DiscreteSampler;
+import org.apache.commons.rng.sampling.distribution.LongSampler;
 
 /**
  * Utility class for testing random samplers.
@@ -77,6 +78,20 @@ public final class RandomAssert {
     }
 
     /**
+     * Exercise the {@link LongSampler} interface, and
+     * ensure that the two samplers produce the same sequence.
+     *
+     * @param sampler1 First sampler.
+     * @param sampler2 Second sampler.
+     */
+    public static void assertProduceSameSequence(LongSampler sampler1,
+                                                 LongSampler sampler2) {
+        for (int i = 0; i < SAMPLES; i++) {
+            Assert.assertEquals(sampler1.sample(), sampler2.sample());
+        }
+    }
+
+    /**
      * Exercise the {@link Sampler} interface, and
      * ensure that the two samplers produce the same sequence.
      *
diff --git 
a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/UniformLongSamplerTest.java
 
b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/UniformLongSamplerTest.java
new file mode 100644
index 0000000..33ced6b
--- /dev/null
+++ 
b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/UniformLongSamplerTest.java
@@ -0,0 +1,324 @@
+/*
+ * 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.sampling.distribution;
+
+import org.apache.commons.rng.UniformRandomProvider;
+import org.apache.commons.rng.core.source64.LongProvider;
+import org.apache.commons.rng.sampling.RandomAssert;
+import org.apache.commons.rng.simple.RandomSource;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.Locale;
+
+/**
+ * Test for the {@link UniformLongSampler}. The tests hit edge cases for the 
sampler
+ * and demonstrates uniformity of output when the underlying RNG output is 
uniform.
+ *
+ * <p>Note: No statistical tests for uniformity are performed on the output. 
The tests
+ * are constructed on the premise that the underlying sampling methods 
correctly
+ * use the random bits from {@link UniformRandomProvider}. Correctness
+ * for a small range is tested against {@link 
UniformRandomProvider#nextLong(long)}
+ * and correctness for a large range is tested that the {@link 
UniformRandomProvider#nextLong()}
+ * is within the range limits. Power of two ranges are tested against a bit 
shift
+ * of a random long.
+ */
+public class UniformLongSamplerTest {
+    /**
+     * Test the constructor with a bad range.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testConstructorThrowsWithLowerAboveUpper() {
+        final long upper = 55;
+        final long lower = upper + 1;
+        final UniformRandomProvider rng = RandomSource.SPLIT_MIX_64.create(0L);
+        UniformLongSampler.of(rng, lower, upper);
+    }
+
+    @Test
+    public void testSamplesWithRangeOf1() {
+        final long upper = 99;
+        final long lower = upper;
+        final UniformRandomProvider rng = RandomSource.SPLIT_MIX_64.create();
+        final UniformLongSampler sampler = UniformLongSampler.of(rng, lower, 
upper);
+        for (int i = 0; i < 10; i++) {
+            Assert.assertEquals(lower, sampler.sample());
+        }
+    }
+
+    /**
+     * Test samples with a full long range.
+     * The output should be the same as the long values produced from a RNG.
+     */
+    @Test
+    public void testSamplesWithFullRange() {
+        final long upper = Long.MAX_VALUE;
+        final long lower = Long.MIN_VALUE;
+        final UniformRandomProvider rng1 = 
RandomSource.SPLIT_MIX_64.create(0L);
+        final UniformRandomProvider rng2 = 
RandomSource.SPLIT_MIX_64.create(0L);
+        final UniformLongSampler sampler = UniformLongSampler.of(rng2, lower, 
upper);
+        for (int i = 0; i < 10; i++) {
+            Assert.assertEquals(rng1.nextLong(), sampler.sample());
+        }
+    }
+
+    /**
+     * Test samples with a non-power of 2 range.
+     * The output should be the same as the long values produced from a RNG
+     * based on o.a.c.rng.core.BaseProvider as the rejection algorithm is
+     * the same.
+     */
+    @Test
+    public void testSamplesWithSmallNonPowerOf2Range() {
+        final long upper = 234293789329234L;
+        for (final long lower : new long[] {-13, 0, 13}) {
+            final long n = upper - lower + 1;
+            final UniformRandomProvider rng1 = 
RandomSource.SPLIT_MIX_64.create(0L);
+            final UniformRandomProvider rng2 = 
RandomSource.SPLIT_MIX_64.create(0L);
+            final UniformLongSampler sampler = UniformLongSampler.of(rng2, 
lower, upper);
+            for (int i = 0; i < 10; i++) {
+                Assert.assertEquals(lower + rng1.nextLong(n), 
sampler.sample());
+            }
+        }
+    }
+
+    /**
+     * Test samples with a power of 2 range.
+     * This tests the minimum and maximum output should be the range limits.
+     */
+    @Test
+    public void testSamplesWithPowerOf2Range() {
+        final UniformRandomProvider rngZeroBits = new LongProvider() {
+            @Override
+            public long next() {
+                // No bits
+                return 0L;
+            }
+        };
+        final UniformRandomProvider rngAllBits = new LongProvider() {
+            @Override
+            public long next() {
+                // All bits
+                return -1L;
+            }
+        };
+
+        final long lower = -3;
+        UniformLongSampler sampler;
+        // The upper range for a positive long is 2^63-1. So the max positive 
power of
+        // 2 is 2^62. However the sampler should handle a bit shift of 63 to 
create a range
+        // of Long.MIN_VALUE as this is a power of 2 as an unsigned long 
(2^63).
+        for (int i = 0; i < 64; i++) {
+            final long range = 1L << i;
+            final long upper = lower + range - 1;
+            sampler = UniformLongSampler.of(rngZeroBits, lower, upper);
+            Assert.assertEquals("Zero bits sample", lower, sampler.sample());
+            sampler = UniformLongSampler.of(rngAllBits, lower, upper);
+            Assert.assertEquals("All bits sample", upper, sampler.sample());
+        }
+    }
+
+    /**
+     * Test samples with a power of 2 range.
+     * This tests the output is created using a bit shift.
+     */
+    @Test
+    public void testSamplesWithPowerOf2RangeIsBitShift() {
+        final long lower = 0;
+        UniformLongSampler sampler;
+        // Power of 2 sampler used for a bit shift of 1 to 63.
+        for (int i = 1; i <= 63; i++) {
+            // Upper is inclusive so subtract 1
+            final long upper = (1L << i) - 1;
+            final int shift = 64 - i;
+            final UniformRandomProvider rng1 = 
RandomSource.SPLIT_MIX_64.create(0L);
+            final UniformRandomProvider rng2 = 
RandomSource.SPLIT_MIX_64.create(0L);
+            sampler = UniformLongSampler.of(rng2, lower, upper);
+            for (int j = 0; j < 10; j++) {
+                Assert.assertEquals(rng1.nextLong() >>> shift, 
sampler.sample());
+            }
+        }
+    }
+
+    /**
+     * Test samples with a large non-power of 2 range.
+     * This tests the large range algorithm uses a rejection method.
+     */
+    @Test
+    public void testSamplesWithLargeNonPowerOf2RangeIsRejectionMethod() {
+        // Create a range bigger than 2^63
+        final long upper = Long.MAX_VALUE / 2 + 1;
+        final long lower = Long.MIN_VALUE / 2 - 1;
+        final UniformRandomProvider rng1 = 
RandomSource.SPLIT_MIX_64.create(0L);
+        final UniformRandomProvider rng2 = 
RandomSource.SPLIT_MIX_64.create(0L);
+        final UniformLongSampler sampler = UniformLongSampler.of(rng2, lower, 
upper);
+        for (int i = 0; i < 10; i++) {
+            // Get the expected value by the rejection method
+            long expected;
+            do {
+                expected = rng1.nextLong();
+            } while (expected < lower || expected > upper);
+            Assert.assertEquals(expected, sampler.sample());
+        }
+    }
+
+    @Test
+    public void testOffsetSamplesWithNonPowerOf2Range() {
+        assertOffsetSamples(257);
+    }
+
+    @Test
+    public void testOffsetSamplesWithPowerOf2Range() {
+        assertOffsetSamples(256);
+    }
+
+    @Test
+    public void testOffsetSamplesWithRangeOf1() {
+        assertOffsetSamples(1);
+    }
+
+    private static void assertOffsetSamples(long range) {
+        final Long seed = RandomSource.createLong();
+        final UniformRandomProvider rng1 = 
RandomSource.SPLIT_MIX_64.create(seed);
+        final UniformRandomProvider rng2 = 
RandomSource.SPLIT_MIX_64.create(seed);
+        final UniformRandomProvider rng3 = 
RandomSource.SPLIT_MIX_64.create(seed);
+
+        // Since the upper limit is inclusive
+        range = range - 1;
+        final long offsetLo = -13;
+        final long offsetHi = 42;
+        final UniformLongSampler sampler = UniformLongSampler.of(rng1, 0, 
range);
+        final UniformLongSampler samplerLo = UniformLongSampler.of(rng2, 
offsetLo, offsetLo + range);
+        final UniformLongSampler samplerHi = UniformLongSampler.of(rng3, 
offsetHi, offsetHi + range);
+        for (int i = 0; i < 10; i++) {
+            final long sample1 = sampler.sample();
+            final long sample2 = samplerLo.sample();
+            final long sample3 = samplerHi.sample();
+            Assert.assertEquals("Incorrect negative offset sample", sample1 + 
offsetLo, sample2);
+            Assert.assertEquals("Incorrect positive offset sample", sample1 + 
offsetHi, sample3);
+        }
+    }
+
+    /**
+     * Test the sample uniformity when using a small range that is a power of 
2.
+     */
+    @Test
+    public void testSampleUniformityWithPowerOf2Range() {
+        // Test using a RNG that outputs a counter of integers.
+        // The n most significant bits will be represented uniformly over a
+        // sequence that is a 2^n long.
+        final UniformRandomProvider rng = new LongProvider() {
+            private long bits = 0;
+
+            @Override
+            public long next() {
+                // We reverse the bits because the most significant bits are 
used
+                return Long.reverse(bits++);
+            }
+        };
+
+        // n = upper range exclusive
+        final int n = 32; // power of 2
+        final int[] histogram = new int[n];
+
+        final long lower = 0;
+        final long upper = n - 1;
+
+        final UniformLongSampler sampler = UniformLongSampler.of(rng, lower, 
upper);
+
+        final int expected = 2;
+        for (int i = expected * n; i-- > 0;) {
+            histogram[(int) sampler.sample()]++;
+        }
+
+        // This should be even across the entire range
+        for (int value : histogram) {
+            Assert.assertEquals(expected, value);
+        }
+    }
+
+    @Test
+    public void testSharedStateSamplerWithSmallRange() {
+        assertSharedStateSampler(5, 67);
+    }
+
+    @Test
+    public void testSharedStateSamplerWithLargeRange() {
+        // Set the range so rejection below or above the threshold occurs with 
approximately
+        // p=0.25 for each bound.
+        assertSharedStateSampler(Long.MIN_VALUE / 2 - 1, Long.MAX_VALUE / 2 + 
1);
+    }
+
+    @Test
+    public void testSharedStateSamplerWithPowerOf2Range() {
+        assertSharedStateSampler(0, (1L << 45) - 1);
+    }
+
+    @Test
+    public void testSharedStateSamplerWithRangeOf1() {
+        assertSharedStateSampler(968757657572323L, 968757657572323L);
+    }
+
+    /**
+     * Test the SharedStateSampler implementation returns the same sequence as 
the source sampler
+     * when using an identical RNG.
+     *
+     * @param lower Lower.
+     * @param upper Upper.
+     */
+    private static void assertSharedStateSampler(long lower, long upper) {
+        final UniformRandomProvider rng1 = 
RandomSource.SPLIT_MIX_64.create(0L);
+        final UniformRandomProvider rng2 = 
RandomSource.SPLIT_MIX_64.create(0L);
+        final UniformLongSampler sampler1 = UniformLongSampler.of(rng1, lower, 
upper);
+        final UniformLongSampler sampler2 = 
sampler1.withUniformRandomProvider(rng2);
+        RandomAssert.assertProduceSameSequence(sampler1, sampler2);
+    }
+
+    @Test
+    public void testToStringWithSmallRange() {
+        assertToString(5, 67);
+    }
+
+    @Test
+    public void testToStringWithLargeRange() {
+        assertToString(-99999999, Long.MAX_VALUE);
+    }
+
+    @Test
+    public void testToStringWithPowerOf2Range() {
+        // Note the range is upper - lower + 1
+        assertToString(0, 31);
+    }
+
+    @Test
+    public void testToStringWithRangeOf1() {
+        assertToString(9, 9);
+    }
+
+    /**
+     * Test the toString method contains the term "uniform". This is true of 
all samplers
+     * even for a fixed sample from a range of 1.
+     *
+     * @param lower Lower.
+     * @param upper Upper.
+     */
+    private static void assertToString(long lower, long upper) {
+        final UniformRandomProvider rng = RandomSource.SPLIT_MIX_64.create(0L);
+        final UniformLongSampler sampler = UniformLongSampler.of(rng, lower, 
upper);
+        
Assert.assertTrue(sampler.toString().toLowerCase(Locale.US).contains("uniform"));
+    }
+}

Reply via email to