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:26 UTC

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

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