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 585e205  RNG-144: AhrensDieterExponentialSampler to avoid an infinite 
loop
585e205 is described below

commit 585e205cb3266c6fc3074699946a080c61aa5c3e
Author: aherbert <aherb...@apache.org>
AuthorDate: Fri Jun 11 12:54:50 2021 +0100

    RNG-144: AhrensDieterExponentialSampler to avoid an infinite loop
---
 .../AhrensDieterExponentialSampler.java            | 26 +++++++++++++++++++--
 .../AhrensDieterExponentialSamplerTest.java        | 27 ++++++++++++++++++++++
 src/changes/changes.xml                            |  4 ++++
 3 files changed, 55 insertions(+), 2 deletions(-)

diff --git 
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/AhrensDieterExponentialSampler.java
 
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/AhrensDieterExponentialSampler.java
index 6ae1850..702ecf7 100644
--- 
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/AhrensDieterExponentialSampler.java
+++ 
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/AhrensDieterExponentialSampler.java
@@ -21,7 +21,12 @@ import org.apache.commons.rng.UniformRandomProvider;
 /**
  * Sampling from an <a 
href="http://mathworld.wolfram.com/ExponentialDistribution.html";>exponential 
distribution</a>.
  *
- * <p>Sampling uses {@link UniformRandomProvider#nextDouble()}.</p>
+ * <p>Sampling uses:</p>
+ *
+ * <ul>
+ *   <li>{@link UniformRandomProvider#nextLong()}
+ *   <li>{@link UniformRandomProvider#nextDouble()}
+ * </ul>
  *
  * @since 1.0
  */
@@ -41,6 +46,11 @@ public class AhrensDieterExponentialSampler
      * By trying, n = 16 in Java is enough to reach 1.
      */
     private static final double[] EXPONENTIAL_SA_QI = new double[16];
+    /**
+     * The multiplier to convert the least significant 53-bits of a {@code 
long} to a {@code double}.
+     * Taken from org.apache.commons.rng.core.util.NumberFactory.
+     */
+    private static final double DOUBLE_MULTIPLIER = 0x1.0p-53d;
     /** The mean of this distribution. */
     private final double mean;
     /** Underlying source of randomness. */
@@ -94,7 +104,8 @@ public class AhrensDieterExponentialSampler
     public double sample() {
         // Step 1:
         double a = 0;
-        double u = rng.nextDouble();
+        // Avoid u=0 which creates an infinite loop
+        double u = nextNonZeroDouble();
 
         // Step 2 and 3:
         while (u < 0.5) {
@@ -130,6 +141,17 @@ public class AhrensDieterExponentialSampler
         return mean * (a + umin * EXPONENTIAL_SA_QI[0]);
     }
 
+    /**
+     * Create a double in the interval {@code (0, 1]}.
+     *
+     * @return a {@code double} value in the interval {@code (0, 1]}.
+     */
+    private double nextNonZeroDouble() {
+        // This matches the method in 
o.a.c.rng.core.util.NumberFactory.makeDouble(long)
+        // but shifts the range from [0, 1) to (0, 1].
+        return ((rng.nextLong() >>> 11) + 1L) * DOUBLE_MULTIPLIER;
+    }
+
     /** {@inheritDoc} */
     @Override
     public String toString() {
diff --git 
a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/AhrensDieterExponentialSamplerTest.java
 
b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/AhrensDieterExponentialSamplerTest.java
index de771b6..d7b81a8 100644
--- 
a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/AhrensDieterExponentialSamplerTest.java
+++ 
b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/AhrensDieterExponentialSamplerTest.java
@@ -16,16 +16,25 @@
  */
 package org.apache.commons.rng.sampling.distribution;
 
+import java.util.concurrent.TimeUnit;
 import org.apache.commons.rng.RestorableUniformRandomProvider;
 import org.apache.commons.rng.UniformRandomProvider;
+import org.apache.commons.rng.core.source64.SplitMix64;
 import org.apache.commons.rng.sampling.RandomAssert;
 import org.apache.commons.rng.simple.RandomSource;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.Timeout;
 
 /**
  * Test for the {@link AhrensDieterExponentialSampler}. The tests hit edge 
cases for the sampler.
  */
 public class AhrensDieterExponentialSamplerTest {
+
+    /** The global timeout for tests. Used to kill a test stuck in an infinite 
loop. */
+    @Rule
+    public Timeout globalTimeout = new Timeout(50, TimeUnit.MILLISECONDS);
+
     /**
      * Test the constructor with a bad mean.
      */
@@ -50,4 +59,22 @@ public class AhrensDieterExponentialSamplerTest {
         final SharedStateContinuousSampler sampler2 = 
sampler1.withUniformRandomProvider(rng2);
         RandomAssert.assertProduceSameSequence(sampler1, sampler2);
     }
+
+    /**
+     * Test the sampler is robust to a generator that outputs zeros.
+     * See RNG-144.
+     */
+    @Test
+    public void testSamplerWithZeroFromRandomGenerator() {
+        // A broken generator that returns zero.
+        final UniformRandomProvider rng = new SplitMix64(0) {
+            @Override
+            public long nextLong() {
+                return 0L;
+            }
+        };
+        final SharedStateContinuousSampler sampler = 
AhrensDieterExponentialSampler.of(rng, 1);
+        // This should not infinite loop
+        sampler.sample();
+    }
 }
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 64b5231..b603c09 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -77,6 +77,10 @@ re-run tests that fail, and pass the build if they succeed
 within the allotted number of reruns (the test will be marked
 as 'flaky' in the report).
 ">
+      <action dev="aherbert" type="fix" issue="144">
+        "AhrensDieterExponentialSampler": Avoid possible infinite loop during 
sampling if the
+        underlying UniformRandomProvider creates a zero for the uniform 
deviate.
+      </action>
       <action dev="aherbert" type="add" issue="143">
         "RandomSource": Add an instance create method. Deprecate the static 
create method.
       </action>

Reply via email to