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

asf-gitbox-commits 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 c6a5e9f6 RNG-194: Samplers to validate parameters are finite
c6a5e9f6 is described below

commit c6a5e9f686c53c0b9ff620497f82c29e72ba1c7c
Author: Alex Herbert <[email protected]>
AuthorDate: Fri Aug 21 16:10:06 2026 +0100

    RNG-194: Samplers to validate parameters are finite
---
 .../AhrensDieterExponentialSampler.java            |   8 +-
 .../AhrensDieterMarsagliaTsangGammaSampler.java    |  19 ++-
 .../distribution/BoxMullerGaussianSampler.java     |   7 +-
 .../distribution/BoxMullerLogNormalSampler.java    |   3 +-
 .../sampling/distribution/ChengBetaSampler.java    |  10 +-
 .../distribution/ContinuousUniformSampler.java     |  11 +-
 .../rng/sampling/distribution/GaussianSampler.java |   4 +-
 .../sampling/distribution/GeometricSampler.java    |  65 +++++++---
 .../rng/sampling/distribution/InternalUtils.java   |  19 +--
 .../InverseTransformParetoSampler.java             |   8 +-
 .../rng/sampling/distribution/LevySampler.java     |   6 +-
 .../sampling/distribution/LogNormalSampler.java    |   9 +-
 .../RejectionInversionZipfSampler.java             |   6 +-
 .../rng/sampling/distribution/ZigguratSampler.java |   5 +-
 .../distribution/ContinuousUniformSamplerTest.java |   8 ++
 .../distribution/GeometricSamplerTest.java         |  21 +++
 .../NonFiniteParameterValidationTest.java          | 142 +++++++++++++++++++++
 src/changes/changes.xml                            |   5 +
 18 files changed, 286 insertions(+), 70 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 844d50e4..2e1d6af6 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
@@ -77,12 +77,13 @@ public class AhrensDieterExponentialSampler
      *
      * @param rng Generator of uniformly distributed random numbers.
      * @param mean Mean of this distribution.
