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 2021/11/22 08:49:24 UTC

[commons-statistics] branch master updated (008d103 -> 8fb31cf)

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

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


    from 008d103  Special case of CDF/SF for the Poisson distribution with x=0
     new 71d8081  STATISTICS-47: Add inverse survival probability
     new c7bae4b  STATISTICS-47: Add implementations for inverse survival probability
     new 05d4200  Use high-precision sqrt(2 * sd * sd)
     new 8fb31cf  STATISTIC-47: Add isf command to distribution examples application

The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../AbstractContinuousDistribution.java            | 134 +++++++++---
 .../distribution/AbstractDiscreteDistribution.java | 130 ++++++++----
 .../distribution/CauchyDistribution.java           |  17 ++
 .../distribution/ChiSquaredDistribution.java       |   6 +
 .../distribution/ContinuousDistribution.java       |  25 ++-
 .../distribution/DiscreteDistribution.java         |  31 ++-
 .../distribution/ExponentialDistribution.java      |  21 +-
 .../distribution/GeometricDistribution.java        |  74 +++++--
 .../distribution/GumbelDistribution.java           |  12 ++
 .../distribution/LaplaceDistribution.java          |  14 ++
 .../statistics/distribution/LevyDistribution.java  |  11 +-
 .../distribution/LogNormalDistribution.java        |  23 ++-
 .../distribution/LogisticDistribution.java         |  13 ++
 .../distribution/NormalDistribution.java           |   9 +-
 .../distribution/ParetoDistribution.java           |  13 ++
 .../statistics/distribution/TDistribution.java     |   9 +
 .../distribution/TriangularDistribution.java       |  46 ++++-
 .../distribution/TruncatedNormalDistribution.java  |  21 +-
 .../UniformContinuousDistribution.java             |  10 +-
 .../distribution/UniformDiscreteDistribution.java  |  81 ++++++--
 .../distribution/WeibullDistribution.java          |  17 ++
 .../AbstractContinuousDistributionTest.java        | 158 ++++++++++++++-
 .../AbstractDiscreteDistributionTest.java          | 147 +++++++++++---
 .../BaseContinuousDistributionTest.java            | 149 ++++++++++----
 .../distribution/BaseDiscreteDistributionTest.java | 224 +++++++++++++++++----
 .../distribution/BaseDistributionTest.java         |  53 +++++
 .../distribution/BetaDistributionTest.java         |   4 +-
 .../distribution/ContinuousDistributionTest.java   |   8 +-
 .../distribution/DiscreteDistributionTest.java     |  11 +
 .../distribution/DistributionTestData.java         |  75 ++++++-
 .../distribution/ExponentialDistributionTest.java  |   7 +
 .../distribution/GeometricDistributionTest.java    | 121 ++++++++++-
 .../distribution/NormalDistributionTest.java       |   6 +-
 .../UniformDiscreteDistributionTest.java           |  89 +++++---
 .../distribution/test.beta.15.properties           |   2 +
 .../statistics/distribution/test.beta.5.properties |   2 +
 .../distribution/test.binomial.1.properties        |   8 +-
 .../distribution/test.binomial.4.properties        |   8 +-
 .../distribution/test.binomial.5.properties        |   3 +
 .../distribution/test.binomial.6.properties        |   2 +
 .../distribution/test.binomial.7.properties        |   2 +
 .../distribution/test.chisquared.2.properties      |   3 +
 .../statistics/distribution/test.f.6.properties    |   2 +
 .../distribution/test.geometric.1.properties       |  35 ++++
 .../distribution/test.geometric.2.properties       |   2 +
 .../distribution/test.hypergeometric.1.properties  |   4 +
 .../distribution/test.hypergeometric.2.properties  |   2 +
 .../distribution/test.hypergeometric.3.properties  |   2 +
 .../distribution/test.hypergeometric.4.properties  |   2 +
 .../distribution/test.pascal.1.properties          |   5 +
 .../distribution/test.pascal.2.properties          |   2 +
 .../distribution/test.poisson.1.properties         |  11 +-
 .../statistics/distribution/test.t.3.properties    |   4 +-
 .../distribution/test.triangular.1.properties      |   1 +
 .../distribution/test.triangular.2.properties      |   1 +
 ...e.2.properties => test.triangular.3.properties} |  22 +-
 ...e.2.properties => test.triangular.4.properties} |  22 +-
 .../distribution/test.uniformdiscrete.1.properties |   5 +
 .../distribution/test.uniformdiscrete.2.properties |   2 +
 .../statistics/distribution/test.zipf.1.properties |   4 +
 .../examples-distribution/HOWTO.md                 |   3 +-
 .../examples/distribution/BetaCommand.java         |  27 ++-
 .../examples/distribution/BinomialCommand.java     |  27 ++-
 .../examples/distribution/CauchyCommand.java       |  27 ++-
 .../examples/distribution/ChiSquaredCommand.java   |  27 ++-
 .../distribution/DistributionFunction.java         |   4 +-
 .../examples/distribution/DistributionOptions.java |   2 +-
 .../examples/distribution/DistributionUtils.java   | 117 ++++++++---
 .../examples/distribution/ExpCommand.java          |  27 ++-
 .../statistics/examples/distribution/FCommand.java |  27 ++-
 .../examples/distribution/GammaCommand.java        |  27 ++-
 .../examples/distribution/GeometricCommand.java    |  27 ++-
 .../examples/distribution/GumbelCommand.java       |  27 ++-
 .../distribution/HypergeometricCommand.java        |  27 ++-
 .../examples/distribution/LaplaceCommand.java      |  27 ++-
 .../examples/distribution/LevyCommand.java         |  27 ++-
 .../examples/distribution/LogNormalCommand.java    |  27 ++-
 .../examples/distribution/LogisticCommand.java     |  27 ++-
 .../examples/distribution/NakagamiCommand.java     |  27 ++-
 .../examples/distribution/NormalCommand.java       |  27 ++-
 .../examples/distribution/ParetoCommand.java       |  27 ++-
 .../examples/distribution/PascalCommand.java       |  27 ++-
 .../examples/distribution/PoissonCommand.java      |  27 ++-
 .../statistics/examples/distribution/TCommand.java |  27 ++-
 .../examples/distribution/TriangularCommand.java   |  35 ++--
 .../distribution/TruncatedNormalCommand.java       |  27 ++-
 .../distribution/UniformContinuousCommand.java     |  27 ++-
 .../distribution/UniformDiscreteCommand.java       |  27 ++-
 .../examples/distribution/WeibullCommand.java      |  27 ++-
 .../examples/distribution/ZipfCommand.java         |  27 ++-
 src/main/resources/pmd/pmd-ruleset.xml             |   7 +
 91 files changed, 2212 insertions(+), 558 deletions(-)
 copy commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/{test.uniformdiscrete.2.properties => test.triangular.3.properties} (69%)
 copy commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/{test.uniformdiscrete.2.properties => test.triangular.4.properties} (69%)

[commons-statistics] 03/04: Use high-precision sqrt(2 * sd * sd)

Posted by ah...@apache.org.
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-statistics.git

commit 05d420002b4822cb9d57c97d0fc8d9bf529c4a94
Author: Alex Herbert <ah...@apache.org>
AuthorDate: Fri Nov 19 08:15:14 2021 +0000

    Use high-precision sqrt(2 * sd * sd)
---
 .../commons/statistics/distribution/LogNormalDistribution.java    | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/LogNormalDistribution.java b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/LogNormalDistribution.java
index ca22b02..875a0be 100644
--- a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/LogNormalDistribution.java
+++ b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/LogNormalDistribution.java
@@ -42,10 +42,10 @@ import org.apache.commons.rng.sampling.distribution.ZigguratSampler;
  * </ul>
  */
 public final class LogNormalDistribution extends AbstractContinuousDistribution {
+    /** 0.5 * ln(2 * pi). Computed to 25-digits precision. */
+    private static final double HALF_LOG_TWO_PI = 0.9189385332046727417803297;
     /** &radic;(2 &pi;). */
     private static final double SQRT2PI = Math.sqrt(2 * Math.PI);
-    /** &radic;(2). */
-    private static final double SQRT2 = Math.sqrt(2);
     /** The mu parameter of this distribution. */
     private final double mu;
     /** The sigma parameter of this distribution. */
@@ -65,8 +65,8 @@ public final class LogNormalDistribution extends AbstractContinuousDistribution
                                   double sigma) {
         this.mu = mu;
         this.sigma = sigma;
-        logSigmaPlusHalfLog2Pi = Math.log(sigma) + 0.5 * Math.log(2 * Math.PI);
-        sigmaSqrt2 = sigma * SQRT2;
+        logSigmaPlusHalfLog2Pi = Math.log(sigma) + HALF_LOG_TWO_PI;
+        sigmaSqrt2 = ExtendedPrecision.sqrt2xx(sigma);
         sigmaSqrt2Pi = sigma * SQRT2PI;
     }
 

[commons-statistics] 02/04: STATISTICS-47: Add implementations for inverse survival probability

Posted by ah...@apache.org.
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-statistics.git

commit c7bae4b283e424271d7141d0ee552d126e0a63c8
Author: Alex Herbert <ah...@apache.org>
AuthorDate: Thu Nov 18 09:29:29 2021 +0000

    STATISTICS-47: Add implementations for inverse survival probability
    
    Add tests for inverse survival probability.
    
    Update test tolerances for cases where the test tolerance is now limited
    by the inverse SF.
    
    Add test data for properties files that have icdf.points to test the
    complement of the icdf.
---
 .../AbstractContinuousDistribution.java            |  10 +-
 .../distribution/AbstractDiscreteDistribution.java |   6 +-
 .../distribution/CauchyDistribution.java           |  17 ++
 .../distribution/ChiSquaredDistribution.java       |   6 +
 .../distribution/ExponentialDistribution.java      |  21 +-
 .../distribution/GeometricDistribution.java        |  74 +++++--
 .../distribution/GumbelDistribution.java           |  12 ++
 .../distribution/LaplaceDistribution.java          |  14 ++
 .../statistics/distribution/LevyDistribution.java  |  11 +-
 .../distribution/LogNormalDistribution.java        |  15 +-
 .../distribution/LogisticDistribution.java         |  13 ++
 .../distribution/NormalDistribution.java           |   9 +-
 .../distribution/ParetoDistribution.java           |  13 ++
 .../statistics/distribution/TDistribution.java     |   9 +
 .../distribution/TriangularDistribution.java       |  46 ++++-
 .../distribution/TruncatedNormalDistribution.java  |  21 +-
 .../UniformContinuousDistribution.java             |  10 +-
 .../distribution/UniformDiscreteDistribution.java  |  81 ++++++--
 .../distribution/WeibullDistribution.java          |  17 ++
 .../BaseContinuousDistributionTest.java            | 149 ++++++++++----
 .../distribution/BaseDiscreteDistributionTest.java | 224 +++++++++++++++++----
 .../distribution/BaseDistributionTest.java         |  53 +++++
 .../distribution/BetaDistributionTest.java         |   4 +-
 .../distribution/DistributionTestData.java         |  75 ++++++-
 .../distribution/ExponentialDistributionTest.java  |   7 +
 .../distribution/GeometricDistributionTest.java    | 121 ++++++++++-
 .../distribution/NormalDistributionTest.java       |   6 +-
 .../UniformDiscreteDistributionTest.java           |  89 +++++---
 .../distribution/test.beta.15.properties           |   2 +
 .../statistics/distribution/test.beta.5.properties |   2 +
 .../distribution/test.binomial.1.properties        |   8 +-
 .../distribution/test.binomial.4.properties        |   8 +-
 .../distribution/test.binomial.5.properties        |   3 +
 .../distribution/test.binomial.6.properties        |   2 +
 .../distribution/test.binomial.7.properties        |   2 +
 .../distribution/test.chisquared.2.properties      |   3 +
 .../statistics/distribution/test.f.6.properties    |   2 +
 .../distribution/test.geometric.1.properties       |  35 ++++
 .../distribution/test.geometric.2.properties       |   2 +
 .../distribution/test.hypergeometric.1.properties  |   4 +
 .../distribution/test.hypergeometric.2.properties  |   2 +
 .../distribution/test.hypergeometric.3.properties  |   2 +
 .../distribution/test.hypergeometric.4.properties  |   2 +
 .../distribution/test.pascal.1.properties          |   5 +
 .../distribution/test.pascal.2.properties          |   2 +
 .../distribution/test.poisson.1.properties         |  11 +-
 .../statistics/distribution/test.t.3.properties    |   4 +-
 .../distribution/test.triangular.1.properties      |   1 +
 .../distribution/test.triangular.2.properties      |   1 +
 ...e.2.properties => test.triangular.3.properties} |  22 +-
 ...e.2.properties => test.triangular.4.properties} |  22 +-
 .../distribution/test.uniformdiscrete.1.properties |   5 +
 .../distribution/test.uniformdiscrete.2.properties |   2 +
 .../statistics/distribution/test.zipf.1.properties |   4 +
 54 files changed, 1104 insertions(+), 187 deletions(-)

diff --git a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/AbstractContinuousDistribution.java b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/AbstractContinuousDistribution.java
index 178fe4d..3ce4ad9 100644
--- a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/AbstractContinuousDistribution.java
+++ b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/AbstractContinuousDistribution.java
@@ -148,7 +148,7 @@ abstract class AbstractContinuousDistribution
      * @throws IllegalArgumentException if {@code p < 0} or {@code p > 1}
      */
     @Override