-     * @throws IllegalArgumentException if {@code mean <= 0}
+     * @throws IllegalArgumentException if {@code mean <= 0}, or if {@code 
mean}
+     * is not finite
      */
     public AhrensDieterExponentialSampler(UniformRandomProvider rng,
                                           double mean) {
         // Validation before java.lang.Object constructor exits prevents 
partially initialized object
-        this(InternalUtils.requireStrictlyPositive(mean, "mean"), rng);
+        this(InternalUtils.requireStrictlyPositiveFinite(mean, "mean"), rng);
     }
 
     /**
@@ -161,7 +162,8 @@ public class AhrensDieterExponentialSampler
      * @param rng Generator of uniformly distributed random numbers.
      * @param mean Mean of the distribution.
      * @return the sampler
-     * @throws IllegalArgumentException if {@code mean <= 0}
+     * @throws IllegalArgumentException if {@code mean <= 0}, or if {@code 
mean}
+     * is not finite
      * @since 1.3
      */
     public static SharedStateContinuousSampler of(UniformRandomProvider rng,
diff --git 
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/AhrensDieterMarsagliaTsangGammaSampler.java
 
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/AhrensDieterMarsagliaTsangGammaSampler.java
index 3bb16cd3..e0788d1e 100644
--- 
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/AhrensDieterMarsagliaTsangGammaSampler.java
+++ 
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/AhrensDieterMarsagliaTsangGammaSampler.java
@@ -73,14 +73,15 @@ public class AhrensDieterMarsagliaTsangGammaSampler
          * @param rng Generator of uniformly distributed random numbers.
          * @param alpha Alpha parameter of the distribution.
          * @param theta Theta parameter of the distribution.
-         * @throws IllegalArgumentException if {@code alpha <= 0} or {@code 
theta <= 0}
+         * @throws IllegalArgumentException if {@code alpha <= 0} or {@code 
theta <= 0},
+         * or if {@code alpha} or {@code theta} are not finite
          */
         BaseGammaSampler(UniformRandomProvider rng,
                          double alpha,
                          double theta) {
             // Validation before java.lang.Object constructor exits prevents 
partially initialized object
-            this(InternalUtils.requireStrictlyPositive(alpha, "alpha"),
-                 InternalUtils.requireStrictlyPositive(theta, "theta"),
+            this(InternalUtils.requireStrictlyPositiveFinite(alpha, "alpha"),
+                 InternalUtils.requireStrictlyPositiveFinite(theta, "theta"),
                  rng);
         }
 
@@ -136,7 +137,8 @@ public class AhrensDieterMarsagliaTsangGammaSampler
          * @param rng Generator of uniformly distributed random numbers.
          * @param alpha Alpha parameter of the distribution.
          * @param theta Theta parameter of the distribution.
-         * @throws IllegalArgumentException if {@code alpha <= 0} or {@code 
theta <= 0}
+         * @throws IllegalArgumentException if {@code alpha <= 0} or {@code 
theta <= 0},
+         * or if {@code alpha} or {@code theta} are not finite
          */
         AhrensDieterGammaSampler(UniformRandomProvider rng,
                                  double alpha,
@@ -221,7 +223,8 @@ public class AhrensDieterMarsagliaTsangGammaSampler
          * @param rng Generator of uniformly distributed random numbers.
          * @param alpha Alpha parameter of the distribution.
          * @param theta Theta parameter of the distribution.
-         * @throws IllegalArgumentException if {@code alpha <= 0} or {@code 
theta <= 0}
+         * @throws IllegalArgumentException if {@code alpha <= 0} or {@code 
theta <= 0},
+         * or if {@code alpha} or {@code theta} are not finite
          */
         MarsagliaTsangGammaSampler(UniformRandomProvider rng,
                                    double alpha,
@@ -282,7 +285,8 @@ public class AhrensDieterMarsagliaTsangGammaSampler
      * @param rng Generator of uniformly distributed random numbers.
      * @param alpha Alpha parameter of the distribution (this is a shape 
parameter).
      * @param theta Theta parameter of the distribution (this is a scale 
parameter).
-     * @throws IllegalArgumentException if {@code alpha <= 0} or {@code theta 
<= 0}
+     * @throws IllegalArgumentException if {@code alpha <= 0} or {@code theta 
<= 0},
+     * or if {@code alpha} or {@code theta} are not finite
      */
     public AhrensDieterMarsagliaTsangGammaSampler(UniformRandomProvider rng,
                                                   double alpha,
@@ -321,7 +325,8 @@ public class AhrensDieterMarsagliaTsangGammaSampler
      * @param alpha Alpha parameter of the distribution (this is a shape 
parameter).
      * @param theta Theta parameter of the distribution (this is a scale 
parameter).
      * @return the sampler
-     * @throws IllegalArgumentException if {@code alpha <= 0} or {@code theta 
<= 0}
+     * @throws IllegalArgumentException if {@code alpha <= 0} or {@code theta 
<= 0},
+     * or if {@code alpha} or {@code theta} are not finite
      * @since 1.3
      */
     public static SharedStateContinuousSampler of(UniformRandomProvider rng,
diff --git 
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/BoxMullerGaussianSampler.java
 
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/BoxMullerGaussianSampler.java
index 7831a01d..2361c3fd 100644
--- 
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/BoxMullerGaussianSampler.java
+++ 
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/BoxMullerGaussianSampler.java
@@ -53,12 +53,15 @@ public class BoxMullerGaussianSampler
      * @param rng Generator of uniformly distributed random numbers.
      * @param mean Mean of the Gaussian distribution.
      * @param standardDeviation Standard deviation of the Gaussian 
distribution.
-     * @throws IllegalArgumentException if {@code standardDeviation <= 0}
+     * @throws IllegalArgumentException if {@code standardDeviation <= 0} or 
is not finite;
+     * or {@code mean} is not finite
      */
     public BoxMullerGaussianSampler(UniformRandomProvider rng,
                                     double mean,
                                     double standardDeviation) {
-        this(mean, 
InternalUtils.requireStrictlyPositiveFinite(standardDeviation, 
"standardDeviation"), rng);
+        this(InternalUtils.requireFinite(mean, "mean"),
+             InternalUtils.requireStrictlyPositiveFinite(standardDeviation, 
"standardDeviation"),
+             rng);
     }
 
     /**
diff --git 
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/BoxMullerLogNormalSampler.java
 
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/BoxMullerLogNormalSampler.java
index 3e23fde4..8d3e576e 100644
--- 
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/BoxMullerLogNormalSampler.java
+++ 
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/BoxMullerLogNormalSampler.java
@@ -47,7 +47,8 @@ public class BoxMullerLogNormalSampler
      * @param rng Generator of uniformly distributed random numbers.
      * @param mu Mean of the natural logarithm of the distribution values.
      * @param sigma Standard deviation of the natural logarithm of the 
distribution values.
-     * @throws IllegalArgumentException if {@code sigma <= 0}.
+     * @throws IllegalArgumentException if {@code sigma <= 0}, or if {@code mu}
+     * or {@code sigma} are not finite.
      */
     public BoxMullerLogNormalSampler(UniformRandomProvider rng,
                                      double mu,
diff --git 
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/ChengBetaSampler.java
 
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/ChengBetaSampler.java
index 67fd9156..4e42097a 100644
--- 
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/ChengBetaSampler.java
+++ 
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/ChengBetaSampler.java
@@ -309,7 +309,8 @@ public class ChengBetaSampler
      * @param rng Generator of uniformly distributed random numbers.
      * @param alpha Distribution first shape parameter.
      * @param beta Distribution second shape parameter.
-     * @throws IllegalArgumentException if {@code alpha <= 0} or {@code beta 
<= 0}
+     * @throws IllegalArgumentException if {@code alpha <= 0} or {@code beta 
<= 0},
+     * or if {@code alpha} or {@code beta} are not finite
      */
     public ChengBetaSampler(UniformRandomProvider rng,
                             double alpha,
@@ -354,14 +355,15 @@ public class ChengBetaSampler
      * @param alpha Distribution first shape parameter.
      * @param beta Distribution second shape parameter.
      * @return the sampler
-     * @throws IllegalArgumentException if {@code alpha <= 0} or {@code beta 
<= 0}
+     * @throws IllegalArgumentException if {@code alpha <= 0} or {@code beta 
<= 0},
+     * or if {@code alpha} or {@code beta} are not finite
      * @since 1.3
      */
     public static SharedStateContinuousSampler of(UniformRandomProvider rng,
                                                   double alpha,
                                                   double beta) {
-        InternalUtils.requireStrictlyPositive(alpha, "alpha");
-        InternalUtils.requireStrictlyPositive(beta, "beta");
+        InternalUtils.requireStrictlyPositiveFinite(alpha, "alpha");
+        InternalUtils.requireStrictlyPositiveFinite(beta, "beta");
 
         // Choose the algorithm.
         final double a = Math.min(alpha, beta);
diff --git 
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/ContinuousUniformSampler.java
 
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/ContinuousUniformSampler.java
index a5f3e463..52b4940e 100644
--- 
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/ContinuousUniformSampler.java
+++ 
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/ContinuousUniformSampler.java
@@ -105,14 +105,15 @@ public class ContinuousUniformSampler
      * @param rng Generator of uniformly distributed random numbers.
      * @param lo Lower bound.
      * @param hi Higher bound.
+     * @throws IllegalArgumentException if {@code lo} or {@code hi} are not 
finite
      */
     public ContinuousUniformSampler(UniformRandomProvider rng,
                                     double lo,
                                     double hi) {
         super(null);
         this.rng = rng;
-        this.lo = lo;
-        this.hi = hi;
+        this.lo = InternalUtils.requireFinite(lo, "lower bound");
+        this.hi = InternalUtils.requireFinite(hi, "higher bound");
     }
 
     /** {@inheritDoc} */
@@ -163,6 +164,7 @@ public class ContinuousUniformSampler
      * @param lo Lower bound.
      * @param hi Higher bound.
      * @return the sampler
+     * @throws IllegalArgumentException if {@code lo} or {@code hi} are not 
finite
      * @since 1.3
      */
     public static SharedStateContinuousSampler of(UniformRandomProvider rng,
@@ -187,13 +189,16 @@ public class ContinuousUniformSampler
      * @param excludeBounds Set to {@code true} to use the open interval
      * {@code (lower, upper)}.
      * @return the sampler
-     * @throws IllegalArgumentException If the open interval is invalid.
+     * @throws IllegalArgumentException If the interval bounds are not finite,
+     * or the open interval is invalid.
      * @since 1.4
      */
     public static SharedStateContinuousSampler of(UniformRandomProvider rng,
                                                   double lo,
                                                   double hi,
                                                   boolean excludeBounds) {
+        InternalUtils.requireFinite(lo, "lo");
+        InternalUtils.requireFinite(hi, "hi");
         if (excludeBounds) {
             if (!validateOpenInterval(lo, hi)) {
                 throw new IllegalArgumentException("Invalid open interval (" +
diff --git 
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/GaussianSampler.java
 
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/GaussianSampler.java
index 3db9a875..8a4880d3 100644
--- 
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/GaussianSampler.java
+++ 
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/GaussianSampler.java
@@ -46,8 +46,8 @@ public class GaussianSampler implements 
SharedStateContinuousSampler {
      * @param normalized Generator of N(0,1) Gaussian distributed random 
numbers.
      * @param mean Mean of the Gaussian distribution.
      * @param standardDeviation Standard deviation of the Gaussian 
distribution.
-     * @throws IllegalArgumentException if {@code standardDeviation <= 0} or 
is infinite;
-     * or {@code mean} is infinite
+     * @throws IllegalArgumentException if {@code standardDeviation <= 0} or 
is not finite;
+     * or {@code mean} is not finite
      */
     public GaussianSampler(NormalizedGaussianSampler normalized,
                            double mean,
diff --git 
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/GeometricSampler.java
 
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/GeometricSampler.java
index 27b63edd..33cf1a21 100644
--- 
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/GeometricSampler.java
+++ 
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/GeometricSampler.java
@@ -71,6 +71,32 @@ public final class GeometricSampler {
         }
     }
 
+    /**
+     * Sample from the geometric distribution when the probability of success 
is effectively zero.
+     */
+    private static final class GeometricP0Sampler
+        implements SharedStateDiscreteSampler {
+        /** The single instance. */
+        static final GeometricP0Sampler INSTANCE = new GeometricP0Sampler();
+
+        @Override
+        public int sample() {
+            // When probability of success is effectively 0 the sample is 
always the max value
+            return Integer.MAX_VALUE;
+        }
+
+        @Override
+        public String toString() {
+            return "Geometric(p~0) deviate";
+        }
+
+        @Override
+        public SharedStateDiscreteSampler 
withUniformRandomProvider(UniformRandomProvider rng) {
+            // No requirement for a new instance
+            return this;
+        }
+    }
+
     /**
      * Sample from the geometric distribution by using a related exponential 
distribution.
      */
@@ -83,22 +109,11 @@ public final class GeometricSampler {
 
         /**
          * @param rng Generator of uniformly distributed random numbers
-         * @param probabilityOfSuccess The probability of success (must be in 
the range
-         * {@code [0 < probabilityOfSuccess < 1]})
+         * @param exponentialMean The mean of the related exponential 
distribution (must be
+         * strictly positive finite)
          */
-        GeometricExponentialSampler(UniformRandomProvider rng, double 
probabilityOfSuccess) {
+        GeometricExponentialSampler(UniformRandomProvider rng, double 
exponentialMean) {
             this.rng = rng;
-            // Use a related exponential distribution:
-            // λ = −ln(1 − probabilityOfSuccess)
-            // exponential mean = 1 / λ
-            // --
-            // Note on validation:
-            // If probabilityOfSuccess == Math.nextDown(1.0) the exponential 
mean is >0 (valid).
-            // If probabilityOfSuccess == Double.MIN_VALUE the exponential 
mean is +Infinity
-            // and the sample will always be Integer.MAX_VALUE (the 
distribution is truncated). It
-            // is noted in the class Javadoc that the use of a small p leads 
to truncation so
-            // no checks are made for this case.
-            final double exponentialMean = 1.0 / 
(-Math.log1p(-probabilityOfSuccess));
             exponentialSampler = ZigguratSampler.Exponential.of(rng, 
exponentialMean);
         }
 
@@ -149,8 +164,24 @@ public final class GeometricSampler {
                 "Probability of success (p) must be in the range [0 < p <= 1]: 
" +
                     probabilityOfSuccess);
         }
-        return probabilityOfSuccess == 1 ?
-            GeometricP1Sampler.INSTANCE :
-            new GeometricExponentialSampler(rng, probabilityOfSuccess);
+        if (probabilityOfSuccess == 1) {
+            return GeometricP1Sampler.INSTANCE;
+        }
+        // Use a related exponential distribution:
+        // λ = −ln(1 − probabilityOfSuccess)
+        // exponential mean = 1 / λ
+        // --
+        // Note on validation:
+        // If probabilityOfSuccess == Math.nextDown(1.0) the exponential mean 
is >0 (valid).
+        // If probabilityOfSuccess == Double.MIN_VALUE the exponential mean is 
+Infinity
+        // and the sample will always be Integer.MAX_VALUE (the distribution 
is truncated). It
+        // is noted in the class Javadoc that the use of a small p leads to 
truncation.
+        // This special case requires a p=0 sampler as the infinite mean will 
raise an exception
+        // from the exponential sampler.
+        final double exponentialMean = 1.0 / 
(-Math.log1p(-probabilityOfSuccess));
+        if (Double.isInfinite(exponentialMean)) {
+            return GeometricP0Sampler.INSTANCE;
+        }
+        return new GeometricExponentialSampler(rng, exponentialMean);
     }
 }
diff --git 
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/InternalUtils.java
 
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/InternalUtils.java
index 8e150d09..de1e139c 100644
--- 
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/InternalUtils.java
+++ 
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/InternalUtils.java
@@ -134,30 +134,13 @@ final class InternalUtils {
         return x;
     }
 
-    /**
-     * Checks the value {@code x >= 0}.
-     * Note: This method allows {@code x == -0.0}.
-     *
-     * @param x Value.
-     * @param name Name of the value.
-     * @return x
-     * @throws IllegalArgumentException if {@code x < 0}
-     */
-    static double requirePositive(double x, String name) {
-        // Logic inversion detects NaN
-        if (!(x >= 0)) {
-            throw new IllegalArgumentException(name + " is not positive: " + 
x);
-        }
-        return x;
-    }
-
     /**
      * Checks the value {@code x > 0}.
      *
      * @param x Value.
      * @param name Name of the value.
      * @return x
-     * @throws IllegalArgumentException if {@code x <= 0}
+     * @throws IllegalArgumentException if {@code x <= 0} or is NaN
      */
     static double requireStrictlyPositive(double x, String name) {
         // Logic inversion detects NaN
diff --git 
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/InverseTransformParetoSampler.java
 
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/InverseTransformParetoSampler.java
index 08cfaedb..c078b9e6 100644
--- 
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/InverseTransformParetoSampler.java
+++ 
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/InverseTransformParetoSampler.java
@@ -44,13 +44,14 @@ public class InverseTransformParetoSampler
      * @param rng Generator of uniformly distributed random numbers.
      * @param scale Scale of the distribution.
      * @param shape Shape of the distribution.
-     * @throws IllegalArgumentException if {@code scale <= 0} or {@code shape 
<= 0}
+     * @throws IllegalArgumentException if {@code scale <= 0} or {@code shape 
<= 0},
+     * or if {@code scale} is not finite or {@code shape} is NaN
      */
     public InverseTransformParetoSampler(UniformRandomProvider rng,
                                          double scale,
                                          double shape) {
         // Validation before java.lang.Object constructor exits prevents 
partially initialized object
-        this(InternalUtils.requireStrictlyPositive(scale, "scale"),
+        this(InternalUtils.requireStrictlyPositiveFinite(scale, "scale"),
              InternalUtils.requireStrictlyPositive(shape, "shape"),
              rng);
     }
@@ -118,7 +119,8 @@ public class InverseTransformParetoSampler
      * @param scale Scale of the distribution.
      * @param shape Shape of the distribution.
      * @return the sampler
-     * @throws IllegalArgumentException if {@code scale <= 0} or {@code shape 
<= 0}
+     * @throws IllegalArgumentException if {@code scale <= 0} or {@code shape 
<= 0},
+     * or if {@code scale} is not finite
      * @since 1.3
      */
     public static SharedStateContinuousSampler of(UniformRandomProvider rng,
diff --git 
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/LevySampler.java
 
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/LevySampler.java
index 28468f8b..91c274c7 100644
--- 
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/LevySampler.java
+++ 
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/LevySampler.java
@@ -86,12 +86,14 @@ public final class LevySampler implements 
SharedStateContinuousSampler {
      * @param location Location of the Lévy distribution.
      * @param scale Scale of the Lévy distribution.
      * @return the sampler
-     * @throws IllegalArgumentException if {@code scale <= 0}
+     * @throws IllegalArgumentException if {@code scale <= 0}, or if
+     * {@code location} or {@code scale} are not finite
      */
     public static LevySampler of(UniformRandomProvider rng,
                                  double location,
                                  double scale) {
-        InternalUtils.requireStrictlyPositive(scale, "scale");
+        InternalUtils.requireFinite(location, "location");
+        InternalUtils.requireStrictlyPositiveFinite(scale, "scale");
         return new LevySampler(rng, location, scale);
     }
 }
diff --git 
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/LogNormalSampler.java
 
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/LogNormalSampler.java
index 52f02708..3c97bc06 100644
--- 
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/LogNormalSampler.java
+++ 
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/LogNormalSampler.java
@@ -37,13 +37,15 @@ public class LogNormalSampler implements 
SharedStateContinuousSampler {
      * @param gaussian N(0,1) generator.
      * @param mu Mean of the natural logarithm of the distribution values.
      * @param sigma Standard deviation of the natural logarithm of the 
distribution values.
-     * @throws IllegalArgumentException if {@code sigma <= 0}.
+     * @throws IllegalArgumentException if {@code sigma <= 0}, or if {@code mu}
+     * or {@code sigma} are not finite.
      */
     public LogNormalSampler(NormalizedGaussianSampler gaussian,
                             double mu,
                             double sigma) {
         // Validation before java.lang.Object constructor exits prevents 
partially initialized object
-        this(mu, InternalUtils.requireStrictlyPositive(sigma, "sigma"), 
gaussian);
+        this(InternalUtils.requireFinite(mu, "mu"),
+             InternalUtils.requireStrictlyPositiveFinite(sigma, "sigma"), 
gaussian);
     }
 
     /**
@@ -111,7 +113,8 @@ public class LogNormalSampler implements 
SharedStateContinuousSampler {
      * @param mu Mean of the natural logarithm of the distribution values.
      * @param sigma Standard deviation of the natural logarithm of the 
distribution values.
      * @return the sampler
-     * @throws IllegalArgumentException if {@code sigma <= 0}.
+     * @throws IllegalArgumentException if {@code sigma <= 0}, or if {@code mu}
+     * or {@code sigma} are not finite.
      * @see #withUniformRandomProvider(UniformRandomProvider)
      * @since 1.3
      */
diff --git 
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/RejectionInversionZipfSampler.java
 
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/RejectionInversionZipfSampler.java
index 0aa97187..1c6dc97f 100644
--- 
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/RejectionInversionZipfSampler.java
+++ 
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/RejectionInversionZipfSampler.java
@@ -271,7 +271,7 @@ public class RejectionInversionZipfSampler
      * @param numberOfElements Number of elements.
      * @param exponent Exponent.
      * @throws IllegalArgumentException if {@code numberOfElements <= 0}
-     * or {@code exponent < 0}.
+     * or {@code exponent < 0} or is non-finite.
      */
     public RejectionInversionZipfSampler(UniformRandomProvider rng,
                                          int numberOfElements,
@@ -333,7 +333,7 @@ public class RejectionInversionZipfSampler
      * @param exponent Exponent.
      * @return the sampler
      * @throws IllegalArgumentException if {@code numberOfElements <= 0} or
-     * {@code exponent < 0}.
+     * {@code exponent < 0} or is non-finite.
      * @since 1.3
      */
     public static SharedStateDiscreteSampler of(UniformRandomProvider rng,
@@ -342,7 +342,7 @@ public class RejectionInversionZipfSampler
         if (numberOfElements <= 0) {
             throw new IllegalArgumentException("number of elements is not 
strictly positive: " + numberOfElements);
         }
-        InternalUtils.requirePositive(exponent, "exponent");
+        InternalUtils.requirePositiveFinite(exponent, "exponent");
 
         // When the exponent is at the limit of 0 the distribution PMF reduces 
to 1 / n
         // and sampling can use a discrete uniform sampler.
diff --git 
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/ZigguratSampler.java
 
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/ZigguratSampler.java
index f714d107..600e96f0 100644
--- 
a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/ZigguratSampler.java
+++ 
b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/ZigguratSampler.java
@@ -724,10 +724,11 @@ public abstract class ZigguratSampler implements 
SharedStateContinuousSampler {
          * @param rng Generator of uniformly distributed random numbers.
          * @param mean Mean.
          * @return the sampler
-         * @throws IllegalArgumentException if the mean is not strictly 
positive ({@code mean <= 0})
+         * @throws IllegalArgumentException if the mean is not strictly 
positive
+         * and finite ({@code mean <= 0} or infinite)
          */
         public static Exponential of(UniformRandomProvider rng, double mean) {
-            return new ExponentialMean(rng, 
InternalUtils.requireStrictlyPositive(mean, "mean"));
+            return new ExponentialMean(rng, 
InternalUtils.requireStrictlyPositiveFinite(mean, "mean"));
         }
     }
 
diff --git 
a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/ContinuousUniformSamplerTest.java
 
b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/ContinuousUniformSamplerTest.java
index 43db611e..1db9e73c 100644
--- 
a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/ContinuousUniformSamplerTest.java
+++ 
b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/ContinuousUniformSamplerTest.java
@@ -114,6 +114,14 @@ class ContinuousUniformSamplerTest {
             {1.23, Math.nextUp(1.23)},
             // Different exponent
             {2.0, Math.nextDown(2.0)},
+            // Non-finite bounds. An infinite bound creates samples equal to 
the bound
+            // (or NaN) and the resampling to exclude the bounds cannot 
terminate.
+            {0.0, Double.POSITIVE_INFINITY},
+            {Double.NEGATIVE_INFINITY, 0.0},
+            {Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY},
+            {0.0, Double.NaN},
+            {Double.NaN, 0.0},
+            {Double.NaN, Double.NaN},
         }) {
             final double low = interval[0];
             final double high = interval[1];
diff --git 
a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/GeometricSamplerTest.java
 
b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/GeometricSamplerTest.java
index ff69911b..9e5f5f31 100644
--- 
a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/GeometricSamplerTest.java
+++ 
b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/GeometricSamplerTest.java
@@ -70,6 +70,18 @@ class GeometricSamplerTest {
             "Missing 'Geometric' from toString");
     }
 
+    /**
+     * Test the edge case where the probability of success approaches 0 since 
it uses a different
+     * {@link Object#toString()} method to the normal case tested elsewhere.
+     */
+    @Test
+    void testProbabilityOfSuccessIsZeroSamplerToString() {
+        final UniformRandomProvider unusedRng = RandomAssert.seededRNG();
+        final SharedStateDiscreteSampler sampler = 
GeometricSampler.of(unusedRng, Double.MIN_VALUE);
+        Assertions.assertTrue(sampler.toString().contains("Geometric"),
+            "Missing 'Geometric' from toString");
+    }
+
     /**
      * Test the edge case where the probability of success is nearly 0. This 
is a valid geometric
      * distribution but the sample is clipped to max integer value because the 
underlying
@@ -129,6 +141,15 @@ class GeometricSamplerTest {
         testSharedStateSampler(1.0);
     }
 
+    /**
+     * Test the SharedStateSampler implementation with the edge case when the 
probability of
+     * success approaches {@code 0.0}.
+     */
+    @Test
+    void testSharedStateSamplerWithProbabilityOfSuccessEffectivelyZero() {
+        testSharedStateSampler(Double.MIN_VALUE);
+    }
+
     /**
      * Test the SharedStateSampler implementation.
      *
diff --git 
a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/NonFiniteParameterValidationTest.java
 
b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/NonFiniteParameterValidationTest.java
new file mode 100644
index 00000000..8823cff5
--- /dev/null
+++ 
b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/NonFiniteParameterValidationTest.java
@@ -0,0 +1,142 @@
+/*
+ * 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.sampling.RandomAssert;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.function.Executable;
+
+/**
+ * Test that public sampler factory entry points reject non-finite (infinite 
or NaN)
+ * distribution parameters instead of silently creating a sampler that returns
+ * NaN, infinite or degenerate-constant samples forever.
+ *
+ * <p>This test collects all samplers identified for the 1.8 release that did 
not
+ * verify finite arguments. Other samplers validating for finite arguments
+ * have tests in their respective test class.
+ *
+ * <p>See RNG-194.
+ */
+class NonFiniteParameterValidationTest {
+    /** Positive infinity. */
+    private static final double INF = Double.POSITIVE_INFINITY;
+    /** Not a number. */
+    private static final double NAN = Double.NaN;
+
+
+    @Test
+    void testBoxMullerGaussianSamplerThrowsWithNonFiniteParameters() {
+        final UniformRandomProvider rng = RandomAssert.seededRNG();
+        assertThrowsIAE(() -> new BoxMullerGaussianSampler(rng, INF, 1.0));
+        assertThrowsIAE(() -> new BoxMullerGaussianSampler(rng, 0.0, INF));
+        assertThrowsIAE(() -> new BoxMullerGaussianSampler(rng, NAN, 1.0));
+        assertThrowsIAE(() -> new BoxMullerGaussianSampler(rng, 0.0, NAN));
+    }
+
+    @Test
+    void testChengBetaSamplerThrowsWithNonFiniteShape() {
+        final UniformRandomProvider rng = RandomAssert.seededRNG();
+        assertThrowsIAE(() -> ChengBetaSampler.of(rng, INF, 2.0));
+        assertThrowsIAE(() -> ChengBetaSampler.of(rng, 2.0, INF));
+        assertThrowsIAE(() -> ChengBetaSampler.of(rng, NAN, 2.0));
+        assertThrowsIAE(() -> ChengBetaSampler.of(rng, 2.0, NAN));
+    }
+
+    @Test
+    void testGammaSamplerThrowsWithNonFiniteParameters() {
+        final UniformRandomProvider rng = RandomAssert.seededRNG();
+        assertThrowsIAE(() -> AhrensDieterMarsagliaTsangGammaSampler.of(rng, 
INF, 1.0));
+        assertThrowsIAE(() -> AhrensDieterMarsagliaTsangGammaSampler.of(rng, 
0.5, INF));
+        assertThrowsIAE(() -> AhrensDieterMarsagliaTsangGammaSampler.of(rng, 
1.5, INF));
+        assertThrowsIAE(() -> AhrensDieterMarsagliaTsangGammaSampler.of(rng, 
NAN, 1.0));
+        assertThrowsIAE(() -> AhrensDieterMarsagliaTsangGammaSampler.of(rng, 
0.5, NAN));
+        assertThrowsIAE(() -> AhrensDieterMarsagliaTsangGammaSampler.of(rng, 
1.5, NAN));
+    }
+
+    @Test
+    void testLogNormalSamplerThrowsWithNonFiniteParameters() {
+        final UniformRandomProvider rng = RandomAssert.seededRNG();
+        final NormalizedGaussianSampler gaussian = 
ZigguratSampler.NormalizedGaussian.of(rng);
+        assertThrowsIAE(() -> LogNormalSampler.of(gaussian, INF, 1.0));
+        assertThrowsIAE(() -> LogNormalSampler.of(gaussian, 0.0, INF));
+        assertThrowsIAE(() -> LogNormalSampler.of(gaussian, NAN, 1.0));
+        assertThrowsIAE(() -> LogNormalSampler.of(gaussian, 0.0, NAN));
+    }
+
+    @Test
+    void testLevySamplerThrowsWithNonFiniteParameters() {
+        final UniformRandomProvider rng = RandomAssert.seededRNG();
+        assertThrowsIAE(() -> LevySampler.of(rng, INF, 1.0));
+        assertThrowsIAE(() -> LevySampler.of(rng, 0.0, INF));
+        assertThrowsIAE(() -> LevySampler.of(rng, NAN, 1.0));
+        assertThrowsIAE(() -> LevySampler.of(rng, 0.0, NAN));
+    }
+
+    @Test
+    void testExponentialSamplersThrowWithNonFiniteParameters() {
+        final UniformRandomProvider rng = RandomAssert.seededRNG();
+        assertThrowsIAE(() -> AhrensDieterExponentialSampler.of(rng, INF));
+        assertThrowsIAE(() -> ZigguratSampler.Exponential.of(rng, INF));
+        assertThrowsIAE(() -> AhrensDieterExponentialSampler.of(rng, NAN));
+        assertThrowsIAE(() -> ZigguratSampler.Exponential.of(rng, NAN));
+    }
+
+    @Test
+    void testParetoSamplerThrowsWithNonFiniteScale() {
+        final UniformRandomProvider rng = RandomAssert.seededRNG();
+        assertThrowsIAE(() -> InverseTransformParetoSampler.of(rng, INF, 1.0));
+        assertThrowsIAE(() -> InverseTransformParetoSampler.of(rng, NAN, 1.0));
+        // Note: an infinite shape is a supported limit of the distribution
+        // (all samples equal the scale); it is deliberately not rejected.
+        assertThrowsIAE(() -> InverseTransformParetoSampler.of(rng, 1.0, NAN));
+    }
+
+    @Test
+    void testContinuousUniformSamplerThrowsWithNonFiniteBounds() {
+        final UniformRandomProvider rng = RandomAssert.seededRNG();
+        assertThrowsIAE(() -> ContinuousUniformSampler.of(rng, INF, 1.0));
+        assertThrowsIAE(() -> ContinuousUniformSampler.of(rng, 0.0, INF));
+        assertThrowsIAE(() -> ContinuousUniformSampler.of(rng, INF, 1.0, 
true));
+        assertThrowsIAE(() -> ContinuousUniformSampler.of(rng, 0.0, INF, 
false));
+        assertThrowsIAE(() -> new ContinuousUniformSampler(rng, INF, 1.0));
+        assertThrowsIAE(() -> new ContinuousUniformSampler(rng, 0.0, INF));
+        assertThrowsIAE(() -> ContinuousUniformSampler.of(rng, NAN, 1.0));
+        assertThrowsIAE(() -> ContinuousUniformSampler.of(rng, 0.0, NAN));
+        assertThrowsIAE(() -> ContinuousUniformSampler.of(rng, NAN, 1.0, 
true));
+        assertThrowsIAE(() -> ContinuousUniformSampler.of(rng, 0.0, NAN, 
false));
+        assertThrowsIAE(() -> new ContinuousUniformSampler(rng, NAN, 1.0));
+        assertThrowsIAE(() -> new ContinuousUniformSampler(rng, 0.0, NAN));
+    }
+
+    @Test
+    void testRejectionInversionZipfSamplerThrowsWithNonFiniteExponent() {
+        final UniformRandomProvider rng = RandomAssert.seededRNG();
+        assertThrowsIAE(() -> RejectionInversionZipfSampler.of(rng, 10, INF));
+        assertThrowsIAE(() -> RejectionInversionZipfSampler.of(rng, 10, NAN));
+    }
+
+    /**
+     * Assert the executable throws an {@link IllegalArgumentException}.
+     *
+     * @param executable the executable
+     */
+    private static void assertThrowsIAE(Executable executable) {
+        Assertions.assertThrows(IllegalArgumentException.class, executable);
+    }
+}
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 7cbd4abb..ad886f69 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -56,6 +56,11 @@ If the output is not quite correct, check for invisible 
trailing spaces!
     <release version="1.8" date="TBD" description="
 New features, updates and bug fixes (requires Java 8).
 ">
+      <action dev="aherbert" type="fix" due-to="Security scan, Alex Herbert" 
issue="RNG-194">
+        Samplers to validate parameters are finite to avoid creating a sampler 
that
+        returns NaN, infinite or degenerate-constant samples forever; or 
enters an
+        infinite loop during sample generation.
+      </action>
 </release>
 
     <release version="1.7" date="2026-04-20" description="

Reply via email to