-    public double inverseCumulativeProbability(final double p) {
+    public double inverseCumulativeProbability(double p) {
         ArgumentUtils.checkProbability(p);
         return inverseProbability(p, 1 - p, false);
     }
@@ -168,7 +168,7 @@ abstract class AbstractContinuousDistribution
      * @throws IllegalArgumentException if {@code p < 0} or {@code p > 1}
      */
     @Override
-    public double inverseSurvivalProbability(final double p) {
+    public double inverseSurvivalProbability(double p) {
         ArgumentUtils.checkProbability(p);
         return inverseProbability(1 - p, p, true);
     }
@@ -214,12 +214,12 @@ abstract class AbstractContinuousDistribution
          */
 
         double lowerBound = getSupportLowerBound();
-        double upperBound = getSupportUpperBound();
         if (p == 0) {
-            return complement ? upperBound : lowerBound;
+            return lowerBound;
         }
+        double upperBound = getSupportUpperBound();
         if (q == 0) {
-            return complement ? lowerBound : upperBound;
+            return upperBound;
         }
 
         final double mu = getMean();
diff --git a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/AbstractDiscreteDistribution.java b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/AbstractDiscreteDistribution.java
index 9c7394a..4bb5cbc 100644
--- a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/AbstractDiscreteDistribution.java
+++ b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/AbstractDiscreteDistribution.java
@@ -112,7 +112,7 @@ abstract class AbstractDiscreteDistribution
      * @throws IllegalArgumentException if {@code p < 0} or {@code p > 1}
      */
     @Override
-    public int inverseCumulativeProbability(final double p) {
+    public int inverseCumulativeProbability(double p) {
         ArgumentUtils.checkProbability(p);
         return inverseProbability(p, 1 - p, false);
     }
@@ -132,7 +132,7 @@ abstract class AbstractDiscreteDistribution
      * @throws IllegalArgumentException if {@code p < 0} or {@code p > 1}
      */
     @Override
-    public int inverseSurvivalProbability(final double p) {
+    public int inverseSurvivalProbability(double p) {
         ArgumentUtils.checkProbability(p);
         return inverseProbability(1 - p, p, true);
     }
@@ -145,7 +145,7 @@ abstract class AbstractDiscreteDistribution
      * @param complement Set to true to compute the inverse survival probability
      * @return the value
      */
-    private int inverseProbability(final double p, final double q, boolean complement) {
+    private int inverseProbability(double p, double q, boolean complement) {
 
         int lower = getSupportLowerBound();
         if (p == 0) {
diff --git a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/CauchyDistribution.java b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/CauchyDistribution.java
index d9cd109..3a08311 100644
--- a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/CauchyDistribution.java
+++ b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/CauchyDistribution.java
@@ -126,6 +126,23 @@ public final class CauchyDistribution extends AbstractContinuousDistribution {
     /**
      * {@inheritDoc}
      *
+     * <p>Returns {@code Double.NEGATIVE_INFINITY} when {@code p == 1}
+     * and {@code Double.POSITIVE_INFINITY} when {@code p == 0}.
+     */
+    @Override
+    public double inverseSurvivalProbability(double p) {
+        ArgumentUtils.checkProbability(p);
+        if (p == 1) {
+            return Double.NEGATIVE_INFINITY;
+        } else  if (p == 0) {
+            return Double.POSITIVE_INFINITY;
+        }
+        return location - scale * Math.tan(Math.PI * (p - 0.5));
+    }
+
+    /**
+     * {@inheritDoc}
+     *
      * <p>The mean is always undefined no matter the parameters.
      *
      * @return mean (always Double.NaN)
diff --git a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/ChiSquaredDistribution.java b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/ChiSquaredDistribution.java
index 815d5eb..780c304 100644
--- a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/ChiSquaredDistribution.java
+++ b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/ChiSquaredDistribution.java
@@ -98,6 +98,12 @@ public final class ChiSquaredDistribution extends AbstractContinuousDistribution
         return gamma.inverseCumulativeProbability(p);
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public double inverseSurvivalProbability(double p) {
+        return gamma.inverseSurvivalProbability(p);
+    }
+
     /**
      * {@inheritDoc}
      *
diff --git a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/ExponentialDistribution.java b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/ExponentialDistribution.java
index b70a498..58267d5 100644
--- a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/ExponentialDistribution.java
+++ b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/ExponentialDistribution.java
@@ -109,7 +109,7 @@ public final class ExponentialDistribution extends AbstractContinuousDistributio
     /**
      * {@inheritDoc}
      *
-     * <p>Returns {@code 0} when {@code p= = 0} and
+     * <p>Returns {@code 0} when {@code p == 0} and
      * {@code Double.POSITIVE_INFINITY} when {@code p == 1}.
      */
     @Override
@@ -118,7 +118,24 @@ public final class ExponentialDistribution extends AbstractContinuousDistributio
         if (p == 1) {
             return Double.POSITIVE_INFINITY;
         }
-        return -mean * Math.log1p(-p);
+        // Subtract from zero to prevent returning -0.0 for p=-0.0
+        return 0 - mean * Math.log1p(-p);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>Returns {@code 0} when {@code p == 1} and
+     * {@code Double.POSITIVE_INFINITY} when {@code p == 0}.
+     */
+    @Override
+    public double inverseSurvivalProbability(double p) {
+        ArgumentUtils.checkProbability(p);
+        if (p == 0) {
+            return Double.POSITIVE_INFINITY;
+        }
+        // Subtract from zero to prevent returning -0.0 for p=1
+        return 0 - mean * Math.log(p);
     }
 
     /**
diff --git a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/GeometricDistribution.java b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/GeometricDistribution.java
index 9a38dbf..7e001dd 100644
--- a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/GeometricDistribution.java
+++ b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/GeometricDistribution.java
@@ -33,6 +33,9 @@ public final class GeometricDistribution extends AbstractDiscreteDistribution {
     private final double logProbabilityOfSuccess;
     /** {@code log(1 - p)} where p is the probability of success. */
     private final double log1mProbabilityOfSuccess;
+    /** Value of survival probability for x=0.
+     * Used in the survival functions. Equal to (1 - probability of success). */
+    private final double sf0;
     /** Implementation of PMF(x). Assumes that {@code x > 0}. */
     private final IntToDoubleFunction pmf;
 
@@ -43,15 +46,16 @@ public final class GeometricDistribution extends AbstractDiscreteDistribution {
         probabilityOfSuccess = p;
         logProbabilityOfSuccess = Math.log(p);
         log1mProbabilityOfSuccess = Math.log1p(-p);
+        sf0 = 1 - p;
 
         // Choose the PMF implementation.
         // When p >= 0.5 then 1 - p is exact and using the power function
         // is consistently more accurate than the use of the exponential function.
         // When p -> 0 then the exponential function avoids large error propagation
         // of the power function used with an inexact 1 - p.
+        // Also compute the survival probability for use when x=0.
         if (p >= HALF) {
-            final double q = 1 - p;
-            pmf = x -> Math.pow(q, x) * probabilityOfSuccess;
+            pmf = x -> Math.pow(sf0, x) * probabilityOfSuccess;
         } else {
             pmf = x -> Math.exp(log1mProbabilityOfSuccess * x) * probabilityOfSuccess;
         }
@@ -85,7 +89,7 @@ public final class GeometricDistribution extends AbstractDiscreteDistribution {
     public double probability(int x) {
         if (x <= 0) {
             // Special case of x=0 exploiting cancellation.
-            return x == 0 ? probabilityOfSuccess : 0.0;
+            return x == 0 ? probabilityOfSuccess : 0;
         }
         return pmf.applyAsDouble(x);
     }
@@ -103,8 +107,9 @@ public final class GeometricDistribution extends AbstractDiscreteDistribution {
     /** {@inheritDoc} */
     @Override
     public double cumulativeProbability(int x) {
-        if (x < 0) {
-            return 0.0;
+        if (x <= 0) {
+            // Note: CDF(x=0) = PDF(x=0) = probabilityOfSuccess
+            return x == 0 ? probabilityOfSuccess : 0;
         }
         // Note: Double addition avoids overflow. This may compute a value less than 1.0
         // for the max integer value when p is very small.
@@ -114,8 +119,10 @@ public final class GeometricDistribution extends AbstractDiscreteDistribution {
     /** {@inheritDoc} */
     @Override
     public double survivalProbability(int x) {
-        if (x < 0) {
-            return 1.0;
+        if (x <= 0) {
+            // Note: SF(x=0) = 1 - PDF(x=0) = 1 - probabilityOfSuccess
+            // Use a pre-computed value to avoid cancellation when probabilityOfSuccess -> 0
+            return x == 0 ? sf0 : 1;
         }
         // Note: Double addition avoids overflow. This may compute a value greater than 0.0
         // for the max integer value when p is very small.
@@ -129,17 +136,58 @@ public final class GeometricDistribution extends AbstractDiscreteDistribution {
         if (p == 1) {
             return getSupportUpperBound();
         }
-        if (p == 0) {
+        if (p <= probabilityOfSuccess) {
             return 0;
         }
-        final int x = (int) Math.ceil(Math.log1p(-p) / log1mProbabilityOfSuccess - 1);
-        // Note: x may be too high due to floating-point error and rounding up with ceil.
-        // Return the next value down if that is also above the input cumulative probability.
+        // p > probabilityOfSuccess
+        // => log(1-p) < log(1-probabilityOfSuccess);
+        // Both terms are negative as probabilityOfSuccess > 0.
+        // This should be lower bounded to (2 - 1) = 1
+        int x = (int) (Math.ceil(Math.log1p(-p) / log1mProbabilityOfSuccess) - 1);
+
+        // Correct rounding errors.
         // This ensures x == icdf(cdf(x))
-        if (x <= 0) {
+
+        if (cumulativeProbability(x - 1) >= p) {
+            // No checks for x=0.
+            // If x=0; cdf(-1) = 0 and the condition is false as p>0 at this point.
+            x--;
+        } else if (cumulativeProbability(x) < p && x < Integer.MAX_VALUE) {
+            // The supported upper bound is max_value here as probabilityOfSuccess != 1
+            x++;
+        }
+
+        return x;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int inverseSurvivalProbability(double p) {
+        ArgumentUtils.checkProbability(p);
+        if (p == 0) {
+            return getSupportUpperBound();
+        }
+        if (p >= sf0) {
             return 0;
         }
-        return cumulativeProbability(x - 1) >= p ? x - 1 : x;
+
+        // p < 1 - probabilityOfSuccess
+        // Inversion as for icdf using log(p) in place of log1p(-p)
+        int x = (int) (Math.ceil(Math.log(p) / log1mProbabilityOfSuccess) - 1);
+
+        // Correct rounding errors.
+        // This ensures x == isf(sf(x))
+
+        if (survivalProbability(x - 1) <= p) {
+            // No checks for x=0
+            // If x=0; sf(-1) = 1 and the condition is false as p<1 at this point.
+            x--;
+        } else if (survivalProbability(x) > p && x < Integer.MAX_VALUE) {
+            // The supported upper bound is max_value here as probabilityOfSuccess != 1
+            x++;
+        }
+
+        return x;
     }
 
     /**
diff --git a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/GumbelDistribution.java b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/GumbelDistribution.java
index 46eaa81..cc52082 100644
--- a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/GumbelDistribution.java
+++ b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/GumbelDistribution.java
@@ -132,6 +132,18 @@ public final class GumbelDistribution extends AbstractContinuousDistribution {
         return mu - Math.log(-Math.log(p)) * beta;
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public double inverseSurvivalProbability(double p) {
+        ArgumentUtils.checkProbability(p);
+        if (p == 1) {
+            return Double.NEGATIVE_INFINITY;
+        } else if (p == 0) {
+            return Double.POSITIVE_INFINITY;
+        }
+        return mu - Math.log(-Math.log1p(-p)) * beta;
+    }
+
     /**
      * {@inheritDoc}
      *
diff --git a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/LaplaceDistribution.java b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/LaplaceDistribution.java
index 1a8e511..de8255b 100644
--- a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/LaplaceDistribution.java
+++ b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/LaplaceDistribution.java
@@ -117,6 +117,20 @@ public final class LaplaceDistribution extends AbstractContinuousDistribution {
         return mu + beta * x;
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public double inverseSurvivalProbability(double p) {
+        ArgumentUtils.checkProbability(p);
+        if (p == 1) {
+            return Double.NEGATIVE_INFINITY;
+        } else if (p == 0) {
+            return Double.POSITIVE_INFINITY;
+        }
+        // By symmetry: x = -icdf(p); then transform back by the scale and location
+        final double x = (p > 0.5) ? Math.log(2.0 * (1.0 - p)) : -Math.log(2.0 * p);
+        return mu + beta * x;
+    }
+
     /**
      * {@inheritDoc}
      *
diff --git a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/LevyDistribution.java b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/LevyDistribution.java
index f43a141..2125816 100644
--- a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/LevyDistribution.java
+++ b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/LevyDistribution.java
@@ -18,6 +18,7 @@ package org.apache.commons.statistics.distribution;
 
 import org.apache.commons.numbers.gamma.Erf;
 import org.apache.commons.numbers.gamma.Erfc;
+import org.apache.commons.numbers.gamma.InverseErf;
 import org.apache.commons.numbers.gamma.InverseErfc;
 import org.apache.commons.rng.UniformRandomProvider;
 import org.apache.commons.rng.sampling.distribution.LevySampler;
@@ -153,12 +154,20 @@ public final class LevyDistribution extends AbstractContinuousDistribution {
 
     /** {@inheritDoc} */
     @Override
-    public double inverseCumulativeProbability(final double p) {
+    public double inverseCumulativeProbability(double p) {
         ArgumentUtils.checkProbability(p);
         final double t = InverseErfc.value(p);
         return mu + halfC / (t * t);
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public double inverseSurvivalProbability(double p) {
+        ArgumentUtils.checkProbability(p);
+        final double t = InverseErf.value(p);
+        return mu + halfC / (t * t);
+    }
+
     /**
      * {@inheritDoc}
      *
diff --git a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/LogNormalDistribution.java b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/LogNormalDistribution.java
index bd17627..ca22b02 100644
--- a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/LogNormalDistribution.java
+++ b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/LogNormalDistribution.java
@@ -182,9 +182,6 @@ public final class LogNormalDistribution extends AbstractContinuousDistribution
             return 0;
         }
         final double dev = Math.log(x) - mu;
-        if (Math.abs(dev) > 40 * sigma) {
-            return dev < 0 ? 0.0d : 1.0d;
-        }
         return 0.5 * Erfc.value(-dev / sigmaSqrt2);
     }
 
@@ -195,19 +192,23 @@ public final class LogNormalDistribution extends AbstractContinuousDistribution
             return 1;
         }
         final double dev = Math.log(x) - mu;
-        if (Math.abs(dev) > 40 * sigma) {
-            return dev > 0 ? 0.0d : 1.0d;
-        }
         return 0.5 * Erfc.value(dev / sigmaSqrt2);
     }
 
     /** {@inheritDoc} */
     @Override
-    public double inverseCumulativeProbability(final double p) {
+    public double inverseCumulativeProbability(double p) {
         ArgumentUtils.checkProbability(p);
         return Math.exp(mu - sigmaSqrt2 * InverseErfc.value(2 * p));
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public double inverseSurvivalProbability(double p) {
+        ArgumentUtils.checkProbability(p);
+        return Math.exp(mu + sigmaSqrt2 * InverseErfc.value(2 * p));
+    }
+
     /**
      * {@inheritDoc}
      *
diff --git a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/LogisticDistribution.java b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/LogisticDistribution.java
index 24635e9..09ce61c 100644
--- a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/LogisticDistribution.java
+++ b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/LogisticDistribution.java
@@ -140,6 +140,19 @@ public final class LogisticDistribution extends AbstractContinuousDistribution {
         }
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public double inverseSurvivalProbability(double p) {
+        ArgumentUtils.checkProbability(p);
+        if (p == 1) {
+            return SUPPORT_LO;
+        } else if (p == 0) {
+            return SUPPORT_HI;
+        } else {
+            return scale * -Math.log(p / (1 - p)) + mu;
+        }
+    }
+
     /**
      * {@inheritDoc}
      *
diff --git a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/NormalDistribution.java b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/NormalDistribution.java
index fd8610e..730157f 100644
--- a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/NormalDistribution.java
+++ b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/NormalDistribution.java
@@ -133,13 +133,20 @@ public final class NormalDistribution extends AbstractContinuousDistribution {
 
     /** {@inheritDoc} */
     @Override
-    public double inverseCumulativeProbability(final double p) {
+    public double inverseCumulativeProbability(double p) {
         ArgumentUtils.checkProbability(p);
         return mean - sdSqrt2 * InverseErfc.value(2 * p);
     }
 
     /** {@inheritDoc} */
     @Override
+    public double inverseSurvivalProbability(double p) {
+        ArgumentUtils.checkProbability(p);
+        return mean + sdSqrt2 * InverseErfc.value(2 * p);
+    }
+
+    /** {@inheritDoc} */
+    @Override
     public double getMean() {
         return mean;
     }
diff --git a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/ParetoDistribution.java b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/ParetoDistribution.java
index 7065089..5462cb1 100644
--- a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/ParetoDistribution.java
+++ b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/ParetoDistribution.java
@@ -204,6 +204,19 @@ public final class ParetoDistribution extends AbstractContinuousDistribution {
         return scale / Math.exp(Math.log1p(-p) / shape);
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public double inverseSurvivalProbability(double p) {
+        ArgumentUtils.checkProbability(p);
+        if (p == 1) {
+            return getSupportLowerBound();
+        }
+        if (p == 0) {
+            return getSupportUpperBound();
+        }
+        return scale / Math.pow(p, 1 / shape);
+    }
+
     /**
      * {@inheritDoc}
      * <p>
diff --git a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/TDistribution.java b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/TDistribution.java
index 6985dca..e3e7ed5 100644
--- a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/TDistribution.java
+++ b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/TDistribution.java
@@ -72,6 +72,8 @@ public abstract class TDistribution extends AbstractContinuousDistribution {
             return STANDARD_NORMAL.inverseCumulativeProbability(p);
         }
 
+        // Survival probability functions inherit the symmetry operations from the TDistribution
+
         @Override
         public double getMean() {
             return 0;
@@ -271,6 +273,13 @@ public abstract class TDistribution extends AbstractContinuousDistribution {
         return cumulativeProbability(-x);
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public double inverseSurvivalProbability(double p) {
+        // Exploit symmetry
+        return -inverseCumulativeProbability(p);
+    }
+
     /**
      * {@inheritDoc}
      *
diff --git a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/TriangularDistribution.java b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/TriangularDistribution.java
index 4c3f834..f5b7b8a 100644
--- a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/TriangularDistribution.java
+++ b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/TriangularDistribution.java
@@ -36,6 +36,8 @@ public final class TriangularDistribution extends AbstractContinuousDistribution
     private final double divisor2;
     /** Cumulative probability at the mode. */
     private final double cdfMode;
+    /** Survival probability at the mode. */
+    private final double sfMode;
 
     /**
      * @param a Lower limit of this distribution (inclusive).
@@ -51,6 +53,7 @@ public final class TriangularDistribution extends AbstractContinuousDistribution
         divisor1 = (b - a) * (c - a);
         divisor2 = (b - a) * (b - c);
         cdfMode = (c - a) / (b - a);
+        sfMode = (b - c) / (b - a);
     }
 
     /**
@@ -136,7 +139,7 @@ public final class TriangularDistribution extends AbstractContinuousDistribution
      */
     @Override
     public double cumulativeProbability(double x)  {
-        if (x < a) {
+        if (x <= a) {
             return 0;
         }
         if (x < c) {
@@ -146,13 +149,35 @@ public final class TriangularDistribution extends AbstractContinuousDistribution
         if (x == c) {
             return cdfMode;
         }
-        if (x <= b) {
+        if (x < b) {
             final double divident = (b - x) * (b - x);
             return 1 - (divident / divisor2);
         }
         return 1;
     }
 
+
+    /** {@inheritDoc} */
+    @Override
+    public double survivalProbability(double x)  {
+        // By symmetry:
+        if (x <= a) {
+            return 1;
+        }
+        if (x < c) {
+            final double divident = (x - a) * (x - a);
+            return 1 - (divident / divisor1);
+        }
+        if (x == c) {
+            return sfMode;
+        }
+        if (x < b) {
+            final double divident = (b - x) * (b - x);
+            return divident / divisor2;
+        }
+        return 0;
+    }
+
     /** {@inheritDoc} */
     @Override
     public double inverseCumulativeProbability(double p) {
@@ -169,6 +194,23 @@ public final class TriangularDistribution extends AbstractContinuousDistribution
         return b - Math.sqrt((1 - p) * divisor2);
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public double inverseSurvivalProbability(double p) {
+        // By symmetry:
+        ArgumentUtils.checkProbability(p);
+        if (p == 1) {
+            return a;
+        }
+        if (p == 0) {
+            return b;
+        }
+        if (p >= sfMode) {
+            return a + Math.sqrt((1 - p) * divisor1);
+        }
+        return b - Math.sqrt(p * divisor2);
+    }
+
     /**
      * {@inheritDoc}
      *
diff --git a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/TruncatedNormalDistribution.java b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/TruncatedNormalDistribution.java
index a921b81..45066fc 100644
--- a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/TruncatedNormalDistribution.java
+++ b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/TruncatedNormalDistribution.java
@@ -47,6 +47,9 @@ public final class TruncatedNormalDistribution extends AbstractContinuousDistrib
     /** Stored value of {@code parentNormal.cumulativeProbability(lower)}. Used to map
      * a probability into the range of the parent normal distribution. */
     private final double cdfAlpha;
+    /** Stored value of {@code parentNormal.survivalProbability(upper)}. Used to map
+     * a probability into the range of the parent normal distribution. */
+    private final double sfBeta;
 
     /**
      * @param mean Mean for the parent distribution.
@@ -64,8 +67,9 @@ public final class TruncatedNormalDistribution extends AbstractContinuousDistrib
 
         cdfDelta = parentNormal.probability(lower, upper);
         logCdfDelta = Math.log(cdfDelta);
-        // Used to map the inverseCumulativeProbability
+        // Used to map the inverse probability.
         cdfAlpha = parentNormal.cumulativeProbability(lower);
+        sfBeta = parentNormal.survivalProbability(upper);
 
         // Calculation of variance and mean.
         //
@@ -205,6 +209,21 @@ public final class TruncatedNormalDistribution extends AbstractContinuousDistrib
         return clipToRange(x);
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public double inverseSurvivalProbability(double p) {
+        ArgumentUtils.checkProbability(p);
+        // Exact bound
+        if (p == 1) {
+            return lower;
+        } else if (p == 0) {
+            return upper;
+        }
+        // Linearly map p to the range [lower, upper]
+        final double x = parentNormal.inverseSurvivalProbability(sfBeta + p * cdfDelta);
+        return clipToRange(x);
+    }
+
     /**
      * {@inheritDoc}
      *
diff --git a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/UniformContinuousDistribution.java b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/UniformContinuousDistribution.java
index af64f48..48145de 100644
--- a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/UniformContinuousDistribution.java
+++ b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/UniformContinuousDistribution.java
@@ -111,12 +111,20 @@ public final class UniformContinuousDistribution extends AbstractContinuousDistr
 
     /** {@inheritDoc} */
     @Override
-    public double inverseCumulativeProbability(final double p) {
+    public double inverseCumulativeProbability(double p) {
         ArgumentUtils.checkProbability(p);
         // Avoid floating-point error for lower + p * (upper - lower) when p == 1.
         return p == 1 ? upper : p * upperMinusLower + lower;
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public double inverseSurvivalProbability(double p) {
+        ArgumentUtils.checkProbability(p);
+        // Avoid floating-point error for upper - p * (upper - lower) when p == 1.
+        return p == 1 ? lower : upper - p * upperMinusLower;
+    }
+
     /**
      * {@inheritDoc}
      *
diff --git a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/UniformDiscreteDistribution.java b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/UniformDiscreteDistribution.java
index 37a868e..4841dd9 100644
--- a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/UniformDiscreteDistribution.java
+++ b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/UniformDiscreteDistribution.java
@@ -35,6 +35,8 @@ public final class UniformDiscreteDistribution extends AbstractDiscreteDistribut
     private final double pmf;
     /** Cache of the log probability. */
     private final double logPmf;
+    /** Value of survival probability for x=0. Used in the inverse survival function. */
+    private final double sf0;
 
     /**
      * @param lower Lower bound (inclusive) of this distribution.
@@ -47,6 +49,7 @@ public final class UniformDiscreteDistribution extends AbstractDiscreteDistribut
         upperMinusLowerPlus1 = (double) upper - (double) lower + 1;
         pmf = 1.0 / upperMinusLowerPlus1;
         logPmf = -Math.log(upperMinusLowerPlus1);
+        sf0 = (upperMinusLowerPlus1 - 1) / upperMinusLowerPlus1;
     }
 
     /**
@@ -88,10 +91,11 @@ public final class UniformDiscreteDistribution extends AbstractDiscreteDistribut
     /** {@inheritDoc} */
     @Override
     public double cumulativeProbability(int x) {
-        if (x < lower) {
-            return 0;
+        if (x <= lower) {
+            // Note: CDF(x=0) = PDF(x=0)
+            return x == lower ? pmf : 0;
         }
-        if (x > upper) {
+        if (x >= upper) {
             return 1;
         }
         return ((double) x - lower + 1) / upperMinusLowerPlus1;
@@ -100,10 +104,12 @@ public final class UniformDiscreteDistribution extends AbstractDiscreteDistribut
     /** {@inheritDoc} */
     @Override
     public double survivalProbability(int x) {
-        if (x < lower) {
-            return 1;
+        if (x <= lower) {
+            // Note: SF(x=0) = 1 - PDF(x=0)
+            // Use a pre-computed value to avoid cancellation when probabilityOfSuccess -> 0
+            return x == lower ? sf0 : 1;
         }
-        if (x > upper) {
+        if (x >= upper) {
             return 0;
         }
         return ((double) upper - x) / upperMinusLowerPlus1;
@@ -111,17 +117,66 @@ public final class UniformDiscreteDistribution extends AbstractDiscreteDistribut
 
     /** {@inheritDoc} */
     @Override
-    public int inverseCumulativeProbability(final double p) {
+    public int inverseCumulativeProbability(double p) {
         ArgumentUtils.checkProbability(p);
-        // Casting will clip overflows to int min or max value
-        final int x = (int) (Math.ceil(p * upperMinusLowerPlus1 + lower - 1));
-        // Note: x may be too high due to floating-point error and rounding up with ceil.
-        // Return the next value down if that is also above the input cumulative probability.
+        if (p > sf0) {
+            return upper;
+        }
+        if (p <= pmf) {
+            return lower;
+        }
+        // p in ( pmf         , sf0             ]
+        // p in ( 1 / {u-l+1} , {u-l} / {u-l+1} ]
+        // x in ( l           , u-1             ]
+        int x = (int) (lower + Math.ceil(p * upperMinusLowerPlus1) - 1);
+
+        // Correct rounding errors.
         // This ensures x == icdf(cdf(x))
-        if (x <= lower) {
+        // Note: Directly computing the CDF(x-1) avoids integer overflow if x=min_value
+
+        if (((double) x - lower) / upperMinusLowerPlus1 >= p) {
+            // No check for x > lower: cdf(x=lower) = 0 and thus is below p
+            // cdf(x-1) >= p
+            x--;
+        } else if (((double) x - lower + 1) / upperMinusLowerPlus1 < p) {
+            // No check for x < upper: cdf(x=upper) = 1 and thus is above p
+            // cdf(x) < p
+            x++;
+        }
+
+        return x;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int inverseSurvivalProbability(final double p) {
+        ArgumentUtils.checkProbability(p);
+        if (p < pmf) {
+            return upper;
+        }
+        if (p >= sf0) {
             return lower;
         }
-        return cumulativeProbability(x - 1) >= p ? x - 1 : x;
+        // p in [ pmf         , sf0             )
+        // p in [ 1 / {u-l+1} , {u-l} / {u-l+1} )
+        // x in [ u-1         , l               )
+        int x = (int) (upper - Math.floor(p * upperMinusLowerPlus1));
+
+        // Correct rounding errors.
+        // This ensures x == isf(sf(x))
+        // Note: Directly computing the SF(x-1) avoids integer overflow if x=min_value
+
+        if (((double) upper - x + 1) / upperMinusLowerPlus1 <= p) {
+            // No check for x > lower: sf(x=lower) = 1 and thus is above p
+            // sf(x-1) <= p
+            x--;
+        } else if (((double) upper - x) / upperMinusLowerPlus1 > p) {
+            // No check for x < upper: sf(x=upper) = 0 and thus is below p
+            // sf(x) > p
+            x++;
+        }
+
+        return x;
     }
 
     /**
diff --git a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/WeibullDistribution.java b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/WeibullDistribution.java
index b443b3e..1b55cf4 100644
--- a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/WeibullDistribution.java
+++ b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/WeibullDistribution.java
@@ -212,6 +212,23 @@ public final class WeibullDistribution extends AbstractContinuousDistribution {
     /**
      * {@inheritDoc}
      *
+     * <p>Returns {@code 0} when {@code p == 1} and
+     * {@code Double.POSITIVE_INFINITY} when {@code p == 0}.
+     */
+    @Override
+    public double inverseSurvivalProbability(double p) {
+        ArgumentUtils.checkProbability(p);
+        if (p == 1) {
+            return 0.0;
+        } else  if (p == 0) {
+            return Double.POSITIVE_INFINITY;
+        }
+        return scale * Math.pow(-Math.log(p), 1.0 / shape);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
      * <p>The mean is {@code scale * Gamma(1 + (1 / shape))}, where {@code Gamma()}
      * is the Gamma-function.
      */
diff --git a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/BaseContinuousDistributionTest.java b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/BaseContinuousDistributionTest.java
index 40e8f83..f5a1b44 100644
--- a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/BaseContinuousDistributionTest.java
+++ b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/BaseContinuousDistributionTest.java
@@ -21,9 +21,7 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.Properties;
 import java.util.function.Function;
-import java.util.function.Predicate;
 import java.util.stream.Stream;
-import java.util.stream.Stream.Builder;
 import org.apache.commons.math3.analysis.UnivariateFunction;
 import org.apache.commons.math3.analysis.integration.BaseAbstractUnivariateIntegrator;
 import org.apache.commons.math3.analysis.integration.IterativeLegendreGaussIntegrator;
@@ -31,7 +29,6 @@ import org.apache.commons.math3.util.MathArrays;
 import org.apache.commons.rng.simple.RandomSource;
 import org.apache.commons.statistics.distribution.DistributionTestData.ContinuousDistributionTestData;
 import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.Assumptions;
 import org.junit.jupiter.api.TestInstance;
 import org.junit.jupiter.api.TestInstance.Lifecycle;
 import org.junit.jupiter.params.ParameterizedTest;
@@ -91,12 +88,14 @@ import org.junit.jupiter.params.provider.MethodSource;
  * <li>Points for the PDF (and log PDF) can be specified. The default will use the CDF points.
  * Note: It is not expected that evaluation of the PDF will require different points to the CDF.
  * <li>Points and expected values for the inverse CDF can be specified. These are used in
- * addition to a test of the inverse mapping of the CDF values to the CDF test points. The
+ * addition to test the inverse mapping of the CDF values to the CDF test points. The
  * inverse mapping test can be disabled.
  * <li>Expected values for the log PDF can be specified. The default will use
  * {@link Math#log(double)} on the PDF values.
- * <li>Points and expected values for the survival function can be specified. The default will use
- * the expected CDF values (SF = 1 - CDF).
+ * <li>Points and expected values for the survival function can be specified. These are used in
+ * addition to test the inverse mapping of the SF values to the SF test points. The
+ * inverse mapping test can be disabled.
+ * The default will use the expected CDF values (SF = 1 - CDF).
  * <li>A tolerance for equality assertions. The default is set by {@link #getAbsoluteTolerance()}
  * and {@link #getRelativeTolerance()}.
  * <li>A flag to indicate the returned value for {@link ContinuousDistribution#isSupportConnected()}.
@@ -176,19 +175,24 @@ import org.junit.jupiter.params.provider.MethodSource;
  * # optional (default uses log pdf.values)
  * logpdf.values = -1900.123, -Infinity
  * # optional (default uses cdf.points and 1 - cdf.values)
- * sf.points = 400
+ * sf.points = 400.0
  * sf.values = 0.0
  * # optional high-precision CDF test
  * cdf.hp.points = 1e-16
  * cdf.hp.values = 1.23e-17
  * # optional high-precision survival function test
- * sf.hp.points = 9
+ * sf.hp.points = 9.0
  * sf.hp.values = 2.34e-18
  * # optional inverse CDF test (defaults to ignore)
- * icdf.values = 0.0, 0.5
- * ipdf.values = 0.0, 0.2
+ * icdf.points = 0.0, 0.5
+ * icdf.values = 0.0, 0.2
+ * # optional inverse CDF test (defaults to ignore)
+ * isf.points = 1.0, 0.5
+ * isf.values = 0.0, 0.2
  * # CDF inverse mapping test (default false)
  * disable.cdf.inverse = false
+ * # SF inverse mapping test (default false)
+ * disable.sf.inverse = false
  * # Sampling test (default false)
  * disable.sample = false
  * # PDF values test (default false)
@@ -230,31 +234,8 @@ abstract class BaseContinuousDistributionTest
      * @return the stream
      */
     Stream<Arguments> streamCdfTestPoints() {
-        return streamCdfTestPoints(d -> false);
-    }
-
-    /**
-     * Create a stream of arguments containing the distribution to test, the CDF
-     * test points and the test tolerance.
-     *
-     * @param filter Filter applied on the test data. If true the data is ignored.
-     * @return the stream
-     */
-    Stream<Arguments> streamCdfTestPoints(Predicate<ContinuousDistributionTestData> filter) {
-        final Builder<Arguments> b = Stream.builder();
-        final int[] size = {0};
-        data.forEach(d -> {
-            final double[] p = d.getCdfPoints();
-            if (filter.test(d) || TestUtils.getLength(p) == 0) {
-                return;
-            }
-            size[0]++;
-            b.accept(Arguments.of(namedDistribution(d.getParameters()),
-                     namedArray("points", p),
-                     createTestTolerance(d)));
-        });
-        Assumptions.assumeTrue(size[0] != 0, () -> "Distribution has no data for cdf test points");
-        return b.build();
+        return streamPoints(ContinuousDistributionTestData::getCdfPoints,
+                            this::createTestTolerance, "cdf test points");
     }
 
     /**
@@ -348,6 +329,18 @@ abstract class BaseContinuousDistributionTest
     }
 
     /**
+     * Create a stream of arguments containing the distribution to test, the inverse SF test points
+     * and values, and the test tolerance.
+     *
+     * @return the stream
+     */
+    Stream<Arguments> testInverseSurvivalProbability() {
+        return stream(ContinuousDistributionTestData::getIsfPoints,
+                      ContinuousDistributionTestData::getIsfValues,
+                      this::createTestTolerance, "isf");
+    }
+
+    /**
      * Create a stream of arguments containing the distribution to test, the test points
      * to evaluate the CDF, and the test tolerance. The equality
      * {@code cdf(x) = cdf(icdf(cdf(x)))} must be true within the tolerance.
@@ -355,7 +348,22 @@ abstract class BaseContinuousDistributionTest
      * @return the stream
      */
     Stream<Arguments> testCumulativeProbabilityInverseMapping() {
-        return streamCdfTestPoints(ContinuousDistributionTestData::isDisableCdfInverse);
+        return streamPoints(ContinuousDistributionTestData::isDisableCdfInverse,
+                            ContinuousDistributionTestData::getCdfPoints,
+                            this::createTestTolerance, "cdf test points");
+    }
+
+    /**
+     * Create a stream of arguments containing the distribution to test, the test points
+     * to evaluate the SF, and the test tolerance. The equality
+     * {@code sf(x) = sf(isf(sf(x)))} must be true within the tolerance.
+     *
+     * @return the stream
+     */
+    Stream<Arguments> testSurvivalProbabilityInverseMapping() {
+        return streamPoints(ContinuousDistributionTestData::isDisableSfInverse,
+                            ContinuousDistributionTestData::getSfPoints,
+                            this::createTestTolerance, "sf test points");
     }
 
     /**
@@ -582,6 +590,33 @@ abstract class BaseContinuousDistributionTest
     }
 
     /**
+     * Test that inverse survival probability density calculations match expected values.
+     *
+     * <p>Note: Any expected values outside the support of the distribution are ignored.
+     */
+    @ParameterizedTest
+    @MethodSource
+    final void testInverseSurvivalProbability(ContinuousDistribution dist,
+                                              double[] points,
+                                              double[] values,
+                                              DoubleTolerance tolerance) {
+        final double lower = dist.getSupportLowerBound();
+        final double upper = dist.getSupportUpperBound();
+        for (int i = 0; i < points.length; i++) {
+            final double x = values[i];
+            if (x < lower || x > upper) {
+                continue;
+            }
+            final double p = points[i];
+            TestUtils.assertEquals(
+                x,
+                dist.inverseSurvivalProbability(p),
+                tolerance,
+                () -> "Incorrect inverse survival probability value returned for " + p);
+        }
+    }
+
+    /**
      * Test that an inverse mapping of the cumulative probability density values matches
      * the original point, {@code x = icdf(cdf(x))}.
      *
@@ -617,6 +652,41 @@ abstract class BaseContinuousDistributionTest
     }
 
     /**
+     * Test that an inverse mapping of the survival probability density values matches
+     * the original point, {@code x = isf(sf(x))}.
+     *
+     * <p>Note: It is possible for two points to compute the same SF value. In this
+     * case the mapping is not a bijection. Thus a further forward mapping is performed
+     * to check {@code sf(x) = sf(isf(sf(x)))} within the allowed tolerance.
+     *
+     * <p>Note: Any points outside the support of the distribution are ignored.
+     */
+    @ParameterizedTest
+    @MethodSource
+    final void testSurvivalProbabilityInverseMapping(ContinuousDistribution dist,
+                                                     double[] points,
+                                                     DoubleTolerance tolerance) {
+        final double lower = dist.getSupportLowerBound();
+        final double upper = dist.getSupportUpperBound();
+        for (int i = 0; i < points.length; i++) {
+            final double x = points[i];
+            if (x < lower || x > upper) {
+                continue;
+            }
+            final double p = dist.survivalProbability(x);
+            final double x1 = dist.inverseSurvivalProbability(p);
+            final double p1 = dist.survivalProbability(x1);
+            // Check the inverse SF computed a value that will return to the
+            // same probability value.
+            TestUtils.assertEquals(
+                p,
+                p1,
+                tolerance,
+                () -> "Incorrect SF(inverse SF(SF(x))) value returned for " + x);
+        }
+    }
+
+    /**
      * Test that cumulative probability density and survival probability calculations
      * sum to approximately 1.0.
      */
@@ -692,9 +762,11 @@ abstract class BaseContinuousDistributionTest
         final double lo = dist.getSupportLowerBound();
         Assertions.assertEquals(0.0, dist.cumulativeProbability(lo), "cdf(lower)");
         Assertions.assertEquals(lo, dist.inverseCumulativeProbability(0.0), "icdf(0.0)");
+        Assertions.assertEquals(lo, dist.inverseSurvivalProbability(1.0), "isf(1.0)");
         // Test for rounding errors during inversion
         Assertions.assertTrue(lo <= dist.inverseCumulativeProbability(Double.MIN_VALUE), "lo <= icdf(min)");
         Assertions.assertTrue(lo <= dist.inverseCumulativeProbability(Double.MIN_NORMAL), "lo <= icdf(min_normal)");
+        Assertions.assertTrue(lo <= dist.inverseSurvivalProbability(Math.nextDown(1.0)), "lo <= isf(nextDown(1.0))");
 
         final double below = Math.nextDown(lo);
         Assertions.assertEquals(0.0, dist.density(below), "pdf(x < lower)");
@@ -707,8 +779,11 @@ abstract class BaseContinuousDistributionTest
         Assertions.assertEquals(1.0, dist.cumulativeProbability(hi), "cdf(upper)");
         Assertions.assertEquals(0.0, dist.survivalProbability(hi), "sf(upper)");
         Assertions.assertEquals(hi, dist.inverseCumulativeProbability(1.0), "icdf(1.0)");
+        Assertions.assertEquals(hi, dist.inverseSurvivalProbability(0.0), "isf(0.0)");
         // Test for rounding errors during inversion
         Assertions.assertTrue(hi >= dist.inverseCumulativeProbability(Math.nextDown(1.0)), "hi >= icdf(nextDown(1.0))");
+        Assertions.assertTrue(hi >= dist.inverseSurvivalProbability(Double.MIN_VALUE), "lo <= isf(min)");
+        Assertions.assertTrue(hi >= dist.inverseSurvivalProbability(Double.MIN_NORMAL), "lo <= isf(min_normal)");
 
         final double above = Math.nextUp(hi);
         Assertions.assertEquals(0.0, dist.density(above), "pdf(x > upper)");
@@ -731,6 +806,8 @@ abstract class BaseContinuousDistributionTest
         }
         Assertions.assertThrows(DistributionException.class, () -> dist.inverseCumulativeProbability(-1), "p < 0.0");
         Assertions.assertThrows(DistributionException.class, () -> dist.inverseCumulativeProbability(2), "p > 1.0");
+        Assertions.assertThrows(DistributionException.class, () -> dist.inverseSurvivalProbability(-1), "q < 0.0");
+        Assertions.assertThrows(DistributionException.class, () -> dist.inverseSurvivalProbability(2), "q > 1.0");
     }
 
     /**
diff --git a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/BaseDiscreteDistributionTest.java b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/BaseDiscreteDistributionTest.java
index db1e3e5..a1bce72 100644
--- a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/BaseDiscreteDistributionTest.java
+++ b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/BaseDiscreteDistributionTest.java
@@ -23,7 +23,6 @@ import java.util.Properties;
 import java.util.function.Function;
 import java.util.stream.IntStream;
 import java.util.stream.Stream;
-import java.util.stream.Stream.Builder;
 import org.apache.commons.math3.util.MathArrays;
 import org.apache.commons.rng.simple.RandomSource;
 import org.apache.commons.statistics.distribution.DistributionTestData.DiscreteDistributionTestData;
@@ -88,12 +87,14 @@ import org.junit.jupiter.params.provider.MethodSource;
  * <li>Points for the PMF (and log PMF) can be specified. The default will use the CDF points.
  * Note: It is not expected that evaluation of the PMF will require different points to the CDF.
  * <li>Points and expected values for the inverse CDF can be specified. These are used in
- * addition to a test of the inverse mapping of the CDF values to the CDF test points. The
+ * addition to test the inverse mapping of the CDF values to the CDF test points. The
  * inverse mapping test can be disabled.
  * <li>Expected values for the log PMF can be specified. The default will use
  * {@link Math#log(double)} on the PMF values.
- * <li>Points and expected values for the survival function can be specified. The default will use
- * the expected CDF values (SF = 1 - CDF).
+ * <li>Points and expected values for the survival function can be specified. These are used in
+ * addition to test the inverse mapping of the SF values to the SF test points. The
+ * inverse mapping test can be disabled.
+ * The default will use the expected CDF values (SF = 1 - CDF).
  * <li>A tolerance for equality assertions. The default is set by {@link #getAbsoluteTolerance()}
  * and {@link #getRelativeTolerance()}.
  * <li>A flag to indicate the returned value for {@link DiscreteDistribution#isSupportConnected()}.
@@ -165,7 +166,7 @@ import org.junit.jupiter.params.provider.MethodSource;
  * tolerance.relative.hp = 1e-10
  * # optional (default 0.0 or over-ridden in getHighPrecisionAbsoluteTolerance())
  * tolerance.absolute.hp = 1e-30
- * cdf.points = 0, 0.2
+ * cdf.points = 0, 2
  * cdf.values = 0.0, 0.5
  * # optional (default uses cdf.points)
  * pmf.points = 0, 40000
@@ -183,10 +184,15 @@ import org.junit.jupiter.params.provider.MethodSource;
  * sf.hp.points = 9
  * sf.hp.values = 2.34e-18
  * # optional inverse CDF test (defaults to ignore)
- * icdf.values = 0.0, 0.5
- * ipmf.values = 0.0, 0.2
+ * icdf.points = 0.0, 0.5
+ * icdf.values = 3, 4
+ * # optional inverse CDF test (defaults to ignore)
+ * isf.points = 1.0, 0.5
+ * isf.values = 3, 4
  * # CDF inverse mapping test (default false)
  * disable.cdf.inverse = false
+ * # SF inverse mapping test (default false)
+ * disable.sf.inverse = false
  * # Sampling test (default false)
  * disable.sample = false
  * # PMF values test (default false)
@@ -228,20 +234,8 @@ abstract class BaseDiscreteDistributionTest
      * @return the stream
      */
     Stream<Arguments> streamCdfTestPoints() {
-        final Builder<Arguments> b = Stream.builder();
-        final int[] size = {0};
-        data.forEach(d -> {
-            final int[] p = d.getCdfPoints();
-            if (TestUtils.getLength(p) == 0) {
-                return;
-            }
-            size[0]++;
-            b.accept(Arguments.of(namedDistribution(d.getParameters()),
-                    namedArray("points", p),
-                    createTestTolerance(d)));
-        });
-        Assumptions.assumeTrue(size[0] != 0, () -> "Distribution has no data for test points");
-        return b.build();
+        return streamPoints(DiscreteDistributionTestData::getCdfPoints,
+                            this::createTestTolerance, "cdf test points");
     }
 
     /**
@@ -324,7 +318,7 @@ abstract class BaseDiscreteDistributionTest
 
     /**
      * Create a stream of arguments containing the distribution to test, the inverse CDF test points
-     * and values, and the test tolerance.
+     * and values. No test tolerance is required as the values are integers and must be exact.
      *
      * @return the stream
      */
@@ -334,24 +328,36 @@ abstract class BaseDiscreteDistributionTest
     }
 
     /**
+     * Create a stream of arguments containing the distribution to test, the inverse CDF test points
+     * and values. No test tolerance is required as the values are integers and must be exact.
+     *
+     * @return the stream
+     */
+    Stream<Arguments> testInverseSurvivalProbability() {
+        return stream(DiscreteDistributionTestData::getIsfPoints,
+                      DiscreteDistributionTestData::getIsfValues, "isf");
+    }
+
+    /**
      * Create a stream of arguments containing the distribution to test and the CDF test points.
      *
      * @return the stream
      */
     Stream<Arguments> testCumulativeProbabilityInverseMapping() {
-        final Builder<Arguments> b = Stream.builder();
-        final int[] size = {0};
-        data.forEach(d -> {
-            final int[] p = d.getCdfPoints();
-            if (d.isDisableCdfInverse() || TestUtils.getLength(p) == 0) {
-                return;
-            }
-            size[0]++;
-            b.accept(Arguments.of(namedDistribution(d.getParameters()),
-                     namedArray("points", p)));
-        });
-        Assumptions.assumeTrue(size[0] != 0, () -> "Distribution has no data for cdf test points");
-        return b.build();
+        return stream(DiscreteDistributionTestData::isDisableCdfInverse,
+                      DiscreteDistributionTestData::getCdfPoints,
+                      "cdf test points");
+    }
+
+    /**
+     * Create a stream of arguments containing the distribution to test and the SF test points.
+     *
+     * @return the stream
+     */
+    Stream<Arguments> testSurvivalProbabilityInverseMapping() {
+        return stream(DiscreteDistributionTestData::isDisableSfInverse,
+                      DiscreteDistributionTestData::getSfPoints,
+                      "sf test points");
     }
 
     /**
@@ -591,7 +597,7 @@ abstract class BaseDiscreteDistributionTest
         final int lower = dist.getSupportLowerBound();
         final int upper = dist.getSupportUpperBound();
         for (int i = 0; i < points.length; i++) {
-            final double x = values[i];
+            final int x = values[i];
             if (x < lower || x > upper) {
                 continue;
             }
@@ -604,14 +610,56 @@ abstract class BaseDiscreteDistributionTest
     }
 
     /**
+     * Test that inverse survival probability density calculations match expected values.
+     *
+     * <p>Note: Any expected values outside the support of the distribution are ignored.
+     */
+    @ParameterizedTest
+    @MethodSource
+    final void testInverseSurvivalProbability(DiscreteDistribution dist,
+                                              double[] points,
+                                              int[] values) {
+        final int lower = dist.getSupportLowerBound();
+        final int upper = dist.getSupportUpperBound();
+        for (int i = 0; i < points.length; i++) {
+            final int x = values[i];
+            if (x < lower || x > upper) {
+                continue;
+            }
+            final double p = points[i];
+            Assertions.assertEquals(
+                x,
+                dist.inverseSurvivalProbability(p),
+                () -> "Incorrect inverse survival probability value returned for " + p);
+        }
+    }
+
+    /**
      * Test that an inverse mapping of the cumulative probability density values matches
      * the original point, {@code x = icdf(cdf(x))}.
      *
      * <p>Note: It is possible for two points to compute the same CDF value. In this
-     * case the mapping is not a bijection. Any points computing a CDF=1 are ignored
+     * case the mapping is not a bijection. Any points computing a CDF=0 or 1 are ignored
      * as this is expected to be inverted to the domain bound.
      *
      * <p>Note: Any points outside the support of the distribution are ignored.
+     *
+     * <p>This test checks consistency of the inverse with the forward function.
+     * The test checks (where applicable):
+     * <ul>
+     *  <li>{@code icdf( cdf(x) ) = x}
+     *  <li>{@code icdf( p > cdf(x) ) >= x+1}
+     *  <li>{@code icdf( cdf(x-1) < p < cdf(x) ) = x}
+     * </ul>
+     *
+     * <p>Does not check {@code isf( 1 - cdf(x) ) = x} since the complement {@code q = 1 - p}
+     * is inexact. The bound change for the isf to compute x may be different. The isf bound
+     * change is verified in a separate test.
+     * Thus the {@code icdf <-> cdf} mapping and {@code isf <-> sf} mapping are verified to
+     * have correct boundary changes with respect to the forward function and its inverse
+     * but the boundaries are allowed to be different. This can be corrected for example
+     * with an implementation that has a consistent computation for {@code x > median} and
+     * another for {@code x < median} with an inverse computation determined by {@code p > 0.5}.
      */
     @ParameterizedTest
     @MethodSource
@@ -625,7 +673,7 @@ abstract class BaseDiscreteDistributionTest
                 continue;
             }
             final double p = dist.cumulativeProbability(x);
-            if (p == 1.0) {
+            if ((int) p == p) {
                 // Assume mapping not a bijection and ignore
                 continue;
             }
@@ -634,6 +682,102 @@ abstract class BaseDiscreteDistributionTest
                 x,
                 x1,
                 () -> "Incorrect CDF inverse value returned for " + p);
+            // The next p value up should return the next value
+            final double pp = Math.nextUp(p);
+            if (x != upper && pp != 1 && p != dist.cumulativeProbability(x + 1)) {
+                final double x2 = dist.inverseCumulativeProbability(pp);
+                Assertions.assertEquals(
+                    x + 1,
+                    x2,
+                    () -> "Incorrect CDF inverse value returned for " + pp);
+            }
+
+            // Invert a probability inside the range to the previous CDF value
+            if (x != lower) {
+                final double pm1 = dist.cumulativeProbability(x - 1);
+                final double px = (pm1 + p) / 2;
+                if (px > pm1) {
+                    final double xx = dist.inverseCumulativeProbability(px);
+                    Assertions.assertEquals(
+                        x,
+                        xx,
+                        () -> "Incorrect CDF inverse value returned for " + px);
+                }
+            }
+        }
+    }
+
+    /**
+     * Test that an inverse mapping of the survival probability density values matches
+     * the original point, {@code x = isf(sf(x))}.
+     *
+     * <p>Note: It is possible for two points to compute the same SF value. In this
+     * case the mapping is not a bijection. Any points computing a SF=0 or 1 are ignored
+     * as this is expected to be inverted to the domain bound.
+     *
+     * <p>Note: Any points outside the support of the distribution are ignored.
+     *
+     * <p>This test checks consistency of the inverse with the forward function.
+     * The test checks (where applicable):
+     * <ul>
+     *  <li>{@code isf( sf(x) ) = x}
+     *  <li>{@code isf( p < sf(x) ) >= x+1}
+     *  <li>{@code isf( sf(x-1) > p > sf(x) ) = x}
+     * </ul>
+     *
+     * <p>Does not check {@code icdf( 1 - sf(x) ) = x} since the complement {@code q = 1 - p}
+     * is inexact. The bound change for the icdf to compute x may be different. The icdf bound
+     * change is verified in a separate test.
+     * Thus the {@code icdf <-> cdf} mapping and {@code isf <-> sf} mapping are verified to
+     * have correct boundary changes with respect to the forward function and its inverse
+     * but the boundaries are allowed to be different. This can be corrected for example
+     * with an implementation that has a consistent computation for {@code x > median} and
+     * another for {@code x < median} with an inverse computation determined by {@code p > 0.5}.
+     */
+    @ParameterizedTest
+    @MethodSource
+    final void testSurvivalProbabilityInverseMapping(DiscreteDistribution dist,
+                                                     int[] points) {
+        final int lower = dist.getSupportLowerBound();
+        final int upper = dist.getSupportUpperBound();
+        for (int i = 0; i < points.length; i++) {
+            final int x = points[i];
+            if (x < lower || x > upper) {
+                continue;
+            }
+            final double p = dist.survivalProbability(x);
+            if ((int) p == p) {
+                // Assume mapping not a bijection and ignore
+                continue;
+            }
+            final double x1 = dist.inverseSurvivalProbability(p);
+            Assertions.assertEquals(
+                x,
+                x1,
+                () -> "Incorrect SF inverse value returned for " + p);
+
+            // The next p value down should return the next value
+            final double pp = Math.nextDown(p);
+            if (x != upper && pp != 0 && p != dist.survivalProbability(x + 1)) {
+                final double x2 = dist.inverseSurvivalProbability(pp);
+                Assertions.assertEquals(
+                    x + 1,
+                    x2,
+                    () -> "Incorrect SF inverse value returned for " + pp);
+            }
+
+            // Invert a probability inside the range to the previous SF value
+            if (x != lower) {
+                final double pm1 = dist.survivalProbability(x - 1);
+                final double px = (pm1 + p) / 2;
+                if (px < pm1) {
+                    final double xx = dist.inverseSurvivalProbability(px);
+                    Assertions.assertEquals(
+                        x,
+                        xx,
+                        () -> "Incorrect CDF inverse value returned for " + px);
+                }
+            }
         }
     }
 
@@ -725,6 +869,7 @@ abstract class BaseDiscreteDistributionTest
         final int lo = dist.getSupportLowerBound();
         TestUtils.assertEquals(dist.probability(lo), dist.cumulativeProbability(lo), tolerance, () -> "pmf(lower) != cdf(lower) for " + lo);
         Assertions.assertEquals(lo, dist.inverseCumulativeProbability(0.0), "icdf(0.0)");
+        Assertions.assertEquals(lo, dist.inverseSurvivalProbability(1.0), "isf(1.0)");
 
         if (lo != Integer.MIN_VALUE) {
             final int below = lo - 1;
@@ -737,6 +882,7 @@ abstract class BaseDiscreteDistributionTest
         final int hi = dist.getSupportUpperBound();
         Assertions.assertTrue(lo <= hi, "lower <= upper");
         Assertions.assertEquals(hi, dist.inverseCumulativeProbability(1.0), "icdf(1.0)");
+        Assertions.assertEquals(hi, dist.inverseSurvivalProbability(0.0), "isf(0.0)");
         if (hi != Integer.MAX_VALUE) {
             // For distributions defined up to integer max value we cannot test that
             // the CDF is 1.0 as they may be truncated.
@@ -772,6 +918,8 @@ abstract class BaseDiscreteDistributionTest
         }
         Assertions.assertThrows(DistributionException.class, () -> dist.inverseCumulativeProbability(-1), "p < 0.0");
         Assertions.assertThrows(DistributionException.class, () -> dist.inverseCumulativeProbability(2), "p > 1.0");
+        Assertions.assertThrows(DistributionException.class, () -> dist.inverseSurvivalProbability(-1), "q < 0.0");
+        Assertions.assertThrows(DistributionException.class, () -> dist.inverseSurvivalProbability(2), "q > 1.0");
     }
 
     /**
diff --git a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/BaseDistributionTest.java b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/BaseDistributionTest.java
index 915a0e9..37b108e 100644
--- a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/BaseDistributionTest.java
+++ b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/BaseDistributionTest.java
@@ -622,6 +622,59 @@ abstract class BaseDistributionTest<T, D extends DistributionTestData> {
 
     /**
      * Create a stream of arguments containing the distribution to test, the test
+     * points, and the test tolerance. The points and tolerance
+     * are identified using functions on the test instance data.
+     *
+     * <p>If the length of the points is zero then a
+     * {@link org.opentest4j.TestAbortedException TestAbortedException} is raised.
+     *
+     * @param points Function to create the points
+     * @param tolerance Function to create the tolerance
+     * @param name Name of the function under test
+     * @return the stream
+     */
+    <P, V> Stream<Arguments> streamPoints(Function<D, P> points,
+                                          Function<D, DoubleTolerance> tolerance,
+                                          String name) {
+        return streamPoints(doNotIgnore(), points, tolerance, name);
+    }
+
+    /**
+     * Create a stream of arguments containing the distribution to test, the test
+     * points, and the test tolerance. The points and tolerance
+     * are identified using functions on the test instance data.
+     *
+     * <p>If the length of the points is zero then a
+     * {@link org.opentest4j.TestAbortedException TestAbortedException} is raised.
+     *
+     * @param filter Filter applied on the test data. If true the data is ignored.
+     * @param points Function to create the points
+     * @param tolerance Function to create the tolerance
+     * @param name Name of the function under test
+     * @return the stream
+     */
+    <P, V> Stream<Arguments> streamPoints(Predicate<D> filter,
+                                          Function<D, P> points,
+                                          Function<D, DoubleTolerance> tolerance,
+                                          String name) {
+        final Builder<Arguments> b = Stream.builder();
+        final int[] size = {0};
+        data.forEach(d -> {
+            final P p = points.apply(d);
+            if (filter.test(d) || TestUtils.getLength(p) == 0) {
+                return;
+            }
+            size[0]++;
+            b.accept(Arguments.of(namedDistribution(d.getParameters()),
+                     namedArray("points", p),
+                     tolerance.apply(d)));
+        });
+        Assumptions.assumeTrue(size[0] != 0, () -> "Distribution has no data for " + name);
+        return b.build();
+    }
+
+    /**
+     * Create a stream of arguments containing the distribution to test, the test
      * points, test values and the test tolerance. The points, values and tolerance
      * are identified using functions on the test instance data.
      *
diff --git a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/BetaDistributionTest.java b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/BetaDistributionTest.java
index a2beb0c..e9aef2b 100644
--- a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/BetaDistributionTest.java
+++ b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/BetaDistributionTest.java
@@ -47,8 +47,8 @@ class BetaDistributionTest extends BaseContinuousDistributionTest {
 
     @Override
     protected double getRelativeTolerance() {
-        // A lower tolerance creates failures in the CDF inverse mapping test
-        return 1e-9;
+        // Limited by the inverse SF mapping
+        return 5e-9;
     }
 
     @Override
diff --git a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/DistributionTestData.java b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/DistributionTestData.java
index 1d302e1..c343cd4 100644
--- a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/DistributionTestData.java
+++ b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/DistributionTestData.java
@@ -83,6 +83,8 @@ abstract class DistributionTestData {
 
     /** Disable a {@code x = icdf(cdf(x))} mapping test. */
     private final boolean disableCdfInverse;
+    /** Disable a {@code x = isf(sf(x))} mapping test. */
+    private final boolean disableSfInverse;
     /** Disable tests of the sample method. */
     private final boolean disableSample;
     /** Disable tests of the cumulative probability function method. */
@@ -114,6 +116,10 @@ abstract class DistributionTestData {
         private final double[] icdfPoints;
         /** Expected inverse CDF values. */
         private final double[] icdfValues;
+        /** Test points to evaluate the inverse SF. */
+        private final double[] isfPoints;
+        /** Expected inverse SF values. */
+        private final double[] isfValues;
 
         /**
          * @param props Properties containing the test data
@@ -131,9 +137,11 @@ abstract class DistributionTestData {
             cdfHpPoints = getAsDoubleArray(props, "cdf.hp.points", null);
             sfHpPoints = getAsDoubleArray(props, "sf.hp.points", null);
             // Do not default to an inverse mapping.
-            // A separate cdf.inverse property controls an inverse mapping test.
+            // A separate [cdf|sf].inverse property controls an inverse mapping test.
             icdfPoints = getAsDoubleArray(props, "icdf.points", null);
             icdfValues = getAsDoubleArray(props, "icdf.values", null);
+            isfPoints = getAsDoubleArray(props, "isf.points", null);
+            isfValues = getAsDoubleArray(props, "isf.values", null);
             // Validation
             validatePair(cdfPoints, getCdfValues(), "cdf");
             validatePair(pdfPoints, getPdfValues(), "pdf");
@@ -142,6 +150,7 @@ abstract class DistributionTestData {
             validatePair(cdfHpPoints, getCdfHpValues(), "cdf.hp");
             validatePair(sfHpPoints, getSfHpValues(), "sf.hp");
             validatePair(icdfPoints, icdfValues, "icdf");
+            validatePair(isfPoints, isfValues, "isf");
         }
 
         @Override
@@ -246,6 +255,20 @@ abstract class DistributionTestData {
             return icdfValues;
         }
 
+        @Override
+        double[] getIsfPoints() {
+            return isfPoints;
+        }
+
+        /**
+         * Gets the expected inverse survival probability values for the test inverse SF points.
+         *
+         * @return the inverse SF values
+         */
+        double[] getIsfValues() {
+            return isfValues;
+        }
+
         /**
          * Checks if a test of the PDF method is disabled.
          *
@@ -291,6 +314,10 @@ abstract class DistributionTestData {
         private final double[] icdfPoints;
         /** Expected inverse CDF values. */
         private final int[] icdfValues;
+        /** Test points to evaluate the inverse SF. */
+        private final double[] isfPoints;
+        /** Expected inverse SF values. */
+        private final int[] isfValues;
         /** Disable tests of the summation of the PMF verses the CDF. */
         private final boolean disablePmfSum;
 
@@ -310,9 +337,11 @@ abstract class DistributionTestData {
             cdfHpPoints = getAsIntArray(props, "cdf.hp.points", null);
             sfHpPoints = getAsIntArray(props, "sf.hp.points", null);
             // Do not default to an inverse mapping.
-            // A separate cdf.inverse property controls an inverse mapping test.
+            // A separate [cdf|sf].inverse property controls an inverse mapping test.
             icdfPoints = getAsDoubleArray(props, "icdf.points", null);
             icdfValues = getAsIntArray(props, "icdf.values", null);
+            isfPoints = getAsDoubleArray(props, "isf.points", null);
+            isfValues = getAsIntArray(props, "isf.values", null);
             disablePmfSum = getAsBoolean(props, "disable.pmf.sum", false);
             // Validation
             validatePair(cdfPoints, getCdfValues(), "cdf");
@@ -322,6 +351,7 @@ abstract class DistributionTestData {
             validatePair(cdfHpPoints, getCdfHpValues(), "cdf.hp");
             validatePair(sfHpPoints, getSfHpValues(), "sf.hp");
             validatePair(icdfPoints, icdfValues, "icdf");
+            validatePair(isfPoints, isfValues, "isf");
         }
 
         @Override
@@ -426,6 +456,20 @@ abstract class DistributionTestData {
             return icdfValues;
         }
 
+        @Override
+        double[] getIsfPoints() {
+            return isfPoints;
+        }
+
+        /**
+         * Gets the expected inverse survival probability values for the test inverse SF points.
+         *
+         * @return the inverse SF values
+         */
+        int[] getIsfValues() {
+            return isfValues;
+        }
+
         /**
          * Checks if a test of the PMF method is disabled.
          *
@@ -489,6 +533,7 @@ abstract class DistributionTestData {
         cdfHpValues = getAsDoubleArray(props, "cdf.hp.values", null);
         sfHpValues = getAsDoubleArray(props, "sf.hp.values", null);
         disableCdfInverse = getAsBoolean(props, "disable.cdf.inverse", false);
+        disableSfInverse = getAsBoolean(props, "disable.sf.inverse", false);
         disableSample = getAsBoolean(props, "disable.sample", false);
         disablePf = getAsBoolean(props, "disable." + pf, false);
         disableLogPf = getAsBoolean(props, "disable." + pf, false);
@@ -913,8 +958,20 @@ abstract class DistributionTestData {
     abstract double[] getIcdfPoints();
 
     /**
+     * Gets the points to evaluate the inverse SF.
+     *
+     * @return the inverse SF points
+     */
+    abstract double[] getIsfPoints();
+
+    /**
      * Checks if a {@code x = icdf(cdf(x))} mapping test is disabled.
      *
+     * <p>Note that this property disables a round-trip test of the points used to test
+     * the cumulative probability. The inverse cumulative probability can also be tested
+     * separately using the {@link #getIcdfPoints()} allowing the forward and reverse
+     * functions to target different data.
+     *
      * @return true if a CDF inverse mapping test is disabled.
      */
     boolean isDisableCdfInverse() {
@@ -922,6 +979,20 @@ abstract class DistributionTestData {
     }
 
     /**
+     * Checks if a {@code x = isf(sf(x))} mapping test is disabled.
+     *
+     * <p>Note that this property disables a round-trip test of the points used to test
+     * the survival probability. The inverse survival probability can also be tested
+     * separately using the {@link #getIsfPoints()} allowing the forward and reverse
+     * functions to target different data.
+     *
+     * @return true if a SF inverse mapping test is disabled.
+     */
+    boolean isDisableSfInverse() {
+        return disableSfInverse;
+    }
+
+    /**
      * Checks if a test of the sample method is disabled.
      *
      * @return true if a sample test is disabled.
diff --git a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/ExponentialDistributionTest.java b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/ExponentialDistributionTest.java
index 5533a3f..8726b8a 100644
--- a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/ExponentialDistributionTest.java
+++ b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/ExponentialDistributionTest.java
@@ -69,4 +69,11 @@ class ExponentialDistributionTest extends BaseContinuousDistributionTest {
         // computed using  print(dexp(2, rate=1/3), digits=10) in R 2.5
         Assertions.assertEquals(0.1711390397, d2.density(2.0), 1e-8);
     }
+
+    @Test
+    void testInverseCDFWithZero() {
+        final ExponentialDistribution d1 = ExponentialDistribution.of(1);
+        Assertions.assertEquals(0.0, d1.inverseCumulativeProbability(0.0));
+        Assertions.assertEquals(0.0, d1.inverseCumulativeProbability(-0.0));
+    }
 }
diff --git a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/GeometricDistributionTest.java b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/GeometricDistributionTest.java
index 6e8c184..7931147 100644
--- a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/GeometricDistributionTest.java
+++ b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/GeometricDistributionTest.java
@@ -75,19 +75,60 @@ class GeometricDistributionTest extends BaseDiscreteDistributionTest {
 
     /**
      * Test the inverse CDF returns the correct x from the CDF result.
-     * This case was identified using various probabilities to discover a mismatch
+     * Cases were identified using various probabilities to discover a mismatch
      * of x != icdf(cdf(x)). This occurs due to rounding errors on the inversion.
-     *
-     * @param p Probability of success
      */
     @ParameterizedTest
-    @ValueSource(doubles = {0.2, 0.8})
+    @ValueSource(doubles = {
+        0.2,
+        0.8,
+        // icdf(cdf(x)) requires rounding up
+        0.07131208016887369,
+        0.14441285445326058,
+        0.272118157703929,
+        0.424656239093432,
+        0.00899452845634574,
+        // icdf(cdf(x)) requires rounding down
+        0.3441320118140774,
+        0.5680886873083258,
+        0.8738746761971425,
+        0.17373328785967923,
+        0.09252030895185881,
+    })
     void testInverseCDF(double p) {
         final GeometricDistribution dist = GeometricDistribution.of(p);
         final int[] x = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
         testCumulativeProbabilityInverseMapping(dist, x);
     }
 
+    /**
+     * Test the inverse SF returns the correct x from the SF result.
+     * Cases were identified using various probabilities to discover a mismatch
+     * of x != isf(sf(x)). This occurs due to rounding errors on the inversion.
+     */
+    @ParameterizedTest
+    @ValueSource(doubles = {
+        0.2,
+        0.8,
+        // isf(sf(x)) requires rounding up
+        0.9625911263689207,
+        0.2858964038911178,
+        0.31872883511135996,
+        0.46149078212832284,
+        0.3701613946505057,
+        // isf(sf(x)) requires rounding down
+        0.3796493606864414,
+        0.1113177920615187,
+        0.2587259503484439,
+        0.8996839434455458,
+        0.450704136259792,
+    })
+    void testInverseSF(double p) {
+        final GeometricDistribution dist = GeometricDistribution.of(p);
+        final int[] x = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+        testSurvivalProbabilityInverseMapping(dist, x);
+    }
+
     @Test
     void testAdditionalMoments() {
         GeometricDistribution dist;
@@ -119,12 +160,82 @@ class GeometricDistributionTest extends BaseDiscreteDistributionTest {
         final double cdf = -Math.expm1(Math.log1p(-p) * (x + 1.0));
         Assertions.assertNotEquals(1.0, cdf);
         Assertions.assertEquals(cdf, dist.cumulativeProbability(x));
-        Assertions.assertEquals(x, dist.inverseCumulativeProbability(dist.cumulativeProbability(x)));
+        for (int i = 0; i < 5; i++) {
+            Assertions.assertEquals(x - i, dist.inverseCumulativeProbability(dist.cumulativeProbability(x - i)));
+        }
+
+        // CDF(x=0) = p
+        Assertions.assertEquals(p, dist.cumulativeProbability(0));
+        Assertions.assertEquals(0, dist.inverseCumulativeProbability(p));
+        Assertions.assertEquals(1, dist.inverseCumulativeProbability(Math.nextUp(p)));
+        for (int i = 1; i < 5; i++) {
+            Assertions.assertEquals(i, dist.inverseCumulativeProbability(dist.cumulativeProbability(i)));
+        }
 
         // SF = (1-p)^(x+1)
         // Compute with log for accuracy with small p
         final double sf = Math.exp(Math.log1p(-p) * (x + 1.0));
+        Assertions.assertEquals(1.0 - cdf, sf);
+        Assertions.assertEquals(sf, dist.survivalProbability(x));
+        // SF is too close to 1 to be able to invert
+        Assertions.assertEquals(1.0, sf);
+        Assertions.assertEquals(x, dist.inverseSurvivalProbability(Math.nextDown(1.0)));
+    }
+
+    /**
+     * Test the most extreme parameters. Uses a large enough value of p that the distribution is
+     * compacted to x=0.
+     *
+     * <p>p is one ULP down from 1.0.
+     */
+    @Test
+    void testExtremeParameters2() {
+        final double p = Math.nextDown(1.0);
+        final GeometricDistribution dist = GeometricDistribution.of(p);
+
+        final int x = 0;
+        // CDF = 1 - (1-p)^(x+1)
+        // CDF(x=0) = p
+        Assertions.assertEquals(p, dist.cumulativeProbability(0));
+        Assertions.assertEquals(0, dist.inverseCumulativeProbability(p));
+        // CDF is too close to 1 to be able to invert next value
+        Assertions.assertEquals(Integer.MAX_VALUE, dist.inverseCumulativeProbability(Math.nextUp(p)));
+
+        // SF = (1-p)^(x+1)
+        final double sf = 1 - p;
+        Assertions.assertNotEquals(0.0, sf);
+        Assertions.assertEquals(sf, dist.survivalProbability(x));
+        for (int i = 1; i < 5; i++) {
+            Assertions.assertEquals(i, dist.inverseSurvivalProbability(dist.survivalProbability(i)));
+        }
+    }
+
+    /**
+     * Test the most extreme parameters. Uses a large enough value of p that the distribution is
+     * compacted to x=0.
+     *
+     * <p>p is two ULP down from 1.0.
+     */
+    @Test
+    void testExtremeParameters3() {
+        final double p = Math.nextDown(Math.nextDown(1.0));
+        final GeometricDistribution dist = GeometricDistribution.of(p);
+
+        final int x = 0;
+        // CDF = 1 - (1-p)^(x+1)
+        // CDF(x=0) = p
+        Assertions.assertEquals(p, dist.cumulativeProbability(0));
+        Assertions.assertEquals(0, dist.inverseCumulativeProbability(p));
+        Assertions.assertEquals(1, dist.inverseCumulativeProbability(Math.nextUp(p)));
+        // CDF is too close to 1 to be able to invert next value
+        Assertions.assertEquals(Integer.MAX_VALUE, dist.inverseCumulativeProbability(Math.nextUp(Math.nextUp(p))));
+
+        // SF = (1-p)^(x+1)
+        final double sf = 1 - p;
         Assertions.assertNotEquals(0.0, sf);
         Assertions.assertEquals(sf, dist.survivalProbability(x));
+        for (int i = 1; i < 5; i++) {
+            Assertions.assertEquals(i, dist.inverseSurvivalProbability(dist.survivalProbability(i)));
+        }
     }
 }
diff --git a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/NormalDistributionTest.java b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/NormalDistributionTest.java
index 9625a79..fe16072 100644
--- a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/NormalDistributionTest.java
+++ b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/NormalDistributionTest.java
@@ -47,9 +47,9 @@ class NormalDistributionTest extends BaseContinuousDistributionTest {
 
     @Override
     protected double getRelativeTolerance() {
-        // Tests are limited by the survival probability
-        // Tolerance is 3.3306690738754696E-15.
-        return 15 * RELATIVE_EPS;
+        // Tests are limited by the inverse survival probability
+        // Tolerance is 4.440892098500626E-15.
+        return 20 * RELATIVE_EPS;
     }
 
     @Override
diff --git a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/UniformDiscreteDistributionTest.java b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/UniformDiscreteDistributionTest.java
index 92deb26..3f976c7 100644
--- a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/UniformDiscreteDistributionTest.java
+++ b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/UniformDiscreteDistributionTest.java
@@ -17,8 +17,11 @@
 
 package org.apache.commons.statistics.distribution;
 
+import org.apache.commons.math3.util.MathArrays;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
 
 /**
  * Test cases for {@link UniformDiscreteDistribution}.
@@ -101,33 +104,69 @@ class UniformDiscreteDistributionTest extends BaseDiscreteDistributionTest {
 
     /**
      * Test the inverse CDF returns the correct x from the CDF result.
-     * This case was identified using various x and upper bounds to discover a mismatch
-     * of x != icdf(cdf(x)). This occurs due to rounding errors on the inversion.
+     * Test cases created to generate rounding errors on the inversion.
      */
-    @Test
-    void testInverseCDF() {
-        final int lower = 3;
-        final int x = 23;
-        final int upper = 40;
-        final double range = (double) upper - lower + 1;
+    @ParameterizedTest
+    @CsvSource(value = {
+        // Extreme bounds
+        "-2147483648, -2147483648",
+        "-2147483648, -2147483647",
+        "-2147483648, -2147483646",
+        "-2147483648, -2147483638",
+        "2147483647, 2147483647",
+        "2147483646, 2147483647",
+        "2147483645, 2147483647",
+        "2147483637, 2147483647",
+        // icdf(cdf(x)) requires rounding up
+        "3, 40",
+        "71, 201",
+        "223, 267",
+        "45, 125",
+        "53, 81",
+        // icdf(cdf(x)) requires rounding down
+        "48, 247",
+        "141, 222",
+        "106, 223",
+        "156, 201",
+        "86, 265",
+    })
+    void testInverseCDF(int lower, int upper) {
+        final UniformDiscreteDistribution dist = UniformDiscreteDistribution.of(lower, upper);
+        final int[] x = MathArrays.sequence(upper - lower, lower, 1);
+        testCumulativeProbabilityInverseMapping(dist, x);
+    }
 
+    /**
+     * Test the inverse SF returns the correct x from the SF result.
+     * Test cases created to generate rounding errors on the inversion.
+     */
+    @ParameterizedTest
+    @CsvSource(value = {
+        // Extreme bounds
+        "-2147483648, -2147483648",
+        "-2147483648, -2147483647",
+        "-2147483648, -2147483646",
+        "-2147483648, -2147483638",
+        "2147483647, 2147483647",
+        "2147483646, 2147483647",
+        "2147483645, 2147483647",
+        "2147483637, 2147483647",
+        // isf(sf(x)) requires rounding up
+        "52, 91",
+        "81, 106",
+        "79, 268",
+        "54, 249",
+        "189, 267",
+        // isf(sf(x)) requires rounding down
+        "105, 279",
+        "42, 261",
+        "37, 133",
+        "59, 214",
+        "33, 118",
+    })
+    void testInverseSF(int lower, int upper) {
         final UniformDiscreteDistribution dist = UniformDiscreteDistribution.of(lower, upper);
-        // Compute p and check it is as expected
-        final double p = dist.cumulativeProbability(x);
-        Assertions.assertEquals((x - lower + 1) / range, p);
-
-        // Invert
-        final double value = Math.ceil(p * range + lower - 1);
-        Assertions.assertEquals(x + 1, value, "Expected a rounding error");
-
-        Assertions.assertEquals(x, dist.inverseCumulativeProbability(p), "Expected rounding correction");
-
-        // Test for overflow of an integer when inverting
-        final int min = Integer.MIN_VALUE;
-        final UniformDiscreteDistribution dist2 = UniformDiscreteDistribution.of(min, min + 10);
-        Assertions.assertEquals(min, dist2.inverseCumulativeProbability(0.0));
-        final int max = Integer.MAX_VALUE;
-        final UniformDiscreteDistribution dist3 = UniformDiscreteDistribution.of(max - 10, max);
-        Assertions.assertEquals(max, dist3.inverseCumulativeProbability(1.0));
+        final int[] x = MathArrays.sequence(upper - lower, lower, 1);
+        testSurvivalProbabilityInverseMapping(dist, x);
     }
 }
diff --git a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.beta.15.properties b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.beta.15.properties
index 3165f3a..d9509a6 100644
--- a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.beta.15.properties
+++ b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.beta.15.properties
@@ -14,6 +14,8 @@
 # limitations under the License.
 
 parameters = 1.0 4.0
+# Limited by inverse sf mapping
+tolerance.relative = 5e-8
 # Computed using Matlab
 mean = 0.20000000000000001
 variance = 0.026666666666666672
diff --git a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.beta.5.properties b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.beta.5.properties
index d7d5f5a..baa8be7 100644
--- a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.beta.5.properties
+++ b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.beta.5.properties
@@ -14,6 +14,8 @@
 # limitations under the License.
 
 parameters = 0.1 4.0
+# Limited by inverse sf mapping
+tolerance.relative = 5e-8
 # Computed using Matlab
 mean = 0.024390243902439
 variance = 0.004665756844082
diff --git a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.binomial.1.properties b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.binomial.1.properties
index 4ffcacc..5064881 100644
--- a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.binomial.1.properties
+++ b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.binomial.1.properties
@@ -30,6 +30,10 @@ pmf.values = \
   0.009001692, 0.036756909, 0.1029193452, 0.200120949, 0.266827932,\
   0.2334744405, 0.121060821, 0.0282475249, 0d
 icdf.points = \
-  0, 0.001d, 0.010d, 0.025d, 0.050d, 0.100d,\
-  0.999d, 0.990d, 0.975d, 0.950d, 0.900d, 1d
+  0, 0.001, 0.010, 0.025, 0.050, 0.100,\
+  0.999, 0.990, 0.975, 0.950, 0.900, 1
 icdf.values = 0, 2, 3, 4, 5, 5, 10, 10, 10, 9, 9, 10
+isf.points = \
+  1, 0.999, 0.990, 0.975, 0.950, 0.900,\
+  0.001, 0.010, 0.025, 0.050, 0.100, 0
+isf.values = 0, 2, 3, 4, 5, 5, 10, 10, 10, 9, 9, 10
diff --git a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.binomial.4.properties b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.binomial.4.properties
index 0b68010..1c4c3d6 100644
--- a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.binomial.4.properties
+++ b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.binomial.4.properties
@@ -34,6 +34,10 @@ pmf.values = \
   1.0291934520000002584e-01, 3.6756909000000004273e-02, 9.0016919999999864960e-03, 1.4467005000000008035e-03,\
   1.3778099999999990615e-04, 5.9048999999999949131e-06, 0.0000000000000000000e+00
 icdf.points = \
-  0, 0.001d, 0.010d, 0.025d, 0.050d, 0.100d,\
-  0.999d, 0.990d, 0.975d, 0.950d, 0.900d, 1d
+  0, 0.001, 0.010, 0.025, 0.050, 0.100,\
+  0.999, 0.990, 0.975, 0.950, 0.900, 1
 icdf.values = 0, 0, 0, 0, 1, 1, 8, 7, 6, 5, 5, 10
+isf.points = \
+  1, 0.999, 0.990, 0.975, 0.950, 0.900,\
+  0.001, 0.010, 0.025, 0.050, 0.100, 0
+isf.values = 0, 0, 0, 0, 1, 1, 8, 7, 6, 5, 5, 10
diff --git a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.binomial.5.properties b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.binomial.5.properties
index 000b25a..3ffc6e7 100644
--- a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.binomial.5.properties
+++ b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.binomial.5.properties
@@ -12,6 +12,7 @@
 # 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.
+
 # Degenerate p=1 case
 parameters = 5 1.0
 mean = 5
@@ -23,3 +24,5 @@ cdf.values = 0, 0, 0, 0, 1, 1
 pmf.values = 0, 0, 0, 0, 1, 0
 icdf.points = 0.1, 0.5
 icdf.values = 5, 5
+isf.points = 0.9, 0.5
+isf.values = 5, 5
diff --git a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.binomial.6.properties b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.binomial.6.properties
index 238db21..dde438c 100644
--- a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.binomial.6.properties
+++ b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.binomial.6.properties
@@ -23,3 +23,5 @@ cdf.values = 0, 1, 1, 1, 1, 1
 pmf.values = 0, 1, 0, 0, 0, 0
 icdf.points = 0.1, 0.5
 icdf.values = 0, 0
+isf.points = 0.9, 0.5
+isf.values = 0, 0
diff --git a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.binomial.7.properties b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.binomial.7.properties
index 30ff87e..078a956 100644
--- a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.binomial.7.properties
+++ b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.binomial.7.properties
@@ -23,3 +23,5 @@ cdf.values = 0, 1, 1, 1, 1, 1
 pmf.values = 0, 1, 0, 0, 0, 0
 icdf.points = 0.1, 0.5
 icdf.values = 0, 0
+isf.points = 0.9, 0.5
+isf.values = 0, 0
diff --git a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.chisquared.2.properties b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.chisquared.2.properties
index b7b5fec..2be3be6 100644
--- a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.chisquared.2.properties
+++ b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.chisquared.2.properties
@@ -37,6 +37,9 @@ disable.sample = true
 # TODO: CDF inverse test fails
 disable.cdf.inverse = true
 
+# TODO: SF inverse test fails
+disable.sf.inverse = true
+
 # TODO: Correct small degrees of freedom PDF
 # The underlying Gamma distribution currently switches to the alternate
 # computation which is inaccurate for expected value 4.27e55. The natural
diff --git a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.f.6.properties b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.f.6.properties
index 46c4771..e06f368 100644
--- a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.f.6.properties
+++ b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.f.6.properties
@@ -14,6 +14,8 @@
 # limitations under the License.
 
 parameters = 100.0 100.0
+# Limited by inverse sf mapping
+tolerance.relative = 5e-9
 # Computed using scipy.stats f
 mean = 1.0204081632653061
 variance = 0.04295085381091212
diff --git a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.geometric.1.properties b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.geometric.1.properties
index 58c1a82..1fe4bd0 100644
--- a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.geometric.1.properties
+++ b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.geometric.1.properties
@@ -95,5 +95,40 @@ icdf.values = \
   2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,\
   3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5,\
   5, 5, 6, 6, 6, 6, 7, 7, 8, 9, 10, 2147483647
+isf.points = \
+  1.000, 0.995, 0.990, 0.985, 0.980, 0.975, 0.970, 0.965, 0.960,\
+  0.955, 0.950, 0.945, 0.940, 0.935, 0.930, 0.925, 0.920, 0.915,\
+  0.910, 0.905, 0.900, 0.895, 0.890, 0.885, 0.880, 0.875, 0.870,\
+  0.865, 0.860, 0.855, 0.850, 0.845, 0.840, 0.835, 0.830, 0.825,\
+  0.820, 0.815, 0.810, 0.805, 0.800, 0.795, 0.790, 0.785, 0.780,\
+  0.775, 0.770, 0.765, 0.760, 0.755, 0.750, 0.745, 0.740, 0.735,\
+  0.730, 0.725, 0.720, 0.715, 0.710, 0.705, 0.700, 0.695, 0.690,\
+  0.685, 0.680, 0.675, 0.670, 0.665, 0.660, 0.655, 0.650, 0.645,\
+  0.640, 0.635, 0.630, 0.625, 0.620, 0.615, 0.610, 0.605, 0.600,\
+  0.595, 0.590, 0.585, 0.580, 0.575, 0.570, 0.565, 0.560, 0.555,\
+  0.550, 0.545, 0.540, 0.535, 0.530, 0.525, 0.520, 0.515, 0.510,\
+  0.505, 0.500, 0.495, 0.490, 0.485, 0.480, 0.475, 0.470, 0.465,\
+  0.460, 0.455, 0.450, 0.445, 0.440, 0.435, 0.430, 0.425, 0.420,\
+  0.415, 0.410, 0.405, 0.400, 0.395, 0.390, 0.385, 0.380, 0.375,\
+  0.370, 0.365, 0.360, 0.355, 0.350, 0.345, 0.340, 0.335, 0.330,\
+  0.325, 0.320, 0.315, 0.310, 0.305, 0.300, 0.295, 0.290, 0.285,\
+  0.280, 0.275, 0.270, 0.265, 0.260, 0.255, 0.250, 0.245, 0.240,\
+  0.235, 0.230, 0.225, 0.220, 0.215, 0.210, 0.205, 0.200, 0.195,\
+  0.190, 0.185, 0.180, 0.175, 0.170, 0.165, 0.160, 0.155, 0.150,\
+  0.145, 0.140, 0.135, 0.130, 0.125, 0.120, 0.115, 0.110, 0.105,\
+  0.100, 0.095, 0.090, 0.085, 0.080, 0.075, 0.070, 0.065, 0.060,\
+  0.055, 0.050, 0.045, 0.040, 0.035, 0.030, 0.025, 0.020, 0.015,\
+  0.010, 0.005, 0.000
+isf.values = \
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,\
+  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\
+  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\
+  1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,\
+  2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,\
+  3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5,\
+  5, 5, 6, 6, 6, 6, 7, 7, 8, 9, 10, 2147483647
 sf.points = 74, 81
 sf.values = 2.2979669527522718895e-17, 6.4328367688565960968e-19
diff --git a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.geometric.2.properties b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.geometric.2.properties
index 459c56d..2058332 100644
--- a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.geometric.2.properties
+++ b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.geometric.2.properties
@@ -23,3 +23,5 @@ cdf.values = 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0
 pmf.values = 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0
 icdf.points = 0.1, 0.5, 1.0
 icdf.values = 0, 0, 0
+icdf.points = 0.9, 0.5, 0.0
+icdf.values = 0, 0, 0
diff --git a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.hypergeometric.1.properties b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.hypergeometric.1.properties
index 9fa9568..c2dd5d2 100644
--- a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.hypergeometric.1.properties
+++ b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.hypergeometric.1.properties
@@ -37,3 +37,7 @@ icdf.points = \
   0.0, 0.001, 0.010, 0.025, 0.050, 0.100, 0.999,\
   0.990, 0.975, 0.950, 0.900, 1.0
 icdf.values = 0, 0, 1, 1, 1, 1, 5, 4, 4, 4, 4, 5
+isf.points = \
+  1, 0.999, 0.990, 0.975, 0.950, 0.900,\
+  0.001, 0.010, 0.025, 0.050, 0.100, 0
+isf.values = 0, 0, 1, 1, 1, 1, 5, 4, 4, 4, 4, 5
diff --git a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.hypergeometric.2.properties b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.hypergeometric.2.properties
index eacf8a3..2f6340f 100644
--- a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.hypergeometric.2.properties
+++ b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.hypergeometric.2.properties
@@ -24,3 +24,5 @@ cdf.values = 0, 0, 0, 1, 1
 pmf.values = 0, 0, 0, 1, 0
 icdf.points = 0.1, 0.5
 icdf.values = 3, 3
+isf.points = 0.9, 0.5
+isf.values = 3, 3
diff --git a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.hypergeometric.3.properties b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.hypergeometric.3.properties
index 0418547..f1a2d8e 100644
--- a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.hypergeometric.3.properties
+++ b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.hypergeometric.3.properties
@@ -24,3 +24,5 @@ cdf.values = 0, 1, 1, 1, 1
 pmf.values = 0, 1, 0, 0, 0
 icdf.points = 0.1, 0.5
 icdf.values = 0, 0
+isf.points = 0.9, 0.5
+isf.values = 0, 0
diff --git a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.hypergeometric.4.properties b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.hypergeometric.4.properties
index bd0864d..eaf88e4 100644
--- a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.hypergeometric.4.properties
+++ b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.hypergeometric.4.properties
@@ -24,3 +24,5 @@ cdf.values = 0, 0, 0, 1, 1
 pmf.values = 0, 0, 0, 1, 0
 icdf.points = 0.1, 0.5
 icdf.values = 3, 3
+isf.points = 0.9, 0.5
+isf.values = 3, 3
diff --git a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.pascal.1.properties b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.pascal.1.properties
index 1470b2b..2bbe846 100644
--- a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.pascal.1.properties
+++ b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.pascal.1.properties
@@ -38,5 +38,10 @@ icdf.points = \
   0.990, 0.975, 0.950, 0.900, 1.0
 icdf.values = \
   0, 0, 0, 0, 1, 1, 14, 11, 10, 9, 8, 2147483647
+isf.points = \
+  1, 0.999, 0.990, 0.975, 0.950, 0.900,\
+  0.001, 0.010, 0.025, 0.050, 0.100, 0
+isf.values = \
+  0, 0, 0, 0, 1, 1, 14, 11, 10, 9, 8, 2147483647
 sf.points = 47, 52
 sf.values = 3.1403888119656772712e-17, 1.7075879020163069251e-19
diff --git a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.pascal.2.properties b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.pascal.2.properties
index 13e639d..1e3ebb7 100644
--- a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.pascal.2.properties
+++ b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.pascal.2.properties
@@ -23,3 +23,5 @@ cdf.values = 0.0, 1.0, 1.0, 1.0, 1.0, 1.0
 pmf.values = 0.0, 1.0, 0.0, 0.0, 0.0, 0.0
 icdf.points = 0.1, 0.5, 1.0
 icdf.values = 0, 0, 0
+isf.points = 0.9, 0.5, 0.0
+isf.values = 0, 0, 0
diff --git a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.poisson.1.properties b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.poisson.1.properties
index 5288723..b327d3b 100644
--- a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.poisson.1.properties
+++ b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.poisson.1.properties
@@ -59,10 +59,19 @@ icdf.points = \
   0,\
   0.018315638886, 0.018315638890,\
   0.091578194441, 0.091578194445,\
-  0.238103305552, 0.238103305556,
+  0.238103305552, 0.238103305556
 icdf.values = 0,\
   0, 1,\
   1, 2,\
   2, 3
+isf.points = \
+  1,\
+  0.981684361114, 0.98168436111,\
+  0.908421805559, 0.908421805555,\
+  0.761896694448, 0.761896694444
+isf.values = 0,\
+  0, 1,\
+  1, 2,\
+  2, 3
 sf.points = 30, 32
 sf.values = 1.17324354314644421629e-17,   1.76301746878759321086e-19
diff --git a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.t.3.properties b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.t.3.properties
index e40171e..33bc48e 100644
--- a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.t.3.properties
+++ b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.t.3.properties
@@ -14,8 +14,8 @@
 # limitations under the License.
 
 parameters = 2.0
-# Limited by cdf inverse mapping
-tolerance.relative = 1e-11
+# Limited by sf inverse mapping
+tolerance.relative = 5e-11
 # Computed using scipy stats
 mean = 0
 variance = Infinity
diff --git a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.triangular.1.properties b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.triangular.1.properties
index 90e11a2..64d5da8 100644
--- a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.triangular.1.properties
+++ b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.triangular.1.properties
@@ -12,6 +12,7 @@
 # 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.
+
 # Left side 5 wide, right side 10 wide.
 parameters = -3.0, 2.0, 12.0
 # Computed manually
diff --git a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.triangular.2.properties b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.triangular.2.properties
index fa25c7d..af6570a 100644
--- a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.triangular.2.properties
+++ b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.triangular.2.properties
@@ -12,6 +12,7 @@
 # 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.
+
 # Left side 5 wide, right side 10 wide.
 parameters = 1.0 2.0 5.0
 # Computed using scipy.stats triang(0.25, 1, 4)
diff --git a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.uniformdiscrete.2.properties b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.triangular.3.properties
similarity index 69%
copy from commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.uniformdiscrete.2.properties
copy to commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.triangular.3.properties
index dd8ab7a..580aaf1 100644
--- a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.uniformdiscrete.2.properties
+++ b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.triangular.3.properties
@@ -13,14 +13,16 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# MATH-1141: Degenerate case is allowed.
-parameters = 1 1
-mean = 1
-variance = 0
+# Mode == upper, no right side
+parameters = 1.0, 2.0, 2.0
+# Computed manually
+mean = 1.6666666666666667
+variance = 0.05555555555555555
 lower = 1
-upper = 1
-cdf.points = -1, 0, 1, 2
-cdf.values = 0, 0, 1, 1
-pmf.values = 0, 0, 1, 0
-icdf.points = 0.1 0.5
-icdf.values = 1 1
+upper = 2
+
+cdf.points = 0, 0.5, 1, 1.25,   1.5,  1.75,   2, 3
+# cdf(x) = (x - 1)^2
+cdf.values = 0, 0,   0, 0.0625, 0.25, 0.5625, 1, 1
+# pdf(x) = 2 (x - 1)
+pdf.values = 0, 0,   0, 0.5,    1,    1.5,    2, 0
diff --git a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.uniformdiscrete.2.properties b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.triangular.4.properties
similarity index 69%
copy from commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.uniformdiscrete.2.properties
copy to commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.triangular.4.properties
index dd8ab7a..ac9c2f8 100644
--- a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.uniformdiscrete.2.properties
+++ b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.triangular.4.properties
@@ -13,14 +13,16 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# MATH-1141: Degenerate case is allowed.
-parameters = 1 1
-mean = 1
-variance = 0
+# Mode == lower, no left side
+parameters = 1.0, 1.0, 2.0
+# Computed manually
+mean = 1.3333333333333333
+variance = 0.05555555555555555
 lower = 1
-upper = 1
-cdf.points = -1, 0, 1, 2
-cdf.values = 0, 0, 1, 1
-pmf.values = 0, 0, 1, 0
-icdf.points = 0.1 0.5
-icdf.values = 1 1
+upper = 2
+
+cdf.points = 0, 0.5, 1, 1.25,   1.5,  1.75,   2,  3
+# cdf(x) = 1 - (2 - x)^2
+cdf.values = 0, 0,   0, 0.4375, 0.75, 0.9375, 1,  1
+# pdf(x) = 2 - 2 (x - 1)
+pdf.values = 0, 0,   2, 1.5,    1,    0.5,    0, 0
diff --git a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.uniformdiscrete.1.properties b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.uniformdiscrete.1.properties
index de91519..0242d55 100644
--- a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.uniformdiscrete.1.properties
+++ b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.uniformdiscrete.1.properties
@@ -47,3 +47,8 @@ icdf.points = \
   0.5, 0.999, 0.990, 0.975, 0.950, 0.900, 1
 icdf.values = \
   -3, -3, -3, -3, -3, -3, -2, 1, 5, 5, 5, 5, 5, 5
+isf.points = \
+  1, 0.999, 0.990, 0.975, 0.950, 0.900, 0.800,\
+  0.5, 0.001, 0.010, 0.025, 0.050, 0.100, 0
+isf.values = \
+  -3, -3, -3, -3, -3, -3, -2, 1, 5, 5, 5, 5, 5, 5
diff --git a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.uniformdiscrete.2.properties b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.uniformdiscrete.2.properties
index dd8ab7a..1c502ec 100644
--- a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.uniformdiscrete.2.properties
+++ b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.uniformdiscrete.2.properties
@@ -24,3 +24,5 @@ cdf.values = 0, 0, 1, 1
 pmf.values = 0, 0, 1, 0
 icdf.points = 0.1 0.5
 icdf.values = 1 1
+isf.points = 0.9 0.5
+isf.values = 1 1
diff --git a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.zipf.1.properties b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.zipf.1.properties
index f31b5e1..73afbcc 100644
--- a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.zipf.1.properties
+++ b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.zipf.1.properties
@@ -37,3 +37,7 @@ icdf.points = \
   0, 0.001, 0.010, 0.025, 0.050, 0.3413, 0.3415, 0.999,\
   0.990, 0.975, 0.950, 0.900, 1
 icdf.values = 1, 1, 1, 1, 1, 1, 2, 10, 10, 10, 9, 8, 10
+isf.points = \
+  1, 0.999, 0.990, 0.975, 0.950, 0.6587, 0.6585, 0.001,\
+  0.010, 0.025, 0.050, 0.100, 0
+isf.values = 1, 1, 1, 1, 1, 1, 2, 10, 10, 10, 9, 8, 10

[commons-statistics] 04/04: STATISTIC-47: Add isf command to distribution examples application

Posted by ah...@apache.org.
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-statistics.git

commit 8fb31cf6c6d3b0174263a294115a56a4d6668bcb
Author: Alex Herbert <ah...@apache.org>
AuthorDate: Fri Nov 19 22:12:20 2021 +0000

    STATISTIC-47: Add isf command to distribution examples application
    
    Update check command to check the isf.
    
    Change the check command to use Precision to test relative equality.
    
    If the inverse probability does not map to the original input then do a
    forward mapping to test the value maps back to the same probability.
    This accommodates function pairs that are not bijections, i.e. are
    many-to-one.
---
 .../examples-distribution/HOWTO.md                 |   3 +-
 .../examples/distribution/BetaCommand.java         |  27 +++--
 .../examples/distribution/BinomialCommand.java     |  27 +++--
 .../examples/distribution/CauchyCommand.java       |  27 +++--
 .../examples/distribution/ChiSquaredCommand.java   |  27 +++--
 .../distribution/DistributionFunction.java         |   4 +-
 .../examples/distribution/DistributionOptions.java |   2 +-
 .../examples/distribution/DistributionUtils.java   | 117 ++++++++++++++++-----
 .../examples/distribution/ExpCommand.java          |  27 +++--
 .../statistics/examples/distribution/FCommand.java |  27 +++--
 .../examples/distribution/GammaCommand.java        |  27 +++--
 .../examples/distribution/GeometricCommand.java    |  27 +++--
 .../examples/distribution/GumbelCommand.java       |  27 +++--
 .../distribution/HypergeometricCommand.java        |  27 +++--
 .../examples/distribution/LaplaceCommand.java      |  27 +++--
 .../examples/distribution/LevyCommand.java         |  27 +++--
 .../examples/distribution/LogNormalCommand.java    |  27 +++--
 .../examples/distribution/LogisticCommand.java     |  27 +++--
 .../examples/distribution/NakagamiCommand.java     |  27 +++--
 .../examples/distribution/NormalCommand.java       |  27 +++--
 .../examples/distribution/ParetoCommand.java       |  27 +++--
 .../examples/distribution/PascalCommand.java       |  27 +++--
 .../examples/distribution/PoissonCommand.java      |  27 +++--
 .../statistics/examples/distribution/TCommand.java |  27 +++--
 .../examples/distribution/TriangularCommand.java   |  35 +++---
 .../distribution/TruncatedNormalCommand.java       |  27 +++--
 .../distribution/UniformContinuousCommand.java     |  27 +++--
 .../distribution/UniformDiscreteCommand.java       |  27 +++--
 .../examples/distribution/WeibullCommand.java      |  27 +++--
 .../examples/distribution/ZipfCommand.java         |  27 +++--
 30 files changed, 571 insertions(+), 265 deletions(-)

diff --git a/commons-statistics-examples/examples-distribution/HOWTO.md b/commons-statistics-examples/examples-distribution/HOWTO.md
index eb80c30..4ed0b2c 100644
--- a/commons-statistics-examples/examples-distribution/HOWTO.md
+++ b/commons-statistics-examples/examples-distribution/HOWTO.md
@@ -41,13 +41,14 @@ named by the command. Each distribution command has the following functions:
 | cdf | Cumulative Probability Density Function |
 | survival | Survival Probability Function |
 | icdf | Inverse Cumulative Probability Density Function |
+| isf | Inverse Survival Probability Function |
 | lpdf | Natural logarithm of the PDF (continuous distributions) |
 | lpmf | Natural logarithm of the PMF (discrete distributions) |
 
 For convenicence the discrete distribution pmf and lpmf commands are aliased to pdf and lpdf.
 
 The pdf, cdf and survival functions accept an input value and output a probability or
-probability density. The icdf function accepts an input probability and outputs a value.
+probability density. The icdf and isf functions accepts an input probability and outputs a value.
 The distribution is configured with suitable defaults for the distribution parameters
 and the range of values to evaluate. Executing the command will output a delimited
 table that can be plotted for visual inspection. The output will default to stdout and
diff --git a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/BetaCommand.java b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/BetaCommand.java
index c01a7cb..9ed24ac 100644
--- a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/BetaCommand.java
+++ b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/BetaCommand.java
@@ -36,6 +36,7 @@ import picocli.CommandLine.Option;
              BetaCommand.CDF.class,
              BetaCommand.Survival.class,
              BetaCommand.ICDF.class,
+             BetaCommand.ISF.class,
          })
 class BetaCommand extends AbstractDistributionCommand {
 
@@ -102,6 +103,18 @@ class BetaCommand extends AbstractDistributionCommand {
         }
     }
 
+    /** Base command for the distribution that defines the parameters for inverse probability functions. */
+    private abstract static class InverseProbabilityCommand extends BaseCommand {
+        /** The distribution options. */
+        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
+        private InverseContinuousDistributionOptions distributionOptions = new InverseContinuousDistributionOptions();
+
+        @Override
+        protected DistributionOptions getDistributionOptions() {
+            return distributionOptions;
+        }
+    }
+
     /** Verification checks command. */
     @Command(name = "check",
              hidden = true,
@@ -131,14 +144,10 @@ class BetaCommand extends AbstractDistributionCommand {
     /** ICDF command. */
     @Command(name = "icdf",
              description = "Beta distribution inverse CDF.")
-    static class ICDF extends BaseCommand {
-        /** The distribution options. */
-        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
-        private InverseContinuousDistributionOptions distributionOptions = new InverseContinuousDistributionOptions();
+    static class ICDF extends InverseProbabilityCommand {}
 
-        @Override
-        protected DistributionOptions getDistributionOptions() {
-            return distributionOptions;
-        }
-    }
+    /** ISF command. */
+    @Command(name = "isf",
+             description = "Beta distribution inverse SF.")
+    static class ISF extends InverseProbabilityCommand {}
 }
diff --git a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/BinomialCommand.java b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/BinomialCommand.java
index 2da31f0..296bb63 100644
--- a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/BinomialCommand.java
+++ b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/BinomialCommand.java
@@ -37,6 +37,7 @@ import picocli.CommandLine.Option;
              BinomialCommand.CDF.class,
              BinomialCommand.Survival.class,
              BinomialCommand.ICDF.class,
+             BinomialCommand.ISF.class,
          })
 class BinomialCommand extends AbstractDistributionCommand {
 
@@ -103,6 +104,18 @@ class BinomialCommand extends AbstractDistributionCommand {
         }
     }
 
+    /** Base command for the distribution that defines the parameters for inverse probability functions. */
+    private abstract static class InverseProbabilityCommand extends BaseCommand {
+        /** The distribution options. */
+        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
+        private InverseDiscreteDistributionOptions distributionOptions = new InverseDiscreteDistributionOptions();
+
+        @Override
+        protected DistributionOptions getDistributionOptions() {
+            return distributionOptions;
+        }
+    }
+
     /** Verification checks command. */
     @Command(name = "check",
              hidden = true,
@@ -134,14 +147,10 @@ class BinomialCommand extends AbstractDistributionCommand {
     /** ICDF command. */
     @Command(name = "icdf",
              description = "Binomial distribution inverse CDF.")
-    static class ICDF extends BaseCommand {
-        /** The distribution options. */
-        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
-        private InverseDiscreteDistributionOptions distributionOptions = new InverseDiscreteDistributionOptions();
+    static class ICDF extends InverseProbabilityCommand {}
 
-        @Override
-        protected DistributionOptions getDistributionOptions() {
-            return distributionOptions;
-        }
-    }
+    /** ISF command. */
+    @Command(name = "isf",
+             description = "Binomial distribution inverse SF.")
+    static class ISF extends InverseProbabilityCommand {}
 }
diff --git a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/CauchyCommand.java b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/CauchyCommand.java
index c5f8537..d9637de 100644
--- a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/CauchyCommand.java
+++ b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/CauchyCommand.java
@@ -36,6 +36,7 @@ import picocli.CommandLine.Option;
              CauchyCommand.CDF.class,
              CauchyCommand.Survival.class,
              CauchyCommand.ICDF.class,
+             CauchyCommand.ISF.class,
          })
 class CauchyCommand extends AbstractDistributionCommand {
 
@@ -102,6 +103,18 @@ class CauchyCommand extends AbstractDistributionCommand {
         }
     }
 
+    /** Base command for the distribution that defines the parameters for inverse probability functions. */
+    private abstract static class InverseProbabilityCommand extends BaseCommand {
+        /** The distribution options. */
+        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
+        private InverseContinuousDistributionOptions distributionOptions = new InverseContinuousDistributionOptions();
+
+        @Override
+        protected DistributionOptions getDistributionOptions() {
+            return distributionOptions;
+        }
+    }
+
     /** Verification checks command. */
     @Command(name = "check",
              hidden = true,
@@ -131,14 +144,10 @@ class CauchyCommand extends AbstractDistributionCommand {
     /** ICDF command. */
     @Command(name = "icdf",
              description = "Cauchy distribution inverse CDF.")
-    static class ICDF extends BaseCommand {
-        /** The distribution options. */
-        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
-        private InverseContinuousDistributionOptions distributionOptions = new InverseContinuousDistributionOptions();
+    static class ICDF extends InverseProbabilityCommand {}
 
-        @Override
-        protected DistributionOptions getDistributionOptions() {
-            return distributionOptions;
-        }
-    }
+    /** ISF command. */
+    @Command(name = "isf",
+             description = "Cauchy distribution inverse SF.")
+    static class ISF extends InverseProbabilityCommand {}
 }
diff --git a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/ChiSquaredCommand.java b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/ChiSquaredCommand.java
index 34c614e..8df0119 100644
--- a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/ChiSquaredCommand.java
+++ b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/ChiSquaredCommand.java
@@ -37,6 +37,7 @@ import picocli.CommandLine.Option;
              ChiSquaredCommand.CDF.class,
              ChiSquaredCommand.Survival.class,
              ChiSquaredCommand.ICDF.class,
+             ChiSquaredCommand.ISF.class,
          })
 class ChiSquaredCommand extends AbstractDistributionCommand {
 
@@ -89,6 +90,18 @@ class ChiSquaredCommand extends AbstractDistributionCommand {
         }
     }
 
+    /** Base command for the distribution that defines the parameters for inverse probability functions. */
+    private abstract static class InverseProbabilityCommand extends BaseCommand {
+        /** The distribution options. */
+        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
+        private InverseContinuousDistributionOptions distributionOptions = new InverseContinuousDistributionOptions();
+
+        @Override
+        protected DistributionOptions getDistributionOptions() {
+            return distributionOptions;
+        }
+    }
+
     /** Verification checks command. */
     @Command(name = "check",
              hidden = true,
@@ -118,14 +131,10 @@ class ChiSquaredCommand extends AbstractDistributionCommand {
     /** ICDF command. */
     @Command(name = "icdf",
              description = "Chi-squared distribution inverse CDF.")
-    static class ICDF extends BaseCommand {
-        /** The distribution options. */
-        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
-        private InverseContinuousDistributionOptions distributionOptions = new InverseContinuousDistributionOptions();
+    static class ICDF extends InverseProbabilityCommand {}
 
-        @Override
-        protected DistributionOptions getDistributionOptions() {
-            return distributionOptions;
-        }
-    }
+    /** ISF command. */
+    @Command(name = "isf",
+             description = "Chi-squared distribution inverse SF.")
+    static class ISF extends InverseProbabilityCommand {}
 }
diff --git a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/DistributionFunction.java b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/DistributionFunction.java
index db71cea..8a98dae 100644
--- a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/DistributionFunction.java
+++ b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/DistributionFunction.java
@@ -33,5 +33,7 @@ enum DistributionFunction {
     /** Inverse Cumulative Probability Density Function. */
     ICDF,
     /** Survival Probability Function. */
-    Survival
+    Survival,
+    /** Inverse Survival Probability Function. */
+    ISF
 }
diff --git a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/DistributionOptions.java b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/DistributionOptions.java
index 4d8092e..a193d31 100644
--- a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/DistributionOptions.java
+++ b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/DistributionOptions.java
@@ -46,7 +46,7 @@ class DistributionOptions {
     protected File inputFile;
 
     /** Flag indicating if an exception should be suppressed during function evaluation.
-     * Exceptions are thrown by the ICDF function when the input probability is not in
+     * Exceptions are thrown by the ICDF and ISF functions when the input probability is not in
      * the interval {@code [0, 1]}. */
     @Option(names = { "--no-ex", "--no-exception" },
             description = {"Suppress function evaluation exceptions (returns NaN or integer min value)."})
diff --git a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/DistributionUtils.java b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/DistributionUtils.java
index df3099b..1a87dda 100644
--- a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/DistributionUtils.java
+++ b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/DistributionUtils.java
@@ -24,6 +24,7 @@ import java.io.UncheckedIOException;
 import java.nio.file.Files;
 import java.util.Arrays;
 import java.util.List;
+import org.apache.commons.numbers.core.Precision;
 import org.apache.commons.statistics.distribution.ContinuousDistribution;
 import org.apache.commons.statistics.distribution.DiscreteDistribution;
 
@@ -33,6 +34,10 @@ import org.apache.commons.statistics.distribution.DiscreteDistribution;
 final class DistributionUtils {
     /** Message prefix for an unknown function. */
     private static final String UNKNOWN_FUNCTION = "Unknown function: ";
+    /** Maximum relative error for equality in the 'check' command. */
+    private static final double MAX_RELATIVE_ERROR = 1e-6;
+    /** Maximum absolute error for equality to 0 or 1 for a probability. */
+    private static final double DELTA_P = 1e-6;
 
     /** No public construction. */
     private DistributionUtils() {}
@@ -217,6 +222,12 @@ final class DistributionUtils {
                 if (!(upper == dd.inverseCumulativeProbability(1))) {
                     out.printf("%s upper icdf(1.0) : %s != %s", title, upper, dd.inverseCumulativeProbability(1));
                 }
+                if (!(lower == dd.inverseSurvivalProbability(1))) {
+                    out.printf("%s lower isf(1.0) : %s != %s", title, lower, dd.inverseSurvivalProbability(1));
+                }
+                if (!(upper == dd.inverseSurvivalProbability(0))) {
+                    out.printf("%s upper isf(0.0) : %s != %s", title, upper, dd.inverseSurvivalProbability(0));
+                }
                 // Validate CDF + Survival == 1
                 for (final double x : points) {
                     final double p1 = dd.cumulativeProbability(x);
@@ -226,23 +237,35 @@ final class DistributionUtils {
                         out.printf("%s x=%s : cdf + survival != 1.0 : %s + %s%n", title, x, p1, p2);
                     }
                     // Verify x = icdf(cdf(x)). Ignore p-values close to the bounds.
-                    if (p1 <= 1e-6 || p1 >= 1.0 - 1e-6) {
-                        continue;
+                    if (!closeToInteger(p1)) {
+                        final double xx = dd.inverseCumulativeProbability(p1);
+                        if (!Precision.equalsWithRelativeTolerance(x, xx, MAX_RELATIVE_ERROR) &&
+                            // The inverse may not be a bijection, check forward again
+                            !Precision.equalsWithRelativeTolerance(p1, dd.cumulativeProbability(xx),
+                                                                   MAX_RELATIVE_ERROR)) {
+                            out.printf("%s x=%s : icdf(%s) : %s (cdf=%s)%n", title, x, p1, xx,
+                                dd.cumulativeProbability(xx));
+                        }
                     }
-                    final double xx = dd.inverseCumulativeProbability(p1);
-                    final double dx = Math.abs(x - xx);
-                    // Absolute or relative error
-                    if (!(dx < 1e-10 || dx / Math.abs(x) < 1e-5)) {
-                        out.printf("%s x=%s : icdf(%s) : %s (cdf=%s)%n", title, x, p1, xx,
-                            dd.cumulativeProbability(xx));
+                    // Verify x = isf(sf(x)). Ignore p-values close to the bounds.
+                    if (!closeToInteger(p2)) {
+                        final double xx = dd.inverseSurvivalProbability(p2);
+                        if (!Precision.equalsWithRelativeTolerance(x, xx, MAX_RELATIVE_ERROR) &&
+                            // The inverse may not be a bijection, check forward again
+                            !Precision.equalsWithRelativeTolerance(p2, dd.survivalProbability(xx),
+                                                                   MAX_RELATIVE_ERROR)) {
+                            out.printf("%s x=%s : isf(%s) : %s (sf=%s)%n", title, x, p2, xx,
+                                dd.survivalProbability(xx));
+                        }
                     }
                 }
                 // Validate pdf and logpdf
                 for (final double x : points) {
                     final double p1 = dd.density(x);
-                    final double p2 = dd.logDensity(x);
-                    if (!(Math.abs(Math.exp(p2) - p1) < 1e-10)) {
-                        out.printf("%s x=%s : pdf != exp(logpdf) : %s + %s%n", title, x, p1, Math.exp(p2));
+                    final double lp = dd.logDensity(x);
+                    final double p2 = Math.exp(lp);
+                    if (!Precision.equalsWithRelativeTolerance(p1, p2, MAX_RELATIVE_ERROR)) {
+                        out.printf("%s x=%s : pdf != exp(logpdf) : %s != %s%n", title, x, p1, p2);
                     }
                 }
             });
@@ -273,6 +296,12 @@ final class DistributionUtils {
                 if (!(upper == dd.inverseCumulativeProbability(1))) {
                     out.printf("%s upper != icdf(1.0) : %d != %d", title, upper, dd.inverseCumulativeProbability(1));
                 }
+                if (!(lower == dd.inverseSurvivalProbability(1))) {
+                    out.printf("%s lower isf(1.0) : %d != %d", title, lower, dd.inverseSurvivalProbability(1));
+                }
+                if (!(upper == dd.inverseSurvivalProbability(0))) {
+                    out.printf("%s upper isf(0.0) : %d != %d", title, upper, dd.inverseSurvivalProbability(0));
+                }
                 // Validate CDF + Survival == 1
                 for (final int x : points) {
                     final double p1 = dd.cumulativeProbability(x);
@@ -282,21 +311,29 @@ final class DistributionUtils {
                         out.printf("%s x=%d : cdf + survival != 1.0 : %s + %s%n", title, x, p1, p2);
                     }
                     // Verify x = icdf(cdf(x)). Ignore p-values close to the bounds.
-                    if (p1 <= 1e-6 || p1 >= 1.0 - 1e-6) {
-                        continue;
+                    if (!closeToInteger(p1)) {
+                        final int xx = dd.inverseCumulativeProbability(p1);
+                        if (x != xx) {
+                            out.printf("%s x=%d : icdf(%s) : %d (cdf=%s)%n", title, x, p1, xx,
+                                dd.cumulativeProbability(xx));
+                        }
                     }
-                    final int xx = dd.inverseCumulativeProbability(p1);
-                    if (x != xx) {
-                        out.printf("%s x=%d : icdf(%s) : %d (cdf=%s)%n", title, x, p1, xx,
-                            dd.cumulativeProbability(xx));
+                    // Verify x = isf(sf(x)). Ignore p-values close to the bounds.
+                    if (!closeToInteger(p2)) {
+                        final int xx = dd.inverseSurvivalProbability(p2);
+                        if (x != xx) {
+                            out.printf("%s x=%d : isf(%s) : %d (sf=%s)%n", title, x, p2, xx,
+                                dd.survivalProbability(xx));
+                        }
                     }
                 }
                 // Validate pmf and logpmf
                 for (final int x : points) {
                     final double p1 = dd.probability(x);
-                    final double p2 = dd.logProbability(x);
-                    if (!(Math.abs(Math.exp(p2) - p1) < 1e-10)) {
-                        out.printf("%s x=%d : pmf != exp(logpmf) : %s + %s%n", title, x, p1, Math.exp(p2));
+                    final double lp = dd.logProbability(x);
+                    final double p2 = Math.exp(lp);
+                    if (!Precision.equalsWithRelativeTolerance(p1, p2, MAX_RELATIVE_ERROR)) {
+                        out.printf("%s x=%d : pmf != exp(logpmf) : %s != %s%n", title, x, p1, p2);
                     }
                 }
             });
@@ -445,14 +482,25 @@ final class DistributionUtils {
      * @return the function
      */
     private static ContinuousFunction createFunction(InverseContinuousDistributionOptions distributionOptions) {
+        ContinuousFunction f;
+        switch (distributionOptions.distributionFunction) {
+        case ICDF:
+            f = ContinuousDistribution::inverseCumulativeProbability;
+            break;
+        case ISF:
+            f = ContinuousDistribution::inverseSurvivalProbability;
+            break;
+        default:
+            throw new IllegalArgumentException(UNKNOWN_FUNCTION + distributionOptions.distributionFunction);
+        }
         if (!distributionOptions.suppressException) {
-            return ContinuousDistribution::inverseCumulativeProbability;
+            return f;
         }
         return new ContinuousFunction() {
             @Override
             public double apply(ContinuousDistribution dist, double x) {
                 try {
-                    return dist.inverseCumulativeProbability(x);
+                    return f.apply(dist, x);
                 } catch (IllegalArgumentException ex) {
                     // Ignore
                     return Double.NaN;
@@ -468,14 +516,25 @@ final class DistributionUtils {
      * @return the function
      */
     private static InverseDiscreteFunction createFunction(InverseDiscreteDistributionOptions distributionOptions) {
+        InverseDiscreteFunction f;
+        switch (distributionOptions.distributionFunction) {
+        case ICDF:
+            f = DiscreteDistribution::inverseCumulativeProbability;
+            break;
+        case ISF:
+            f = DiscreteDistribution::inverseSurvivalProbability;
+            break;
+        default:
+            throw new IllegalArgumentException(UNKNOWN_FUNCTION + distributionOptions.distributionFunction);
+        }
         if (!distributionOptions.suppressException) {
-            return DiscreteDistribution::inverseCumulativeProbability;
+            return f;
         }
         return new InverseDiscreteFunction() {
             @Override
             public int apply(DiscreteDistribution dist, double x) {
                 try {
-                    return dist.inverseCumulativeProbability(x);
+                    return f.apply(dist, x);
                 } catch (IllegalArgumentException ex) {
                     // Ignore
                     return Integer.MIN_VALUE;
@@ -694,4 +753,14 @@ final class DistributionUtils {
         }
         return array;
     }
+
+    /**
+     * Check if the value is close to an integer.
+     *
+     * @param p Value
+     * @return true if within a tolerance of an integer
+     */
+    private static boolean closeToInteger(double p) {
+        return Math.abs(Math.rint(p) - p) < DELTA_P;
+    }
 }
diff --git a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/ExpCommand.java b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/ExpCommand.java
index 00cafcb..ffd7b58 100644
--- a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/ExpCommand.java
+++ b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/ExpCommand.java
@@ -36,6 +36,7 @@ import picocli.CommandLine.Option;
              ExpCommand.CDF.class,
              ExpCommand.Survival.class,
              ExpCommand.ICDF.class,
+             ExpCommand.ISF.class,
          })
 class ExpCommand extends AbstractDistributionCommand {
 
@@ -88,6 +89,18 @@ class ExpCommand extends AbstractDistributionCommand {
         }
     }
 
+    /** Base command for the distribution that defines the parameters for inverse probability functions. */
+    private abstract static class InverseProbabilityCommand extends BaseCommand {
+        /** The distribution options. */
+        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
+        private InverseContinuousDistributionOptions distributionOptions = new InverseContinuousDistributionOptions();
+
+        @Override
+        protected DistributionOptions getDistributionOptions() {
+            return distributionOptions;
+        }
+    }
+
     /** Verification checks command. */
     @Command(name = "check",
              hidden = true,
@@ -117,14 +130,10 @@ class ExpCommand extends AbstractDistributionCommand {
     /** ICDF command. */
     @Command(name = "icdf",
              description = "Exponential distribution inverse CDF.")
-    static class ICDF extends BaseCommand {
-        /** The distribution options. */
-        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
-        private InverseContinuousDistributionOptions distributionOptions = new InverseContinuousDistributionOptions();
+    static class ICDF extends InverseProbabilityCommand {}
 
-        @Override
-        protected DistributionOptions getDistributionOptions() {
-            return distributionOptions;
-        }
-    }
+    /** ISF command. */
+    @Command(name = "isf",
+             description = "Exponential distribution inverse SF.")
+    static class ISF extends InverseProbabilityCommand {}
 }
diff --git a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/FCommand.java b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/FCommand.java
index fb5c354..4299e4b 100644
--- a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/FCommand.java
+++ b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/FCommand.java
@@ -36,6 +36,7 @@ import picocli.CommandLine.Option;
              FCommand.CDF.class,
              FCommand.Survival.class,
              FCommand.ICDF.class,
+             FCommand.ISF.class,
          })
 class FCommand extends AbstractDistributionCommand {
 
@@ -102,6 +103,18 @@ class FCommand extends AbstractDistributionCommand {
         }
     }
 
+    /** Base command for the distribution that defines the parameters for inverse probability functions. */
+    private abstract static class InverseProbabilityCommand extends BaseCommand {
+        /** The distribution options. */
+        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
+        private InverseContinuousDistributionOptions distributionOptions = new InverseContinuousDistributionOptions();
+
+        @Override
+        protected DistributionOptions getDistributionOptions() {
+            return distributionOptions;
+        }
+    }
+
     /** Verification checks command. */
     @Command(name = "check",
              hidden = true,
@@ -131,14 +144,10 @@ class FCommand extends AbstractDistributionCommand {
     /** ICDF command. */
     @Command(name = "icdf",
              description = "F distribution inverse CDF.")
-    static class ICDF extends BaseCommand {
-        /** The distribution options. */
-        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
-        private InverseContinuousDistributionOptions distributionOptions = new InverseContinuousDistributionOptions();
+    static class ICDF extends InverseProbabilityCommand {}
 
-        @Override
-        protected DistributionOptions getDistributionOptions() {
-            return distributionOptions;
-        }
-    }
+    /** ISF command. */
+    @Command(name = "isf",
+             description = "F distribution inverse SF.")
+    static class ISF extends InverseProbabilityCommand {}
 }
diff --git a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/GammaCommand.java b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/GammaCommand.java
index e592af7..3fdc22d 100644
--- a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/GammaCommand.java
+++ b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/GammaCommand.java
@@ -37,6 +37,7 @@ import picocli.CommandLine.Option;
              GammaCommand.CDF.class,
              GammaCommand.Survival.class,
              GammaCommand.ICDF.class,
+             GammaCommand.ISF.class,
          })
 class GammaCommand extends AbstractDistributionCommand {
 
@@ -103,6 +104,18 @@ class GammaCommand extends AbstractDistributionCommand {
         }
     }
 
+    /** Base command for the distribution that defines the parameters for inverse probability functions. */
+    private abstract static class InverseProbabilityCommand extends BaseCommand {
+        /** The distribution options. */
+        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
+        private InverseContinuousDistributionOptions distributionOptions = new InverseContinuousDistributionOptions();
+
+        @Override
+        protected DistributionOptions getDistributionOptions() {
+            return distributionOptions;
+        }
+    }
+
     /** Verification checks command. */
     @Command(name = "check",
              hidden = true,
@@ -132,14 +145,10 @@ class GammaCommand extends AbstractDistributionCommand {
     /** ICDF command. */
     @Command(name = "icdf",
              description = "Gamma distribution inverse CDF.")
-    static class ICDF extends BaseCommand {
-        /** The distribution options. */
-        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
-        private InverseContinuousDistributionOptions distributionOptions = new InverseContinuousDistributionOptions();
+    static class ICDF extends InverseProbabilityCommand {}
 
-        @Override
-        protected DistributionOptions getDistributionOptions() {
-            return distributionOptions;
-        }
-    }
+    /** ISF command. */
+    @Command(name = "isf",
+             description = "Gamma distribution inverse SF.")
+    static class ISF extends InverseProbabilityCommand {}
 }
diff --git a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/GeometricCommand.java b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/GeometricCommand.java
index b2506fc..34b4df3 100644
--- a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/GeometricCommand.java
+++ b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/GeometricCommand.java
@@ -37,6 +37,7 @@ import picocli.CommandLine.Option;
              GeometricCommand.CDF.class,
              GeometricCommand.Survival.class,
              GeometricCommand.ICDF.class,
+             GeometricCommand.ISF.class,
          })
 class GeometricCommand extends AbstractDistributionCommand {
 
@@ -89,6 +90,18 @@ class GeometricCommand extends AbstractDistributionCommand {
         }
     }
 
+    /** Base command for the distribution that defines the parameters for inverse probability functions. */
+    private abstract static class InverseProbabilityCommand extends BaseCommand {
+        /** The distribution options. */
+        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
+        private InverseDiscreteDistributionOptions distributionOptions = new InverseDiscreteDistributionOptions();
+
+        @Override
+        protected DistributionOptions getDistributionOptions() {
+            return distributionOptions;
+        }
+    }
+
     /** Verification checks command. */
     @Command(name = "check",
              hidden = true,
@@ -120,14 +133,10 @@ class GeometricCommand extends AbstractDistributionCommand {
     /** ICDF command. */
     @Command(name = "icdf",
              description = "Geometric distribution inverse CDF.")
-    static class ICDF extends BaseCommand {
-        /** The distribution options. */
-        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
-        private InverseDiscreteDistributionOptions distributionOptions = new InverseDiscreteDistributionOptions();
+    static class ICDF extends InverseProbabilityCommand {}
 
-        @Override
-        protected DistributionOptions getDistributionOptions() {
-            return distributionOptions;
-        }
-    }
+    /** ISF command. */
+    @Command(name = "isf",
+             description = "Geometric distribution inverse SF.")
+    static class ISF extends InverseProbabilityCommand {}
 }
diff --git a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/GumbelCommand.java b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/GumbelCommand.java
index 2d4ec2b..9179dcb 100644
--- a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/GumbelCommand.java
+++ b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/GumbelCommand.java
@@ -36,6 +36,7 @@ import picocli.CommandLine.Option;
              GumbelCommand.CDF.class,
              GumbelCommand.Survival.class,
              GumbelCommand.ICDF.class,
+             GumbelCommand.ISF.class,
          })
 class GumbelCommand extends AbstractDistributionCommand {
 
@@ -102,6 +103,18 @@ class GumbelCommand extends AbstractDistributionCommand {
         }
     }
 
+    /** Base command for the distribution that defines the parameters for inverse probability functions. */
+    private abstract static class InverseProbabilityCommand extends BaseCommand {
+        /** The distribution options. */
+        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
+        private InverseContinuousDistributionOptions distributionOptions = new InverseContinuousDistributionOptions();
+
+        @Override
+        protected DistributionOptions getDistributionOptions() {
+            return distributionOptions;
+        }
+    }
+
     /** Verification checks command. */
     @Command(name = "check",
              hidden = true,
@@ -131,14 +144,10 @@ class GumbelCommand extends AbstractDistributionCommand {
     /** ICDF command. */
     @Command(name = "icdf",
              description = "Gumbel distribution inverse CDF.")
-    static class ICDF extends BaseCommand {
-        /** The distribution options. */
-        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
-        private InverseContinuousDistributionOptions distributionOptions = new InverseContinuousDistributionOptions();
+    static class ICDF extends InverseProbabilityCommand {}
 
-        @Override
-        protected DistributionOptions getDistributionOptions() {
-            return distributionOptions;
-        }
-    }
+    /** ISF command. */
+    @Command(name = "isf",
+             description = "Gumbel distribution inverse SF.")
+    static class ISF extends InverseProbabilityCommand {}
 }
diff --git a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/HypergeometricCommand.java b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/HypergeometricCommand.java
index 4d9561c..d6573cc 100644
--- a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/HypergeometricCommand.java
+++ b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/HypergeometricCommand.java
@@ -37,6 +37,7 @@ import picocli.CommandLine.Option;
              HypergeometricCommand.CDF.class,
              HypergeometricCommand.Survival.class,
              HypergeometricCommand.ICDF.class,
+             HypergeometricCommand.ISF.class,
          })
 class HypergeometricCommand extends AbstractDistributionCommand {
 
@@ -114,6 +115,18 @@ class HypergeometricCommand extends AbstractDistributionCommand {
         }
     }
 
+    /** Base command for the distribution that defines the parameters for inverse probability functions. */
+    private abstract static class InverseProbabilityCommand extends BaseCommand {
+        /** The distribution options. */
+        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
+        private InverseDiscreteDistributionOptions distributionOptions = new InverseDiscreteDistributionOptions();
+
+        @Override
+        protected DistributionOptions getDistributionOptions() {
+            return distributionOptions;
+        }
+    }
+
     /** Verification checks command. */
     @Command(name = "check",
              hidden = true,
@@ -145,14 +158,10 @@ class HypergeometricCommand extends AbstractDistributionCommand {
     /** ICDF command. */
     @Command(name = "icdf",
              description = "Hypergeometric distribution inverse CDF.")
-    static class ICDF extends BaseCommand {
-        /** The distribution options. */
-        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
-        private InverseDiscreteDistributionOptions distributionOptions = new InverseDiscreteDistributionOptions();
+    static class ICDF extends InverseProbabilityCommand {}
 
-        @Override
-        protected DistributionOptions getDistributionOptions() {
-            return distributionOptions;
-        }
-    }
+    /** ISF command. */
+    @Command(name = "isf",
+             description = "Hypergeometric distribution inverse SF.")
+    static class ISF extends InverseProbabilityCommand {}
 }
diff --git a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/LaplaceCommand.java b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/LaplaceCommand.java
index ef8a02f..b9aa4f5 100644
--- a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/LaplaceCommand.java
+++ b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/LaplaceCommand.java
@@ -36,6 +36,7 @@ import picocli.CommandLine.Option;
              LaplaceCommand.CDF.class,
              LaplaceCommand.Survival.class,
              LaplaceCommand.ICDF.class,
+             LaplaceCommand.ISF.class,
          })
 class LaplaceCommand extends AbstractDistributionCommand {
 
@@ -102,6 +103,18 @@ class LaplaceCommand extends AbstractDistributionCommand {
         }
     }
 
+    /** Base command for the distribution that defines the parameters for inverse probability functions. */
+    private abstract static class InverseProbabilityCommand extends BaseCommand {
+        /** The distribution options. */
+        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
+        private InverseContinuousDistributionOptions distributionOptions = new InverseContinuousDistributionOptions();
+
+        @Override
+        protected DistributionOptions getDistributionOptions() {
+            return distributionOptions;
+        }
+    }
+
     /** Verification checks command. */
     @Command(name = "check",
              hidden = true,
@@ -131,14 +144,10 @@ class LaplaceCommand extends AbstractDistributionCommand {
     /** ICDF command. */
     @Command(name = "icdf",
              description = "Laplace distribution inverse CDF.")
-    static class ICDF extends BaseCommand {
-        /** The distribution options. */
-        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
-        private InverseContinuousDistributionOptions distributionOptions = new InverseContinuousDistributionOptions();
+    static class ICDF extends InverseProbabilityCommand {}
 
-        @Override
-        protected DistributionOptions getDistributionOptions() {
-            return distributionOptions;
-        }
-    }
+    /** ISF command. */
+    @Command(name = "isf",
+             description = "Laplace distribution inverse SF.")
+    static class ISF extends InverseProbabilityCommand {}
 }
diff --git a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/LevyCommand.java b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/LevyCommand.java
index 7913205..0b9b831 100644
--- a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/LevyCommand.java
+++ b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/LevyCommand.java
@@ -36,6 +36,7 @@ import picocli.CommandLine.Option;
              LevyCommand.CDF.class,
              LevyCommand.Survival.class,
              LevyCommand.ICDF.class,
+             LevyCommand.ISF.class,
          })
 class LevyCommand extends AbstractDistributionCommand {
 
@@ -102,6 +103,18 @@ class LevyCommand extends AbstractDistributionCommand {
         }
     }
 
+    /** Base command for the distribution that defines the parameters for inverse probability functions. */
+    private abstract static class InverseProbabilityCommand extends BaseCommand {
+        /** The distribution options. */
+        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
+        private InverseContinuousDistributionOptions distributionOptions = new InverseContinuousDistributionOptions();
+
+        @Override
+        protected DistributionOptions getDistributionOptions() {
+            return distributionOptions;
+        }
+    }
+
     /** Verification checks command. */
     @Command(name = "check",
              hidden = true,
@@ -131,14 +144,10 @@ class LevyCommand extends AbstractDistributionCommand {
     /** ICDF command. */
     @Command(name = "icdf",
              description = "Levy distribution inverse CDF.")
-    static class ICDF extends BaseCommand {
-        /** The distribution options. */
-        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
-        private InverseContinuousDistributionOptions distributionOptions = new InverseContinuousDistributionOptions();
+    static class ICDF extends InverseProbabilityCommand {}
 
-        @Override
-        protected DistributionOptions getDistributionOptions() {
-            return distributionOptions;
-        }
-    }
+    /** ISF command. */
+    @Command(name = "isf",
+             description = "Levy distribution inverse SF.")
+    static class ISF extends InverseProbabilityCommand {}
 }
diff --git a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/LogNormalCommand.java b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/LogNormalCommand.java
index acc7ac1..4d6c786 100644
--- a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/LogNormalCommand.java
+++ b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/LogNormalCommand.java
@@ -37,6 +37,7 @@ import picocli.CommandLine.Option;
              LogNormalCommand.CDF.class,
              LogNormalCommand.Survival.class,
              LogNormalCommand.ICDF.class,
+             LogNormalCommand.ISF.class,
          })
 class LogNormalCommand extends AbstractDistributionCommand {
 
@@ -103,6 +104,18 @@ class LogNormalCommand extends AbstractDistributionCommand {
         }
     }
 
+    /** Base command for the distribution that defines the parameters for inverse probability functions. */
+    private abstract static class InverseProbabilityCommand extends BaseCommand {
+        /** The distribution options. */
+        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
+        private InverseContinuousDistributionOptions distributionOptions = new InverseContinuousDistributionOptions();
+
+        @Override
+        protected DistributionOptions getDistributionOptions() {
+            return distributionOptions;
+        }
+    }
+
     /** Verification checks command. */
     @Command(name = "check",
              hidden = true,
@@ -132,14 +145,10 @@ class LogNormalCommand extends AbstractDistributionCommand {
     /** ICDF command. */
     @Command(name = "icdf",
              description = "Log-normal distribution inverse CDF.")
-    static class ICDF extends BaseCommand {
-        /** The distribution options. */
-        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
-        private InverseContinuousDistributionOptions distributionOptions = new InverseContinuousDistributionOptions();
+    static class ICDF extends InverseProbabilityCommand {}
 
-        @Override
-        protected DistributionOptions getDistributionOptions() {
-            return distributionOptions;
-        }
-    }
+    /** ISF command. */
+    @Command(name = "isf",
+             description = "Log-normal distribution inverse SF.")
+    static class ISF extends InverseProbabilityCommand {}
 }
diff --git a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/LogisticCommand.java b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/LogisticCommand.java
index b90c7a3..0c14a2f 100644
--- a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/LogisticCommand.java
+++ b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/LogisticCommand.java
@@ -36,6 +36,7 @@ import picocli.CommandLine.Option;
              LogisticCommand.CDF.class,
              LogisticCommand.Survival.class,
              LogisticCommand.ICDF.class,
+             LogisticCommand.ISF.class,
          })
 class LogisticCommand extends AbstractDistributionCommand {
 
@@ -102,6 +103,18 @@ class LogisticCommand extends AbstractDistributionCommand {
         }
     }
 
+    /** Base command for the distribution that defines the parameters for inverse probability functions. */
+    private abstract static class InverseProbabilityCommand extends BaseCommand {
+        /** The distribution options. */
+        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
+        private InverseContinuousDistributionOptions distributionOptions = new InverseContinuousDistributionOptions();
+
+        @Override
+        protected DistributionOptions getDistributionOptions() {
+            return distributionOptions;
+        }
+    }
+
     /** Verification checks command. */
     @Command(name = "check",
              hidden = true,
@@ -131,14 +144,10 @@ class LogisticCommand extends AbstractDistributionCommand {
     /** ICDF command. */
     @Command(name = "icdf",
              description = "Logistic distribution inverse CDF.")
-    static class ICDF extends BaseCommand {
-        /** The distribution options. */
-        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
-        private InverseContinuousDistributionOptions distributionOptions = new InverseContinuousDistributionOptions();
+    static class ICDF extends InverseProbabilityCommand {}
 
-        @Override
-        protected DistributionOptions getDistributionOptions() {
-            return distributionOptions;
-        }
-    }
+    /** ISF command. */
+    @Command(name = "isf",
+             description = "Logistic distribution inverse SF.")
+    static class ISF extends InverseProbabilityCommand {}
 }
diff --git a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/NakagamiCommand.java b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/NakagamiCommand.java
index e0ff5aa..a447b16 100644
--- a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/NakagamiCommand.java
+++ b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/NakagamiCommand.java
@@ -36,6 +36,7 @@ import picocli.CommandLine.Option;
              NakagamiCommand.CDF.class,
              NakagamiCommand.Survival.class,
              NakagamiCommand.ICDF.class,
+             NakagamiCommand.ISF.class,
          })
 class NakagamiCommand extends AbstractDistributionCommand {
 
@@ -102,6 +103,18 @@ class NakagamiCommand extends AbstractDistributionCommand {
         }
     }
 
+    /** Base command for the distribution that defines the parameters for inverse probability functions. */
+    private abstract static class InverseProbabilityCommand extends BaseCommand {
+        /** The distribution options. */
+        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
+        private InverseContinuousDistributionOptions distributionOptions = new InverseContinuousDistributionOptions();
+
+        @Override
+        protected DistributionOptions getDistributionOptions() {
+            return distributionOptions;
+        }
+    }
+
     /** Verification checks command. */
     @Command(name = "check",
              hidden = true,
@@ -131,14 +144,10 @@ class NakagamiCommand extends AbstractDistributionCommand {
     /** ICDF command. */
     @Command(name = "icdf",
              description = "Nakagami distribution inverse CDF.")
-    static class ICDF extends BaseCommand {
-        /** The distribution options. */
-        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
-        private InverseContinuousDistributionOptions distributionOptions = new InverseContinuousDistributionOptions();
+    static class ICDF extends InverseProbabilityCommand {}
 
-        @Override
-        protected DistributionOptions getDistributionOptions() {
-            return distributionOptions;
-        }
-    }
+    /** ISF command. */
+    @Command(name = "isf",
+             description = "Nakagami distribution inverse SF.")
+    static class ISF extends InverseProbabilityCommand {}
 }
diff --git a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/NormalCommand.java b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/NormalCommand.java
index 9ef931a..ad39a9a 100644
--- a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/NormalCommand.java
+++ b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/NormalCommand.java
@@ -37,6 +37,7 @@ import picocli.CommandLine.Option;
              NormalCommand.CDF.class,
              NormalCommand.Survival.class,
              NormalCommand.ICDF.class,
+             NormalCommand.ISF.class,
          })
 class NormalCommand extends AbstractDistributionCommand {
 
@@ -104,6 +105,18 @@ class NormalCommand extends AbstractDistributionCommand {
         }
     }
 
+    /** Base command for the distribution that defines the parameters for inverse probability functions. */
+    private abstract static class InverseProbabilityCommand extends BaseCommand {
+        /** The distribution options. */
+        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
+        private InverseContinuousDistributionOptions distributionOptions = new InverseContinuousDistributionOptions();
+
+        @Override
+        protected DistributionOptions getDistributionOptions() {
+            return distributionOptions;
+        }
+    }
+
     /** Verification checks command. */
     @Command(name = "check",
              hidden = true,
@@ -133,14 +146,10 @@ class NormalCommand extends AbstractDistributionCommand {
     /** ICDF command. */
     @Command(name = "icdf",
              description = "Normal distribution inverse CDF.")
-    static class ICDF extends BaseCommand {
-        /** The distribution options. */
-        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
-        private InverseContinuousDistributionOptions distributionOptions = new InverseContinuousDistributionOptions();
+    static class ICDF extends InverseProbabilityCommand {}
 
-        @Override
-        protected DistributionOptions getDistributionOptions() {
-            return distributionOptions;
-        }
-    }
+    /** ISF command. */
+    @Command(name = "isf",
+             description = "Normal distribution inverse SF.")
+    static class ISF extends InverseProbabilityCommand {}
 }
diff --git a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/ParetoCommand.java b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/ParetoCommand.java
index 025d6e2..8bd305a 100644
--- a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/ParetoCommand.java
+++ b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/ParetoCommand.java
@@ -36,6 +36,7 @@ import picocli.CommandLine.Option;
              ParetoCommand.CDF.class,
              ParetoCommand.Survival.class,
              ParetoCommand.ICDF.class,
+             ParetoCommand.ISF.class,
          })
 class ParetoCommand extends AbstractDistributionCommand {
 
@@ -103,6 +104,18 @@ class ParetoCommand extends AbstractDistributionCommand {
         }
     }
 
+    /** Base command for the distribution that defines the parameters for inverse probability functions. */
+    private abstract static class InverseProbabilityCommand extends BaseCommand {
+        /** The distribution options. */
+        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
+        private InverseContinuousDistributionOptions distributionOptions = new InverseContinuousDistributionOptions();
+
+        @Override
+        protected DistributionOptions getDistributionOptions() {
+            return distributionOptions;
+        }
+    }
+
     /** Verification checks command. */
     @Command(name = "check",
              hidden = true,
@@ -132,14 +145,10 @@ class ParetoCommand extends AbstractDistributionCommand {
     /** ICDF command. */
     @Command(name = "icdf",
              description = "Pareto distribution inverse CDF.")
-    static class ICDF extends BaseCommand {
-        /** The distribution options. */
-        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
-        private InverseContinuousDistributionOptions distributionOptions = new InverseContinuousDistributionOptions();
+    static class ICDF extends InverseProbabilityCommand {}
 
-        @Override
-        protected DistributionOptions getDistributionOptions() {
-            return distributionOptions;
-        }
-    }
+    /** ISF command. */
+    @Command(name = "isf",
+             description = "Pareto distribution inverse SF.")
+    static class ISF extends InverseProbabilityCommand {}
 }
diff --git a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/PascalCommand.java b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/PascalCommand.java
index 1ddc41a..888b2d9 100644
--- a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/PascalCommand.java
+++ b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/PascalCommand.java
@@ -37,6 +37,7 @@ import picocli.CommandLine.Option;
              PascalCommand.CDF.class,
              PascalCommand.Survival.class,
              PascalCommand.ICDF.class,
+             PascalCommand.ISF.class,
          })
 class PascalCommand extends AbstractDistributionCommand {
 
@@ -106,6 +107,18 @@ class PascalCommand extends AbstractDistributionCommand {
         }
     }
 
+    /** Base command for the distribution that defines the parameters for inverse probability functions. */
+    private abstract static class InverseProbabilityCommand extends BaseCommand {
+        /** The distribution options. */
+        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
+        private InverseDiscreteDistributionOptions distributionOptions = new InverseDiscreteDistributionOptions();
+
+        @Override
+        protected DistributionOptions getDistributionOptions() {
+            return distributionOptions;
+        }
+    }
+
     /** Verification checks command. */
     @Command(name = "check",
              hidden = true,
@@ -137,14 +150,10 @@ class PascalCommand extends AbstractDistributionCommand {
     /** ICDF command. */
     @Command(name = "icdf",
              description = "Pascal distribution inverse CDF.")
-    static class ICDF extends BaseCommand {
-        /** The distribution options. */
-        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
-        private InverseDiscreteDistributionOptions distributionOptions = new InverseDiscreteDistributionOptions();
+    static class ICDF extends InverseProbabilityCommand {}
 
-        @Override
-        protected DistributionOptions getDistributionOptions() {
-            return distributionOptions;
-        }
-    }
+    /** ISF command. */
+    @Command(name = "isf",
+             description = "Pascal distribution inverse SF.")
+    static class ISF extends InverseProbabilityCommand {}
 }
diff --git a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/PoissonCommand.java b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/PoissonCommand.java
index bcb6800..b02b903 100644
--- a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/PoissonCommand.java
+++ b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/PoissonCommand.java
@@ -37,6 +37,7 @@ import picocli.CommandLine.Option;
              PoissonCommand.CDF.class,
              PoissonCommand.Survival.class,
              PoissonCommand.ICDF.class,
+             PoissonCommand.ISF.class,
          })
 class PoissonCommand extends AbstractDistributionCommand {
 
@@ -89,6 +90,18 @@ class PoissonCommand extends AbstractDistributionCommand {
         }
     }
 
+    /** Base command for the distribution that defines the parameters for inverse probability functions. */
+    private abstract static class InverseProbabilityCommand extends BaseCommand {
+        /** The distribution options. */
+        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
+        private InverseDiscreteDistributionOptions distributionOptions = new InverseDiscreteDistributionOptions();
+
+        @Override
+        protected DistributionOptions getDistributionOptions() {
+            return distributionOptions;
+        }
+    }
+
     /** Verification checks command. */
     @Command(name = "check",
              hidden = true,
@@ -120,14 +133,10 @@ class PoissonCommand extends AbstractDistributionCommand {
     /** ICDF command. */
     @Command(name = "icdf",
              description = "Poisson distribution inverse CDF.")
-    static class ICDF extends BaseCommand {
-        /** The distribution options. */
-        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
-        private InverseDiscreteDistributionOptions distributionOptions = new InverseDiscreteDistributionOptions();
+    static class ICDF extends InverseProbabilityCommand {}
 
-        @Override
-        protected DistributionOptions getDistributionOptions() {
-            return distributionOptions;
-        }
-    }
+    /** ISF command. */
+    @Command(name = "isf",
+             description = "Poisson distribution inverse SF.")
+    static class ISF extends InverseProbabilityCommand {}
 }
diff --git a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/TCommand.java b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/TCommand.java
index 80ddf76..3412501 100644
--- a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/TCommand.java
+++ b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/TCommand.java
@@ -36,6 +36,7 @@ import picocli.CommandLine.Option;
              TCommand.CDF.class,
              TCommand.Survival.class,
              TCommand.ICDF.class,
+             TCommand.ISF.class,
          })
 class TCommand extends AbstractDistributionCommand {
 
@@ -88,6 +89,18 @@ class TCommand extends AbstractDistributionCommand {
         }
     }
 
+    /** Base command for the distribution that defines the parameters for inverse probability functions. */
+    private abstract static class InverseProbabilityCommand extends BaseCommand {
+        /** The distribution options. */
+        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
+        private InverseContinuousDistributionOptions distributionOptions = new InverseContinuousDistributionOptions();
+
+        @Override
+        protected DistributionOptions getDistributionOptions() {
+            return distributionOptions;
+        }
+    }
+
     /** Verification checks command. */
     @Command(name = "check",
              hidden = true,
@@ -117,14 +130,10 @@ class TCommand extends AbstractDistributionCommand {
     /** ICDF command. */
     @Command(name = "icdf",
              description = "T distribution inverse CDF.")
-    static class ICDF extends BaseCommand {
-        /** The distribution options. */
-        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
-        private InverseContinuousDistributionOptions distributionOptions = new InverseContinuousDistributionOptions();
+    static class ICDF extends InverseProbabilityCommand {}
 
-        @Override
-        protected DistributionOptions getDistributionOptions() {
-            return distributionOptions;
-        }
-    }
+    /** ISF command. */
+    @Command(name = "isf",
+             description = "T distribution inverse SF.")
+    static class ISF extends InverseProbabilityCommand {}
 }
diff --git a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/TriangularCommand.java b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/TriangularCommand.java
index b8e757d..49b4ee8 100644
--- a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/TriangularCommand.java
+++ b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/TriangularCommand.java
@@ -37,6 +37,7 @@ import picocli.CommandLine.Option;
              TriangularCommand.CDF.class,
              TriangularCommand.Survival.class,
              TriangularCommand.ICDF.class,
+             TriangularCommand.ISF.class,
          })
 class TriangularCommand extends AbstractDistributionCommand {
 
@@ -115,6 +116,18 @@ class TriangularCommand extends AbstractDistributionCommand {
         }
     }
 
+    /** Base command for the distribution that defines the parameters for inverse probability functions. */
+    private abstract static class InverseProbabilityCommand extends BaseCommand {
+        /** The distribution options. */
+        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
+        private InverseContinuousDistributionOptions distributionOptions = new InverseContinuousDistributionOptions();
+
+        @Override
+        protected DistributionOptions getDistributionOptions() {
+            return distributionOptions;
+        }
+    }
+
     /** Verification checks command. */
     @Command(name = "check",
              hidden = true,
@@ -123,7 +136,7 @@ class TriangularCommand extends AbstractDistributionCommand {
 
     /** PDF command. */
     @Command(name = "pdf",
-             description = "Triangular PDF.")
+             description = "Triangular distribution PDF.")
     static class PDF extends ProbabilityCommand {}
 
     /** LPDF command. */
@@ -133,25 +146,21 @@ class TriangularCommand extends AbstractDistributionCommand {
 
     /** CDF command. */
     @Command(name = "cdf",
-             description = "Triangular CDF.")
+             description = "Triangular distribution CDF.")
     static class CDF extends ProbabilityCommand {}
 
     /** Survival command. */
     @Command(name = "survival", aliases = {"sur"},
-             description = "Triangular survival probability.")
+             description = "Triangular distribution survival probability.")
     static class Survival extends ProbabilityCommand {}
 
     /** ICDF command. */
     @Command(name = "icdf",
-             description = "Triangular inverse CDF.")
-    static class ICDF extends BaseCommand {
-        /** The distribution options. */
-        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
-        private InverseContinuousDistributionOptions distributionOptions = new InverseContinuousDistributionOptions();
+             description = "Triangular distribution inverse CDF.")
+    static class ICDF extends InverseProbabilityCommand {}
 
-        @Override
-        protected DistributionOptions getDistributionOptions() {
-            return distributionOptions;
-        }
-    }
+    /** ISF command. */
+    @Command(name = "isf",
+             description = "Triangular distribution inverse SF.")
+    static class ISF extends InverseProbabilityCommand {}
 }
diff --git a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/TruncatedNormalCommand.java b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/TruncatedNormalCommand.java
index 2f54b22..3cf0e61 100644
--- a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/TruncatedNormalCommand.java
+++ b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/TruncatedNormalCommand.java
@@ -37,6 +37,7 @@ import picocli.CommandLine.Option;
              TruncatedNormalCommand.CDF.class,
              TruncatedNormalCommand.Survival.class,
              TruncatedNormalCommand.ICDF.class,
+             TruncatedNormalCommand.ISF.class,
          })
 class TruncatedNormalCommand extends AbstractDistributionCommand {
 
@@ -127,6 +128,18 @@ class TruncatedNormalCommand extends AbstractDistributionCommand {
         }
     }
 
+    /** Base command for the distribution that defines the parameters for inverse probability functions. */
+    private abstract static class InverseProbabilityCommand extends BaseCommand {
+        /** The distribution options. */
+        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
+        private InverseContinuousDistributionOptions distributionOptions = new InverseContinuousDistributionOptions();
+
+        @Override
+        protected DistributionOptions getDistributionOptions() {
+            return distributionOptions;
+        }
+    }
+
     /** Verification checks command. */
     @Command(name = "check",
              hidden = true,
@@ -156,14 +169,10 @@ class TruncatedNormalCommand extends AbstractDistributionCommand {
     /** ICDF command. */
     @Command(name = "icdf",
              description = "Truncated normal distribution inverse CDF.")
-    static class ICDF extends BaseCommand {
-        /** The distribution options. */
-        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
-        private InverseContinuousDistributionOptions distributionOptions = new InverseContinuousDistributionOptions();
+    static class ICDF extends InverseProbabilityCommand {}
 
-        @Override
-        protected DistributionOptions getDistributionOptions() {
-            return distributionOptions;
-        }
-    }
+    /** ISF command. */
+    @Command(name = "isf",
+             description = "Truncated normal distribution inverse SF.")
+    static class ISF extends InverseProbabilityCommand {}
 }
diff --git a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/UniformContinuousCommand.java b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/UniformContinuousCommand.java
index ad3df78..ac4852d 100644
--- a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/UniformContinuousCommand.java
+++ b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/UniformContinuousCommand.java
@@ -37,6 +37,7 @@ import picocli.CommandLine.Option;
              UniformContinuousCommand.CDF.class,
              UniformContinuousCommand.Survival.class,
              UniformContinuousCommand.ICDF.class,
+             UniformContinuousCommand.ISF.class,
          })
 class UniformContinuousCommand extends AbstractDistributionCommand {
 
@@ -105,6 +106,18 @@ class UniformContinuousCommand extends AbstractDistributionCommand {
         }
     }
 
+    /** Base command for the distribution that defines the parameters for inverse probability functions. */
+    private abstract static class InverseProbabilityCommand extends BaseCommand {
+        /** The distribution options. */
+        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
+        private InverseContinuousDistributionOptions distributionOptions = new InverseContinuousDistributionOptions();
+
+        @Override
+        protected DistributionOptions getDistributionOptions() {
+            return distributionOptions;
+        }
+    }
+
     /** Verification checks command. */
     @Command(name = "check",
              hidden = true,
@@ -134,14 +147,10 @@ class UniformContinuousCommand extends AbstractDistributionCommand {
     /** ICDF command. */
     @Command(name = "icdf",
              description = "Continuous uniform distribution inverse CDF.")
-    static class ICDF extends BaseCommand {
-        /** The distribution options. */
-        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
-        private InverseContinuousDistributionOptions distributionOptions = new InverseContinuousDistributionOptions();
+    static class ICDF extends InverseProbabilityCommand {}
 
-        @Override
-        protected DistributionOptions getDistributionOptions() {
-            return distributionOptions;
-        }
-    }
+    /** ISF command. */
+    @Command(name = "isf",
+             description = "Continuous uniform distribution inverse SF.")
+    static class ISF extends InverseProbabilityCommand {}
 }
diff --git a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/UniformDiscreteCommand.java b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/UniformDiscreteCommand.java
index d61c360..a16427c 100644
--- a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/UniformDiscreteCommand.java
+++ b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/UniformDiscreteCommand.java
@@ -36,6 +36,7 @@ import picocli.CommandLine.Option;
              UniformDiscreteCommand.CDF.class,
              UniformDiscreteCommand.Survival.class,
              UniformDiscreteCommand.ICDF.class,
+             UniformDiscreteCommand.ISF.class,
          })
 class UniformDiscreteCommand extends AbstractDistributionCommand {
 
@@ -104,6 +105,18 @@ class UniformDiscreteCommand extends AbstractDistributionCommand {
         }
     }
 
+    /** Base command for the distribution that defines the parameters for inverse probability functions. */
+    private abstract static class InverseProbabilityCommand extends BaseCommand {
+        /** The distribution options. */
+        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
+        private InverseDiscreteDistributionOptions distributionOptions = new InverseDiscreteDistributionOptions();
+
+        @Override
+        protected DistributionOptions getDistributionOptions() {
+            return distributionOptions;
+        }
+    }
+
     /** Verification checks command. */
     @Command(name = "check",
              hidden = true,
@@ -135,14 +148,10 @@ class UniformDiscreteCommand extends AbstractDistributionCommand {
     /** ICDF command. */
     @Command(name = "icdf",
              description = "Discrete uniform distribution inverse CDF.")
-    static class ICDF extends BaseCommand {
-        /** The distribution options. */
-        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
-        private InverseDiscreteDistributionOptions distributionOptions = new InverseDiscreteDistributionOptions();
+    static class ICDF extends InverseProbabilityCommand {}
 
-        @Override
-        protected DistributionOptions getDistributionOptions() {
-            return distributionOptions;
-        }
-    }
+    /** ISF command. */
+    @Command(name = "isf",
+             description = "Discrete uniform distribution inverse SF.")
+    static class ISF extends InverseProbabilityCommand {}
 }
diff --git a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/WeibullCommand.java b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/WeibullCommand.java
index 5ff2a86..bcbdaac 100644
--- a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/WeibullCommand.java
+++ b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/WeibullCommand.java
@@ -37,6 +37,7 @@ import picocli.CommandLine.Option;
              WeibullCommand.CDF.class,
              WeibullCommand.Survival.class,
              WeibullCommand.ICDF.class,
+             WeibullCommand.ISF.class,
          })
 class WeibullCommand extends AbstractDistributionCommand {
 
@@ -103,6 +104,18 @@ class WeibullCommand extends AbstractDistributionCommand {
         }
     }
 
+    /** Base command for the distribution that defines the parameters for inverse probability functions. */
+    private abstract static class InverseProbabilityCommand extends BaseCommand {
+        /** The distribution options. */
+        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
+        private InverseContinuousDistributionOptions distributionOptions = new InverseContinuousDistributionOptions();
+
+        @Override
+        protected DistributionOptions getDistributionOptions() {
+            return distributionOptions;
+        }
+    }
+
     /** Verification checks command. */
     @Command(name = "check",
              hidden = true,
@@ -132,14 +145,10 @@ class WeibullCommand extends AbstractDistributionCommand {
     /** ICDF command. */
     @Command(name = "icdf",
              description = "Weibull distribution inverse CDF.")
-    static class ICDF extends BaseCommand {
-        /** The distribution options. */
-        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
-        private InverseContinuousDistributionOptions distributionOptions = new InverseContinuousDistributionOptions();
+    static class ICDF extends InverseProbabilityCommand {}
 
-        @Override
-        protected DistributionOptions getDistributionOptions() {
-            return distributionOptions;
-        }
-    }
+    /** ISF command. */
+    @Command(name = "isf",
+             description = "Weibull distribution inverse SF.")
+    static class ISF extends InverseProbabilityCommand {}
 }
diff --git a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/ZipfCommand.java b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/ZipfCommand.java
index 80918bc..97e88cd 100644
--- a/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/ZipfCommand.java
+++ b/commons-statistics-examples/examples-distribution/src/main/java/org/apache/commons/statistics/examples/distribution/ZipfCommand.java
@@ -36,6 +36,7 @@ import picocli.CommandLine.Option;
              ZipfCommand.CDF.class,
              ZipfCommand.Survival.class,
              ZipfCommand.ICDF.class,
+             ZipfCommand.ISF.class,
          })
 class ZipfCommand extends AbstractDistributionCommand {
 
@@ -102,6 +103,18 @@ class ZipfCommand extends AbstractDistributionCommand {
         }
     }
 
+    /** Base command for the distribution that defines the parameters for inverse probability functions. */
+    private abstract static class InverseProbabilityCommand extends BaseCommand {
+        /** The distribution options. */
+        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
+        private InverseDiscreteDistributionOptions distributionOptions = new InverseDiscreteDistributionOptions();
+
+        @Override
+        protected DistributionOptions getDistributionOptions() {
+            return distributionOptions;
+        }
+    }
+
     /** Verification checks command. */
     @Command(name = "check",
              hidden = true,
@@ -133,14 +146,10 @@ class ZipfCommand extends AbstractDistributionCommand {
     /** ICDF command. */
     @Command(name = "icdf",
              description = "Zipf distribution inverse CDF.")
-    static class ICDF extends BaseCommand {
-        /** The distribution options. */
-        @ArgGroup(validate = false, heading = "Evaluation options:%n", order = 2)
-        private InverseDiscreteDistributionOptions distributionOptions = new InverseDiscreteDistributionOptions();
+    static class ICDF extends InverseProbabilityCommand {}
 
-        @Override
-        protected DistributionOptions getDistributionOptions() {
-            return distributionOptions;
-        }
-    }
+    /** ISF command. */
+    @Command(name = "isf",
+             description = "Zipf distribution inverse SF.")
+    static class ISF extends InverseProbabilityCommand {}
 }

[commons-statistics] 01/04: STATISTICS-47: Add inverse survival probability

Posted by ah...@apache.org.
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-statistics.git

commit 71d8081e71c6a927488ed65541be2b17f0259e15
Author: Alex Herbert <ah...@apache.org>
AuthorDate: Thu Nov 18 00:43:22 2021 +0000

    STATISTICS-47: Add inverse survival probability
    
    Add default methods to the distribution interface.
    
    Add default implementations to the abstract distribution classes.
    
    Add tests for the default implementations.
---
 .../AbstractContinuousDistribution.java            | 138 ++++++++++++++----
 .../distribution/AbstractDiscreteDistribution.java | 128 ++++++++++++-----
 .../distribution/ContinuousDistribution.java       |  25 +++-
 .../distribution/DiscreteDistribution.java         |  31 +++-
 .../AbstractContinuousDistributionTest.java        | 158 +++++++++++++++++++--
 .../AbstractDiscreteDistributionTest.java          | 147 +++++++++++++++----
 .../distribution/ContinuousDistributionTest.java   |   8 +-
 .../distribution/DiscreteDistributionTest.java     |  11 ++
 src/main/resources/pmd/pmd-ruleset.xml             |   7 +
 9 files changed, 542 insertions(+), 111 deletions(-)

diff --git a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/AbstractContinuousDistribution.java b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/AbstractContinuousDistribution.java
index eea04aa..178fe4d 100644
--- a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/AbstractContinuousDistribution.java
+++ b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/AbstractContinuousDistribution.java
@@ -16,6 +16,8 @@
  */
 package org.apache.commons.statistics.distribution;
 
+import java.util.function.DoubleBinaryOperator;
+import java.util.function.DoubleUnaryOperator;
 import org.apache.commons.numbers.rootfinder.BrentSolver;
 import org.apache.commons.rng.UniformRandomProvider;
 import org.apache.commons.rng.sampling.distribution.InverseTransformContinuousSampler;
@@ -147,6 +149,39 @@ abstract class AbstractContinuousDistribution
      */
     @Override
     public double inverseCumulativeProbability(final double p) {
+        ArgumentUtils.checkProbability(p);
+        return inverseProbability(p, 1 - p, false);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The default implementation returns:
+     * <ul>
+     * <li>{@link #getSupportLowerBound()} for {@code p = 1},</li>
+     * <li>{@link #getSupportUpperBound()} for {@code p = 0}, or</li>
+     * <li>the result of a search for a root between the lower and upper bound using
+     *     {@link #survivalProbability(double) survivalProbability(x) - p}.
+     *     The bounds may be bracketed for efficiency.</li>
+     * </ul>
+     *
+     * @throws IllegalArgumentException if {@code p < 0} or {@code p > 1}
+     */
+    @Override
+    public double inverseSurvivalProbability(final double p) {
+        ArgumentUtils.checkProbability(p);
+        return inverseProbability(1 - p, p, true);
+    }
+
+    /**
+     * Implementation for the inverse cumulative or survival probability.
+     *
+     * @param p Cumulative probability.
+     * @param q Survival probability.
+     * @param complement Set to true to compute the inverse survival probability
+     * @return the value
+     */
+    private double inverseProbability(final double p, final double q, boolean complement) {
         /* IMPLEMENTATION NOTES
          * --------------------
          * Where applicable, use is made of the one-sided Chebyshev inequality
@@ -173,17 +208,18 @@ abstract class AbstractContinuousDistribution
          * In cases where the Chebyshev inequality does not apply, geometric
          * progressions 1, 2, 4, ... and -1, -2, -4, ... are used to bracket
          * the root.
+         *
+         * In the case of the survival probability the bracket can be set using the same
+         * bound given that the argument p = 1 - q, with q the survival probability.
          */
-        ArgumentUtils.checkProbability(p);
 
         double lowerBound = getSupportLowerBound();
+        double upperBound = getSupportUpperBound();
         if (p == 0) {
-            return lowerBound;
+            return complement ? upperBound : lowerBound;
         }
-
-        double upperBound = getSupportUpperBound();
-        if (p == 1) {
-            return upperBound;
+        if (q == 0) {
+            return complement ? lowerBound : upperBound;
         }
 
         final double mu = getMean();
@@ -193,55 +229,103 @@ abstract class AbstractContinuousDistribution
 
         if (lowerBound == Double.NEGATIVE_INFINITY) {
             if (chebyshevApplies) {
-                lowerBound = mu - sig * Math.sqrt((1 - p) / p);
+                lowerBound = mu - sig * Math.sqrt(q / p);
             }
             // Bound may have been set as infinite
             if (lowerBound == Double.NEGATIVE_INFINITY) {
                 lowerBound = Math.min(-1, upperBound);
-                while (cumulativeProbability(lowerBound) >= p) {
-                    lowerBound *= 2;
+                if (complement) {
+                    while (survivalProbability(lowerBound) < q) {
+                        lowerBound *= 2;
+                    }
+                } else {
+                    while (cumulativeProbability(lowerBound) >= p) {
+                        lowerBound *= 2;
+                    }
                 }
+                // Ensure finite
+                lowerBound = Math.max(lowerBound, -Double.MAX_VALUE);
             }
         }
 
         if (upperBound == Double.POSITIVE_INFINITY) {
             if (chebyshevApplies) {
-                upperBound = mu + sig * Math.sqrt(p / (1 - p));
+                upperBound = mu + sig * Math.sqrt(p / q);
             }
             // Bound may have been set as infinite
             if (upperBound == Double.POSITIVE_INFINITY) {
                 upperBound = Math.max(1, lowerBound);
-                while (cumulativeProbability(upperBound) < p) {
-                    upperBound *= 2;
+                if (complement) {
+                    while (survivalProbability(upperBound) >= q) {
+                        upperBound *= 2;
+                    }
+                } else {
+                    while (cumulativeProbability(upperBound) < p) {
+                        upperBound *= 2;
+                    }
                 }
+                // Ensure finite
+                upperBound = Math.min(upperBound, Double.MAX_VALUE);
             }
         }
 
+        final DoubleUnaryOperator fun = complement ?
+            arg -> survivalProbability(arg) - q :
+            arg -> cumulativeProbability(arg) - p;
         final double x = new BrentSolver(SOLVER_RELATIVE_ACCURACY,
                                          SOLVER_ABSOLUTE_ACCURACY,
                                          SOLVER_FUNCTION_VALUE_ACCURACY)
-            .findRoot(arg -> cumulativeProbability(arg) - p,
+            .findRoot(fun,
                       lowerBound,
                       0.5 * (lowerBound + upperBound),
                       upperBound);
 
         if (!isSupportConnected()) {
-            /* Test for plateau. */
-            final double dx = SOLVER_ABSOLUTE_ACCURACY;
-            if (x - dx >= lowerBound) {
-                final double px = cumulativeProbability(x);
-                if (cumulativeProbability(x - dx) == px) {
-                    upperBound = x;
-                    while (upperBound - lowerBound > dx) {
-                        final double midPoint = 0.5 * (lowerBound + upperBound);
-                        if (cumulativeProbability(midPoint) < px) {
-                            lowerBound = midPoint;
-                        } else {
-                            upperBound = midPoint;
-                        }
+            return searchPlateau(complement, lowerBound, x);
+        }
+        return x;
+    }
+
+    /**
+     * Test the probability function for a plateau at the point x. If detected
+     * search the plateau for the lowest point y such that
+     * {@code inf{y in R | P(y) == P(x)}}.
+     *
+     * <p>This function is used when the distribution support is not connected
+     * to satisfy the inverse probability requirements of {@link ContinuousDistribution}
+     * on the returned value.
+     *
+     * @param complement Set to true to search the survival probability.
+     * @param lower Lower bound used to limit the search downwards.
+     * @param x Current value.
+     * @return the infimum y
+     */
+    private double searchPlateau(boolean complement, double lower, final double x) {
+        /* Test for plateau. Lower the value x if the probability is the same. */
+        final double dx = SOLVER_ABSOLUTE_ACCURACY;
+        if (x - dx >= lower) {
+            final DoubleUnaryOperator fun = complement ?
+                this::survivalProbability :
+                this::cumulativeProbability;
+            final double px = fun.applyAsDouble(x);
+            if (fun.applyAsDouble(x - dx) == px) {
+                double upperBound = x;
+                double lowerBound = lower;
+                // Bisection search
+                // Require cdf(x) < px and sf(x) > px to move the lower bound
+                // to the midpoint.
+                final DoubleBinaryOperator cmp = complement ?
+                    (a, b) -> a > b ? -1 : 1 :
+                    (a, b) -> a < b ? -1 : 1;
+                while (upperBound - lowerBound > dx) {
+                    final double midPoint = 0.5 * (lowerBound + upperBound);
+                    if (cmp.applyAsDouble(fun.applyAsDouble(midPoint), px) < 0) {
+                        lowerBound = midPoint;
+                    } else {
+                        upperBound = midPoint;
                     }
-                    return upperBound;
                 }
+                return upperBound;
             }
         }
         return x;
diff --git a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/AbstractDiscreteDistribution.java b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/AbstractDiscreteDistribution.java
index 3a3360f..9c7394a 100644
--- a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/AbstractDiscreteDistribution.java
+++ b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/AbstractDiscreteDistribution.java
@@ -16,6 +16,7 @@
  */
 package org.apache.commons.statistics.distribution;
 
+import java.util.function.IntUnaryOperator;
 import org.apache.commons.rng.UniformRandomProvider;
 import org.apache.commons.rng.sampling.distribution.InverseTransformDiscreteSampler;
 
@@ -113,64 +114,111 @@ abstract class AbstractDiscreteDistribution
     @Override
     public int inverseCumulativeProbability(final double p) {
         ArgumentUtils.checkProbability(p);
+        return inverseProbability(p, 1 - p, false);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The default implementation returns:
+     * <ul>
+     * <li>{@link #getSupportLowerBound()} for {@code p = 1},</li>
+     * <li>{@link #getSupportUpperBound()} for {@code p = 0}, or</li>
+     * <li>the result of a search for a root between the lower and upper bound using
+     *     {@link #survivalProbability(int) survivalProbability(x) - p}.
+     *     The bounds may be bracketed for efficiency.</li>
+     * </ul>
+     *
+     * @throws IllegalArgumentException if {@code p < 0} or {@code p > 1}
+     */
+    @Override
+    public int inverseSurvivalProbability(final double p) {
+        ArgumentUtils.checkProbability(p);
+        return inverseProbability(1 - p, p, true);
+    }
+
+    /**
+     * Implementation for the inverse cumulative or survival probability.
+     *
+     * @param p Cumulative probability.
+     * @param q Survival probability.
+     * @param complement Set to true to compute the inverse survival probability
+     * @return the value
+     */
+    private int inverseProbability(final double p, final double q, boolean complement) {
 
         int lower = getSupportLowerBound();
         if (p == 0) {
             return lower;
         }
+        int upper = getSupportUpperBound();
+        if (q == 0) {
+            return upper;
+        }
+
+        // The binary search sets the upper value to the mid-point
+        // based on fun(x) >= 0. The upper value is returned.
+        //
+        // Create a function to search for x where the upper bound can be
+        // lowered if:
+        // cdf(x) >= p
+        // sf(x)  <= q
+        final IntUnaryOperator fun = complement ?
+            x -> Double.compare(q, checkedProbability(survivalProbability(x))) :
+            x -> Double.compare(checkedProbability(cumulativeProbability(x)), p);
+
         if (lower == Integer.MIN_VALUE) {
-            if (checkedCumulativeProbability(lower) >= p) {
+            if (fun.applyAsInt(lower) >= 0) {
                 return lower;
             }
         } else {
-            lower -= 1; // this ensures cumulativeProbability(lower) < p, which
-                        // is important for the solving step
-        }
-
-        int upper = getSupportUpperBound();
-        if (p == 1) {
-            return upper;
+            // this ensures:
+            // cumulativeProbability(lower) < p
+            // survivalProbability(lower) > q
+            // which is important for the solving step
+            lower -= 1;
         }
 
         // use the one-sided Chebyshev inequality to narrow the bracket
-        // cf. AbstractRealDistribution.inverseCumulativeProbability(double)
+        // cf. AbstractContinuousDistribution.inverseCumulativeProbability(double)
         final double mu = getMean();
-        final double sigma = Math.sqrt(getVariance());
+        final double sig = Math.sqrt(getVariance());
         final boolean chebyshevApplies = Double.isFinite(mu) &&
-                                         Double.isFinite(sigma) &&
-                                         sigma != 0.0;
+                                         ArgumentUtils.isFiniteStrictlyPositive(sig);
 
         if (chebyshevApplies) {
-            double k = Math.sqrt((1.0 - p) / p);
-            double tmp = mu - k * sigma;
+            double tmp = mu - sig * Math.sqrt(q / p);
             if (tmp > lower) {
                 lower = ((int) Math.ceil(tmp)) - 1;
             }
-            k = 1.0 / k;
-            tmp = mu + k * sigma;
+            tmp = mu + sig * Math.sqrt(p / q);
             if (tmp < upper) {
                 upper = ((int) Math.ceil(tmp)) - 1;
             }
         }
 
-        return solveInverseCumulativeProbability(p, lower, upper);
+        // TODO
+        // Improve the simple bisection to use a faster search,
+        // e.g. a BrentSolver.
+
+        return solveInverseProbability(fun, lower, upper);
     }
 
     /**
      * This is a utility function used by {@link
-     * #inverseCumulativeProbability(double)}. It assumes {@code 0 < p < 1} and
-     * that the inverse cumulative probability lies in the bracket {@code
+     * #inverseProbability(double, double, boolean)}. It assumes
+     * that the inverse probability lies in the bracket {@code
      * (lower, upper]}. The implementation does simple bisection to find the
-     * smallest {@code p}-quantile {@code inf{x in Z | P(X <= x) >= p}}.
+     * smallest {@code x} such that {@code fun(x) >= 0}.
      *
-     * @param p Cumulative probability.
-     * @param lowerBound Value satisfying {@code cumulativeProbability(lower) < p}.
-     * @param upperBound Value satisfying {@code p <= cumulativeProbability(upper)}.
-     * @return the smallest {@code p}-quantile of this distribution.
+     * @param fun Probability function.
+     * @param lowerBound Value satisfying {@code fun(lower) < 0}.
+     * @param upperBound Value satisfying {@code fun(upper) >= 0}.
+     * @return the smallest x
      */
-    private int solveInverseCumulativeProbability(final double p,
-                                                  int lowerBound,
-                                                  int upperBound) {
+    private static int solveInverseProbability(IntUnaryOperator fun,
+                                               int lowerBound,
+                                               int upperBound) {
         // Use long to prevent overflow during computation of the middle
         long lower = lowerBound;
         long upper = upperBound;
@@ -180,28 +228,30 @@ abstract class AbstractDiscreteDistribution
             // lower and upper arguments of this method are positive, for
             // example, for PoissonDistribution.
             final long middle = (lower + upper) / 2;
-            final double pm = checkedCumulativeProbability((int) middle);
-            if (pm >= p) {
-                upper = middle;
-            } else {
+            final double pm = fun.applyAsInt((int) middle);
+            if (pm < 0) {
                 lower = middle;
+            } else {
+                upper = middle;
             }
         }
         return (int) upper;
     }
 
     /**
-     * Computes the cumulative probability function and checks for {@code NaN}
-     * values returned. Rethrows any exception encountered evaluating the cumulative
-     * probability function. Throws {@code IllegalStateException} if the cumulative
+     * Checks the probability function result for {@code NaN}.
+     * Throws {@code IllegalStateException} if the cumulative
      * probability function returns {@code NaN}.
      *
-     * @param argument Input value.
-     * @return the cumulative probability.
-     * @throws IllegalStateException if the cumulative probability is {@code NaN}.
+     * <p>TODO: Q. Is this required? The bisection search will
+     * eventually return if NaN is computed. Check the origin
+     * of this in Commons Math. Its origins may be for debugging.
+     *
+     * @param result Probability
+     * @return the probability.
+     * @throws IllegalStateException if the probability is {@code NaN}.
      */
-    private double checkedCumulativeProbability(int argument) {
-        final double result = cumulativeProbability(argument);
+    private static double checkedProbability(double result) {
         if (Double.isNaN(result)) {
             throw new IllegalStateException("Internal error");
         }
diff --git a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/ContinuousDistribution.java b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/ContinuousDistribution.java
index ec9493b..1b1c425 100644
--- a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/ContinuousDistribution.java
+++ b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/ContinuousDistribution.java
@@ -86,8 +86,8 @@ public interface ContinuousDistribution {
      * to this distribution, this method returns {@code P(X > x)}.
      * In other words, this method represents the complementary cumulative
      * distribution function.
-     * <p>
-     * By default, this is defined as {@code 1 - cumulativeProbability(x)}, but
+     *
+     * <p>By default, this is defined as {@code 1 - cumulativeProbability(x)}, but
      * the specific implementation may be more accurate.
      *
      * @param x Point at which the survival function is evaluated.
@@ -115,6 +115,27 @@ public interface ContinuousDistribution {
     double inverseCumulativeProbability(double p);
 
     /**
+     * Computes the inverse survival probability function of this distribution. For a random
+     * variable {@code X} distributed according to this distribution, the
+     * returned value is
+     * <ul>
+     * <li>{@code inf{x in R | P(X>=x) <= p}} for {@code 0 <= p < 1},</li>
+     * <li>{@code inf{x in R | P(X>=x) < 1}} for {@code p = 1}.</li>
+     * </ul>
+     *
+     * <p>By default, this is defined as {@code inverseCumulativeProbability(1 - p)}, but
+     * the specific implementation may be more accurate.
+     *
+     * @param p Survival probability.
+     * @return the smallest {@code (1-p)}-quantile of this distribution
+     * (largest 0-quantile for {@code p = 1}).
+     * @throws IllegalArgumentException if {@code p < 0} or {@code p > 1}.
+     */
+    default double inverseSurvivalProbability(double p) {
+        return inverseCumulativeProbability(1 - p);
+    }
+
+    /**
      * Gets the mean of this distribution.
      *
      * @return the mean, or {@code Double.NaN} if it is not defined.
diff --git a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/DiscreteDistribution.java b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/DiscreteDistribution.java
index 05681a1..7cfcb4e 100644
--- a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/DiscreteDistribution.java
+++ b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/DiscreteDistribution.java
@@ -95,8 +95,8 @@ public interface DiscreteDistribution {
      * to this distribution, this method returns {@code P(X > x)}.
      * In other words, this method represents the complementary cumulative
      * distribution function.
-     * <p>
-     * By default, this is defined as {@code 1 - cumulativeProbability(x)}, but
+     *
+     * <p>By default, this is defined as {@code 1 - cumulativeProbability(x)}, but
      * the specific implementation may be more accurate.
      *
      * @param x Point at which the survival function is evaluated.
@@ -115,7 +115,7 @@ public interface DiscreteDistribution {
      * <li>{@code inf{x in Z | P(X<=x) >= p}} for {@code 0 < p <= 1},</li>
      * <li>{@code inf{x in Z | P(X<=x) > 0}} for {@code p = 0}.</li>
      * </ul>
-     * If the result exceeds the range of the data type {@code int},
+     * <p>If the result exceeds the range of the data type {@code int},
      * then {@code Integer.MIN_VALUE} or {@code Integer.MAX_VALUE} is returned.
      * In this case the result of {@link #cumulativeProbability(int)} called
      * using the returned {@code p}-quantile may not compute the original {@code p}.
@@ -128,6 +128,31 @@ public interface DiscreteDistribution {
     int inverseCumulativeProbability(double p);
 
     /**
+     * Computes the inverse survival probability function of this distribution.
+     * For a random variable {@code X} distributed according to this distribution,
+     * the returned value is
+     * <ul>
+     * <li>{@code inf{x in R | P(X>=x) <= p}} for {@code 0 <= p < 1},</li>
+     * <li>{@code inf{x in R | P(X>=x) < 1}} for {@code p = 1}.</li>
+     * </ul>
+     * <p>If the result exceeds the range of the data type {@code int},
+     * then {@code Integer.MIN_VALUE} or {@code Integer.MAX_VALUE} is returned.
+     * In this case the result of {@link #survivalProbability(int)} called
+     * using the returned {@code (1-p)}-quantile may not compute the original {@code p}.
+     *
+     * <p>By default, this is defined as {@code inverseCumulativeProbability(1 - p)}, but
+     * the specific implementation may be more accurate.
+     *
+     * @param p Cumulative probability.
+     * @return the smallest {@code (1-p)}-quantile of this distribution
+     * (largest 0-quantile for {@code p = 1}).
+     * @throws IllegalArgumentException if {@code p < 0} or {@code p > 1}.
+     */
+    default int inverseSurvivalProbability(double p) {
+        return inverseCumulativeProbability(1 - p);
+    }
+
+    /**
      * Gets the mean of this distribution.
      *
      * @return the mean, or {@code Double.NaN} if it is not defined.
diff --git a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/AbstractContinuousDistributionTest.java b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/AbstractContinuousDistributionTest.java
index 463d4f3..a43395d 100644
--- a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/AbstractContinuousDistributionTest.java
+++ b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/AbstractContinuousDistributionTest.java
@@ -22,9 +22,12 @@ import org.apache.commons.math3.analysis.integration.UnivariateIntegrator;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
-/** Various tests related to MATH-699. */
+/**
+ * Test cases for AbstractContinuousDistribution default implementations.
+ */
 class AbstractContinuousDistributionTest {
 
+    /** Various tests related to MATH-699. */
     @Test
     void testContinuous() {
         final double x0 = 0.0;
@@ -95,11 +98,34 @@ class AbstractContinuousDistributionTest {
                 return false;
             }
         };
-        final double expected = x1;
-        final double actual = distribution.inverseCumulativeProbability(p12);
-        Assertions.assertEquals(expected, actual, 1e-8);
+        // CDF is continuous before x1 and after x2. Plateau in between:
+        // CDF(x1 <= X <= x2) = p12.
+        // The inverse returns the infimum.
+        double expected = x1;
+        double actual = distribution.inverseCumulativeProbability(p12);
+        Assertions.assertEquals(expected, actual, 1e-8, "Inverse CDF");
+
+        actual = distribution.inverseSurvivalProbability(1 - p12);
+        Assertions.assertEquals(expected, actual, 1e-8, "Inverse SF");
+
+        // Test the continuous region
+        expected = 0.5 * (x1 - x0);
+        actual = distribution.inverseCumulativeProbability(p12 * 0.5);
+        Assertions.assertEquals(expected, actual, 1e-8, "Inverse CDF");
+
+        actual = distribution.inverseSurvivalProbability(1 - p12 * 0.5);
+        Assertions.assertEquals(expected, actual, 1e-8, "Inverse SF");
+
+        // Edge case where the result is within the solver accuracy of the lower bound
+        expected = x0;
+        actual = distribution.inverseCumulativeProbability(Double.MIN_VALUE);
+        Assertions.assertEquals(expected, actual, 1e-8, "Inverse CDF");
+
+        actual = distribution.inverseSurvivalProbability(Math.nextDown(1.0));
+        Assertions.assertEquals(expected, actual, 1e-8, "Inverse SF");
     }
 
+    /** Various tests related to MATH-699. */
     @Test
     void testDiscontinuous() {
         final double x0 = 0.0;
@@ -177,9 +203,31 @@ class AbstractContinuousDistributionTest {
                 return false;
             }
         };
-        final double expected = x2;
-        final double actual = distribution.inverseCumulativeProbability(p23);
-        Assertions.assertEquals(expected, actual, 1e-8);
+        // CDF continuous before x1 and after x3. Two plateuas in between stepped at x2:
+        // CDF(x1 <= X <= x2) = p12.
+        // CDF(x2 <= X <= x3) = p23. The inverse returns the infimum.
+        double expected = x2;
+        double actual = distribution.inverseCumulativeProbability(p23);
+        Assertions.assertEquals(expected, actual, 1e-8, "Inverse CDF");
+
+        actual = distribution.inverseSurvivalProbability(1 - p23);
+        Assertions.assertEquals(expected, actual, 1e-8, "Inverse SF");
+
+        // Test the continuous region
+        expected = 0.5 * (x1 - x0);
+        actual = distribution.inverseCumulativeProbability(p12 * 0.5);
+        Assertions.assertEquals(expected, actual, 1e-8, "Inverse CDF");
+
+        actual = distribution.inverseSurvivalProbability(1 - p12 * 0.5);
+        Assertions.assertEquals(expected, actual, 1e-8, "Inverse SF");
+
+        // Edge case where the result is within the solver accuracy of the lower bound
+        expected = x0;
+        actual = distribution.inverseCumulativeProbability(Double.MIN_VALUE);
+        Assertions.assertEquals(expected, actual, 1e-8, "Inverse CDF");
+
+        actual = distribution.inverseSurvivalProbability(Math.nextDown(1.0));
+        Assertions.assertEquals(expected, actual, 1e-8, "Inverse SF");
     }
 
     /**
@@ -208,7 +256,7 @@ class AbstractContinuousDistributionTest {
 
             @Override
             public double density(final double x) {
-                return x == x0 ? Double.POSITIVE_INFINITY : 0.0;
+                throw new AssertionError();
             }
 
             @Override
@@ -236,11 +284,99 @@ class AbstractContinuousDistributionTest {
                 return true;
             }
         };
-        final double x = distribution.inverseCumulativeProbability(0.5);
+        double x = distribution.inverseCumulativeProbability(0.5);
         // The value can be anything other than x0
-        Assertions.assertNotEquals(x0, x);
+        Assertions.assertNotEquals(x0, x, "Inverse CDF");
         // Ideally it would be the next value after x0 but accuracy is dependent
         // on the tolerance of the solver
-        Assertions.assertEquals(x0, x, 1e-8);
+        Assertions.assertEquals(x0, x, 1e-8, "Inverse CDF");
+
+        // The same functionality should be supported for the inverse survival probability
+        x = distribution.inverseSurvivalProbability(0.5);
+        Assertions.assertNotEquals(x0, x, "Inverse SF");
+        Assertions.assertEquals(x0, x, 1e-8, "Inverse SF");
+    }
+
+    /**
+     * Test infinite variance. This invalidates the Chebyshev inequality.
+     *
+     * <p>This test verifies the solver reverts to manual bracketing and raises no exception.
+     */
+    @Test
+    void testInfiniteVariance() {
+        // Distribution must have an infinite bound and infinite variance.
+        // This is an invalid case for the Chebyshev inequality.
+
+        // Create a triangle distribution: (a, c, b); a=lower, c=mode, b=upper
+        // (-10, 0, 10)
+        // Area of the first triangle [-10, 0] is set assuming the height is 10
+        // => 10 * 10 * 2 / 2 = 100
+        // Length of triangle to achieve half the area:
+        // x = sqrt(50) = 7.07..
+
+        final AbstractContinuousDistribution distribution;
+        distribution = new AbstractContinuousDistribution() {
+            @Override
+            public double cumulativeProbability(final double x) {
+                if (x > 0) {
+                    // Use symmetry for the upper triangle
+                    return 1 - cumulativeProbability(-x);
+                }
+                if (x < -10) {
+                    return 0;
+                }
+                return Math.pow(x + 10, 2) / 200;
+            }
+
+            @Override
+            public double density(final double x) {
+                throw new AssertionError();
+            }
+
+            @Override
+            public double getMean() {
+                return 0;
+            }
+
+            @Override
+            public double getVariance() {
+                // Report variance incorrectly
+                return Double.POSITIVE_INFINITY;
+            }
+
+            @Override
+            public double getSupportLowerBound() {
+                // Report lower bound incorrectly (it should be -10) to test cdf(0)
+                return Double.NEGATIVE_INFINITY;
+            }
+
+            @Override
+            public double getSupportUpperBound() {
+                // Report upper bound incorrectly (it should be 10) to test cdf(1)
+                return Double.POSITIVE_INFINITY;
+            }
+
+            @Override
+            public boolean isSupportConnected() {
+                return true;
+            }
+        };
+
+        // Accuracy is dependent on the tolerance of the solver
+        final double tolerance = 1e-8;
+
+        final double x = Math.sqrt(50);
+        Assertions.assertEquals(Double.NEGATIVE_INFINITY, distribution.inverseCumulativeProbability(0), "Inverse CDF");
+        Assertions.assertEquals(x - 10, distribution.inverseCumulativeProbability(0.25), tolerance, "Inverse CDF");
+        Assertions.assertEquals(0, distribution.inverseCumulativeProbability(0.5), tolerance, "Inverse CDF");
+        Assertions.assertEquals(10 - x, distribution.inverseCumulativeProbability(0.75), tolerance, "Inverse CDF");
+        Assertions.assertEquals(Double.POSITIVE_INFINITY, distribution.inverseCumulativeProbability(1), "Inverse CDF");
+
+        // The same functionality should be supported for the inverse survival probability
+        Assertions.assertEquals(Double.NEGATIVE_INFINITY, distribution.inverseSurvivalProbability(1), "Inverse CDF");
+        Assertions.assertEquals(x - 10, distribution.inverseSurvivalProbability(0.75), tolerance, "Inverse SF");
+        Assertions.assertEquals(0, distribution.inverseSurvivalProbability(0.5), tolerance, "Inverse SF");
+        Assertions.assertEquals(10 - x, distribution.inverseSurvivalProbability(0.25), tolerance, "Inverse SF");
+        Assertions.assertEquals(Double.POSITIVE_INFINITY, distribution.inverseSurvivalProbability(0), "Inverse CDF");
     }
 }
diff --git a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/AbstractDiscreteDistributionTest.java b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/AbstractDiscreteDistributionTest.java
index 3a49ff3..d27bdfb 100644
--- a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/AbstractDiscreteDistributionTest.java
+++ b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/AbstractDiscreteDistributionTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.commons.statistics.distribution;
 
+import java.util.stream.IntStream;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
@@ -24,40 +25,53 @@ import org.junit.jupiter.api.Test;
  */
 class AbstractDiscreteDistributionTest {
     private final DiceDistribution diceDistribution = new DiceDistribution();
-    private final double p = diceDistribution.probability(1);
 
     @Test
     void testInverseCumulativeProbabilityMethod() {
-        final double precision = 0.000000000000001;
+        // This must be consistent with its own cumulative probability
+        final double[] p = IntStream.rangeClosed(1, 6).mapToDouble(diceDistribution::cumulativeProbability).toArray();
+        Assertions.assertEquals(1.0, p[5], "Incorrect cumulative probability at upper bound");
         Assertions.assertEquals(1, diceDistribution.inverseCumulativeProbability(0));
-        Assertions.assertEquals(1, diceDistribution.inverseCumulativeProbability((1d - Double.MIN_VALUE) / 6d));
-        Assertions.assertEquals(2, diceDistribution.inverseCumulativeProbability((1d + precision) / 6d));
-        Assertions.assertEquals(2, diceDistribution.inverseCumulativeProbability((2d - Double.MIN_VALUE) / 6d));
-        Assertions.assertEquals(3, diceDistribution.inverseCumulativeProbability((2d + precision) / 6d));
-        Assertions.assertEquals(3, diceDistribution.inverseCumulativeProbability((3d - Double.MIN_VALUE) / 6d));
-        Assertions.assertEquals(4, diceDistribution.inverseCumulativeProbability((3d + precision) / 6d));
-        Assertions.assertEquals(4, diceDistribution.inverseCumulativeProbability((4d - Double.MIN_VALUE) / 6d));
-        Assertions.assertEquals(5, diceDistribution.inverseCumulativeProbability((4d + precision) / 6d));
-        Assertions.assertEquals(5, diceDistribution.inverseCumulativeProbability((5d - precision) / 6d)); //Can't use Double.MIN
-        Assertions.assertEquals(6, diceDistribution.inverseCumulativeProbability((5d + precision) / 6d));
-        Assertions.assertEquals(6, diceDistribution.inverseCumulativeProbability((6d - precision) / 6d)); //Can't use Double.MIN
-        Assertions.assertEquals(6, diceDistribution.inverseCumulativeProbability(1d));
+        for (int i = 0; i < 6; i++) {
+            final int x = i + 1;
+            Assertions.assertEquals(x, diceDistribution.inverseCumulativeProbability(Math.nextDown(p[i])));
+            Assertions.assertEquals(x, diceDistribution.inverseCumulativeProbability(p[i]));
+            if (x < 6) {
+                Assertions.assertEquals(x + 1, diceDistribution.inverseCumulativeProbability(Math.nextUp(p[i])));
+            }
+        }
+    }
+
+    @Test
+    void testInverseSurvivalProbabilityMethod() {
+        // This must be consistent with its own survival probability
+        final double[] p = IntStream.rangeClosed(1, 6).mapToDouble(diceDistribution::survivalProbability).toArray();
+        Assertions.assertEquals(0.0, p[5], "Incorrect survival probability at upper bound");
+        Assertions.assertEquals(1, diceDistribution.inverseSurvivalProbability(1));
+        for (int i = 0; i < 6; i++) {
+            final int x = i + 1;
+            Assertions.assertEquals(x, diceDistribution.inverseSurvivalProbability(Math.nextUp(p[i])));
+            Assertions.assertEquals(x, diceDistribution.inverseSurvivalProbability(p[i]));
+            if (x < 6) {
+                Assertions.assertEquals(x + 1, diceDistribution.inverseSurvivalProbability(Math.nextDown(p[i])));
+            }
+        }
     }
 
     @Test
     void testCumulativeProbabilitiesSingleArguments() {
-        for (int i = 1; i < 7; i++) {
+        final double p = diceDistribution.probability(1);
+        for (int i = 1; i <= 6; i++) {
             Assertions.assertEquals(p * i,
-                    diceDistribution.cumulativeProbability(i), Double.MIN_VALUE);
+                    diceDistribution.cumulativeProbability(i), Math.ulp(p * i));
         }
-        Assertions.assertEquals(0.0,
-                diceDistribution.cumulativeProbability(0), Double.MIN_VALUE);
-        Assertions.assertEquals(1.0,
-                diceDistribution.cumulativeProbability(7), Double.MIN_VALUE);
+        Assertions.assertEquals(0.0, diceDistribution.cumulativeProbability(0));
+        Assertions.assertEquals(1.0, diceDistribution.cumulativeProbability(7));
     }
 
     @Test
     void testProbabilitiesRangeArguments() {
+        final double p = diceDistribution.probability(1);
         int lower = 0;
         int upper = 6;
         for (int i = 0; i < 2; i++) {
@@ -76,22 +90,34 @@ class AbstractDiscreteDistributionTest {
     void testInverseCumulativeProbabilityExtremes() {
         // Require a lower bound of MIN_VALUE and the cumulative probability
         // at that bound to be lower/higher than the argument cumulative probability.
+        // Use a uniform distribution so that the default search is supported
+        // for the inverse.
         final DiscreteDistribution dist = new AbstractDiscreteDistribution() {
             @Override
             public double probability(int x) {
-                return 0;
+                throw new AssertionError();
             }
             @Override
             public double cumulativeProbability(int x) {
-                return x == Integer.MIN_VALUE ? 0.1 : 1.0;
+                final int y = x - Integer.MIN_VALUE;
+                if (y == 0) {
+                    return 0.25;
+                } else if (y == 1) {
+                    return 0.5;
+                } else if (y == 2) {
+                    return 0.75;
+                } else {
+                    return 1.0;
+                }
             }
             @Override
             public double getMean() {
-                return 0;
+                return 1.5 + Integer.MIN_VALUE;
             }
             @Override
             public double getVariance() {
-                return 0;
+                // n = upper - lower + 1, the variance is (n^2 - 1) / 12
+                return 15.0 / 12;
             }
             @Override
             public int getSupportLowerBound() {
@@ -99,19 +125,83 @@ class AbstractDiscreteDistributionTest {
             }
             @Override
             public int getSupportUpperBound() {
-                return 42;
+                return Integer.MIN_VALUE + 3;
             }
             @Override
             public boolean isSupportConnected() {
-                return false;
+                throw new AssertionError();
             }
         };
+        Assertions.assertEquals(dist.getSupportLowerBound(), dist.inverseCumulativeProbability(0.0));
         Assertions.assertEquals(Integer.MIN_VALUE, dist.inverseCumulativeProbability(0.05));
+        Assertions.assertEquals(Integer.MIN_VALUE + 1, dist.inverseCumulativeProbability(0.35));
+        Assertions.assertEquals(Integer.MIN_VALUE + 2, dist.inverseCumulativeProbability(0.55));
         Assertions.assertEquals(dist.getSupportUpperBound(), dist.inverseCumulativeProbability(1.0));
+
+        Assertions.assertEquals(dist.getSupportLowerBound(), dist.inverseSurvivalProbability(1.0));
+        Assertions.assertEquals(Integer.MIN_VALUE, dist.inverseSurvivalProbability(0.95));
+        Assertions.assertEquals(Integer.MIN_VALUE + 1, dist.inverseSurvivalProbability(0.65));
+        Assertions.assertEquals(Integer.MIN_VALUE + 2, dist.inverseSurvivalProbability(0.45));
+        Assertions.assertEquals(dist.getSupportUpperBound(), dist.inverseSurvivalProbability(0.0));
+    }
+
+    @Test
+    void testInverseCumulativeProbabilityWithNoMean() {
+        // A NaN mean will invalidate the Chebyshev inequality
+        // to prevent bracketing
+        final DiscreteDistribution dist = new AbstractDiscreteDistribution() {
+            @Override
+            public double probability(int x) {
+                throw new AssertionError();
+            }
+            @Override
+            public double cumulativeProbability(int x) {
+                if (x == 0) {
+                    return 0.25;
+                } else if (x == 1) {
+                    return 0.5;
+                } else if (x == 2) {
+                    return 0.75;
+                } else {
+                    return 1.0;
+                }
+            }
+            @Override
+            public double getMean() {
+                return Double.NaN;
+            }
+            @Override
+            public double getVariance() {
+                return Double.NaN;
+            }
+            @Override
+            public int getSupportLowerBound() {
+                return 0;
+            }
+            @Override
+            public int getSupportUpperBound() {
+                return 3;
+            }
+            @Override
+            public boolean isSupportConnected() {
+                throw new AssertionError();
+            }
+        };
+        Assertions.assertEquals(dist.getSupportLowerBound(), dist.inverseCumulativeProbability(0.0));
+        Assertions.assertEquals(0, dist.inverseCumulativeProbability(0.05));
+        Assertions.assertEquals(1, dist.inverseCumulativeProbability(0.35));
+        Assertions.assertEquals(2, dist.inverseCumulativeProbability(0.55));
+        Assertions.assertEquals(dist.getSupportUpperBound(), dist.inverseCumulativeProbability(1.0));
+
+        Assertions.assertEquals(dist.getSupportLowerBound(), dist.inverseSurvivalProbability(1.0));
+        Assertions.assertEquals(0, dist.inverseSurvivalProbability(0.95));
+        Assertions.assertEquals(1, dist.inverseSurvivalProbability(0.65));
+        Assertions.assertEquals(2, dist.inverseSurvivalProbability(0.45));
+        Assertions.assertEquals(dist.getSupportUpperBound(), dist.inverseSurvivalProbability(0.0));
     }
 
     @Test
-    void testInverseCumulativeProbabilityWithNaN() {
+    void testInverseProbabilityWithNaN() {
         final DiscreteDistribution dist = new AbstractDiscreteDistribution() {
             @Override
             public double probability(int x) {
@@ -144,6 +234,7 @@ class AbstractDiscreteDistributionTest {
             }
         };
         Assertions.assertThrows(IllegalStateException.class, () -> dist.inverseCumulativeProbability(0.5));
+        Assertions.assertThrows(IllegalStateException.class, () -> dist.inverseSurvivalProbability(0.5));
     }
 
     /**
@@ -168,7 +259,7 @@ class AbstractDiscreteDistributionTest {
             } else if (x >= 6) {
                 return 1;
             } else {
-                return p * x;
+                return x / 6d;
             }
         }
 
diff --git a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/ContinuousDistributionTest.java b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/ContinuousDistributionTest.java
index 8158a16..8e6d1f5 100644
--- a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/ContinuousDistributionTest.java
+++ b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/ContinuousDistributionTest.java
@@ -39,7 +39,8 @@ class ContinuousDistributionTest {
             }
             @Override
             public double inverseCumulativeProbability(double p) {
-                return 0;
+                // For the default inverseSurvivalProbability(double) method
+                return 10 * p;
             }
             @Override
             public double getVariance() {
@@ -82,5 +83,10 @@ class ContinuousDistributionTest {
         // Should throw for bad range
         Assertions.assertThrows(DistributionException.class, () -> dist.probability(0.5, 0.4));
         Assertions.assertEquals(high - low, dist.probability(0.5, 1.5));
+        Assertions.assertEquals(high - low, dist.probability(0.5, 1.5));
+        for (final double p : new double[] {0.2, 0.5, 0.7}) {
+            Assertions.assertEquals(dist.inverseCumulativeProbability(1 - p),
+                                    dist.inverseSurvivalProbability(p));
+        }
     }
 }
diff --git a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/DiscreteDistributionTest.java b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/DiscreteDistributionTest.java
index 90437a7..17a261c 100644
--- a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/DiscreteDistributionTest.java
+++ b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/DiscreteDistributionTest.java
@@ -45,6 +45,12 @@ class DiscreteDistributionTest {
                 }
                 return x > 5 ? 1.0 : 0.75;
             }
+
+            @Override
+            public int inverseCumulativeProbability(double p) {
+                // For the default inverseSurvivalProbability(double) method
+                return (int) (10 * p);
+            }
         };
 
         for (final int x : new int[] {Integer.MIN_VALUE, -1, 0, 1, 2, Integer.MAX_VALUE}) {
@@ -53,6 +59,11 @@ class DiscreteDistributionTest {
             // Must return 1 - CDF(x)
             Assertions.assertEquals(1.0 - dist.cumulativeProbability(x), dist.survivalProbability(x));
         }
+
+        for (final double p : new double[] {0.2, 0.5, 0.7}) {
+            Assertions.assertEquals(dist.inverseCumulativeProbability(1 - p),
+                                    dist.inverseSurvivalProbability(p));
+        }
     }
 
     /**
diff --git a/src/main/resources/pmd/pmd-ruleset.xml b/src/main/resources/pmd/pmd-ruleset.xml
index a8e3fa8..e4f6059 100644
--- a/src/main/resources/pmd/pmd-ruleset.xml
+++ b/src/main/resources/pmd/pmd-ruleset.xml
@@ -109,6 +109,13 @@
         value="//ClassOrInterfaceDeclaration[@Image='AbstractContinuousDistribution']"/>
     </properties>
   </rule>
+  <rule ref="category/java/design.xml/ExcessiveMethodLength">
+    <properties>
+      <!-- Method length is due to comments. -->
+      <property name="violationSuppressXPath"
+        value="//ClassOrInterfaceDeclaration[@Image='AbstractContinuousDistribution']"/>
+    </properties>
+  </rule>
 
   <rule ref="category/java/errorprone.xml/AvoidLiteralsInIfCondition">
     <properties>