Merge branch 'benwtrent-STATISTICS-31-SurvivalProbabilityFunction'
diff --git a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/BetaDistribution.java b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/BetaDistribution.java
index 54e9cf3..0d7653f 100644
--- a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/BetaDistribution.java
+++ b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/BetaDistribution.java
@@ -107,6 +107,18 @@
         }
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public double survivalProbability(double x) {
+        if (x <= 0) {
+            return 1;
+        } else if (x >= 1) {
+            return 0;
+        } else {
+            return RegularizedBeta.value(1 - x, beta, alpha);
+        }
+    }
+
     /**
      * {@inheritDoc}
      *
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 1ef4ae1..771ea90 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
@@ -44,7 +44,22 @@
     /** {@inheritDoc} */
     @Override
     public double cumulativeProbability(double x) {
-        return 0.5 + (Math.atan((x - median) / scale) / Math.PI);
+        return cdf((x - median) / scale);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double survivalProbability(double x) {
+        return cdf(-(x - median) / scale);
+    }
+
+    /**
+     * Compute the CDF of the Cauchy distribution with location 0 and scale 1.
+     * @param x Point at which the CDF is evaluated
+     * @return CDF(x)
+     */
+    private static double cdf(double x) {
+        return 0.5 + (Math.atan(x) / Math.PI);
     }
 
     /**
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 e012281..c10adf5 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
@@ -61,6 +61,12 @@
         return gamma.cumulativeProbability(x);
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public double survivalProbability(double x) {
+        return gamma.survivalProbability(x);
+    }
+
     /**
      * {@inheritDoc}
      *
diff --git a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/ContinuousDistribution.java b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/ContinuousDistribution.java
index 19f7584..129dd14 100644
--- a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/ContinuousDistribution.java
+++ b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/ContinuousDistribution.java
@@ -95,6 +95,23 @@
     double cumulativeProbability(double x);
 
     /**
+     * For a random variable {@code X} whose values are distributed according
+     * to this distribution, this method returns {@code P(X > x)}.
+     * In other words, this method represents the complementary cumulative
+     * distribution function.
+     * <p>
+     * By default, this is defined as {@code 1 - cumulativeProbability(x)}, but
+     * the specific implementation may be more accurate.
+     *
+     * @param x Point at which the survival function is evaluated.
+     * @return the probability that a random variable with this
+     * distribution takes a value greater than {@code x}.
+     */
+    default double survivalProbability(double x) {
+        return 1.0 - cumulativeProbability(x);
+    }
+
+    /**
      * Computes the quantile function of this distribution. For a random
      * variable {@code X} distributed according to this distribution, the
      * returned value is
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 20e0d88..e076197 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
@@ -80,7 +80,17 @@
             return 0;
         }
 
-        return 1 - Math.exp(-x / mean);
+        return -Math.expm1(-x / mean);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double survivalProbability(double x)  {
+        if (x <= SUPPORT_LO) {
+            return 1;
+        }
+
+        return Math.exp(-x / mean);
     }
 
     /**
diff --git a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/FDistribution.java b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/FDistribution.java
index b96511f..0acccbb 100644
--- a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/FDistribution.java
+++ b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/FDistribution.java
@@ -118,6 +118,23 @@
                                      0.5 * m);
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public double survivalProbability(double x)  {
+        if (x <= SUPPORT_LO) {
+            return 1;
+        } else if (x >= SUPPORT_HI) {
+            return 0;
+        }
+
+        final double n = numeratorDegreesOfFreedom;
+        final double m = denominatorDegreesOfFreedom;
+
+        return RegularizedBeta.value(m / (m + n * x),
+                0.5 * m,
+                0.5 * n);
+    }
+
     /**
      * Access the numerator degrees of freedom.
      *
diff --git a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/GammaDistribution.java b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/GammaDistribution.java
index 5053955..eb6761c 100644
--- a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/GammaDistribution.java
+++ b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/GammaDistribution.java
@@ -255,6 +255,17 @@
         return RegularizedGamma.P.value(shape, x / scale);
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public double survivalProbability(double x) {
+        if (x <= SUPPORT_LO) {
+            return 1;
+        } else if (x >= SUPPORT_HI) {
+            return 0;
+        }
+        return RegularizedGamma.Q.value(shape, x / scale);
+    }
+
     /**
      * {@inheritDoc}
      *
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 6b28c06..812ad8e 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
@@ -92,6 +92,13 @@
 
     /** {@inheritDoc} */
     @Override
+    public double survivalProbability(double x) {
+        final double z = (x - mu) / beta;
+        return -Math.expm1(-Math.exp(-z));
+    }
+
+    /** {@inheritDoc} */
+    @Override
     public double inverseCumulativeProbability(double p) {
         if (p < 0 || p > 1) {
             throw new DistributionException(DistributionException.INVALID_PROBABILITY, p);
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 a4fab52..ef3c76c 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
@@ -80,6 +80,16 @@
 
     /** {@inheritDoc} */
     @Override
+    public double survivalProbability(double x) {
+        if (x <= mu) {
+            return 1.0 - Math.exp((x - mu) / beta) / 2.0;
+        } else {
+            return Math.exp((mu - x) / beta) / 2.0;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
     public double inverseCumulativeProbability(double p) {
         if (p < 0 ||
             p > 1) {
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 4f59f28..e38d01a 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
@@ -16,6 +16,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.InverseErfc;
 
@@ -102,6 +103,15 @@
 
     /** {@inheritDoc} */
     @Override
+    public double survivalProbability(final double x) {
+        if (x < mu) {
+            return 1;
+        }
+        return Erf.value(Math.sqrt(halfC / (x - mu)));
+    }
+
+    /** {@inheritDoc} */
+    @Override
     public double inverseCumulativeProbability(final double p) {
         if (p < 0 ||
             p > 1) {
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 584bdd5..211170b 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
@@ -17,8 +17,8 @@
 
 package org.apache.commons.statistics.distribution;
 
-import org.apache.commons.numbers.gamma.Erf;
 import org.apache.commons.numbers.gamma.ErfDifference;
+import org.apache.commons.numbers.gamma.Erfc;
 import org.apache.commons.rng.UniformRandomProvider;
 import org.apache.commons.rng.sampling.distribution.LogNormalSampler;
 import org.apache.commons.rng.sampling.distribution.ZigguratNormalizedGaussianSampler;
@@ -151,7 +151,20 @@
         if (Math.abs(dev) > 40 * shape) {
             return dev < 0 ? 0.0d : 1.0d;
         }
-        return 0.5 + 0.5 * Erf.value(dev / (shape * SQRT2));
+        return 0.5 * Erfc.value(-dev / (shape * SQRT2));
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double survivalProbability(double x)  {
+        if (x <= 0) {
+            return 1;
+        }
+        final double dev = Math.log(x) - scale;
+        if (Math.abs(dev) > 40 * shape) {
+            return dev > 0 ? 0.0d : 1.0d;
+        }
+        return 0.5 * Erfc.value(dev / (shape * SQRT2));
     }
 
     /** {@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 0480206..522ccda 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
@@ -92,6 +92,13 @@
 
     /** {@inheritDoc} */
     @Override
+    public double survivalProbability(double x) {
+        final double z = oneOverScale * (x - mu);
+        return 1 / (1 + Math.exp(z));
+    }
+
+    /** {@inheritDoc} */
+    @Override
     public double inverseCumulativeProbability(double p) {
         if (p < 0 ||
             p > 1) {
diff --git a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/NakagamiDistribution.java b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/NakagamiDistribution.java
index da925fb..05429d4 100644
--- a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/NakagamiDistribution.java
+++ b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/NakagamiDistribution.java
@@ -100,6 +100,18 @@
 
     /** {@inheritDoc} */
     @Override
+    public double survivalProbability(double x) {
+        if (x <= SUPPORT_LO) {
+            return 1;
+        } else if (x >= SUPPORT_HI) {
+            return 0;
+        }
+
+        return RegularizedGamma.Q.value(mu, mu * x * x / omega);
+    }
+
+    /** {@inheritDoc} */
+    @Override
     public double getMean() {
         return Gamma.value(mu + 0.5) / Gamma.value(mu) * Math.sqrt(omega / mu);
     }
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 6817a6f..37213ec 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
@@ -96,6 +96,16 @@
 
     /** {@inheritDoc} */
     @Override
+    public double survivalProbability(double x) {
+        final double dev = x - mean;
+        if (Math.abs(dev) > 40 * standardDeviation) {
+            return dev > 0 ? 0.0d : 1.0d;
+        }
+        return 0.5 * Erfc.value(dev / (standardDeviation * SQRT2));
+    }
+
+    /** {@inheritDoc} */
+    @Override
     public double inverseCumulativeProbability(final double p) {
         if (p < 0 ||
             p > 1) {
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 eb45bd5..51bff39 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
@@ -126,7 +126,17 @@
         if (x <= scale) {
             return 0;
         }
-        return 1 - Math.pow(scale / x, shape);
+        // Can be improved by improving log calculation
+        return -Math.expm1(shape * Math.log(scale / x));
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double survivalProbability(double x)  {
+        if (x <= scale) {
+            return 1;
+        }
+        return Math.pow(scale / x, shape);
     }
 
     /**
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 612a89c..f1f90a8 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
@@ -112,6 +112,12 @@
         }
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public double survivalProbability(double x) {
+        return cumulativeProbability(-x);
+    }
+
     /**
      * {@inheritDoc}
      *
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 d79c001..60505d9 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
@@ -126,7 +126,17 @@
             return 0;
         }
 
-        return 1 - Math.exp(-Math.pow(x / scale, shape));
+        return -Math.expm1(-Math.pow(x / scale, shape));
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double survivalProbability(double x) {
+        if (x <= SUPPORT_LO) {
+            return 1;
+        }
+
+        return Math.exp(-Math.pow(x / scale, shape));
     }
 
     /**
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 8e2ce43..ee0aec1 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
@@ -29,10 +29,13 @@
 
     static final double[] ALPHA_BETAS = {0.1, 1, 10, 100, 1000};
     static final double EPSILON = StatUtils.min(ALPHA_BETAS);
+    static final double[] CUMULATIVE_TEST_POINTS = new double[]{
+        -0.1, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1
+    };
 
     @Test
     void testCumulative() {
-        final double[] x = new double[]{-0.1, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1};
+        final double[] x = CUMULATIVE_TEST_POINTS;
         // all test data computed using R 2.5
         checkCumulative(0.1, 0.1,
                         x, new double[]{
@@ -149,6 +152,72 @@
                             0.710208, 0.873964, 0.966656, 0.997272, 1.000000, 1.000000});
     }
 
+    @Test
+    void testSurvival() {
+        checkSurvival(1, 1, new double[]{
+            0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0});
+        checkSurvival(4, 1, new double[]{
+            0.0000, 0.0000, 0.0001, 0.0016, 0.0081, 0.0256, 0.0625, 0.1296, 0.2401,
+            0.4096, 0.6561, 1.0000, 1.0000});
+        checkSurvival(2, 4, new double[]{
+            0.00000, 0.00000, 0.08146, 0.26272, 0.47178, 0.66304, 0.81250, 0.91296,
+            0.96922, 0.99328, 0.99954, 1.00000, 1.00000});
+    }
+
+    @Test
+    void testCumulativeAndSurvivalComplement() {
+        checkCumulativeSurvivalComplement(0.1, 0.1);
+        checkCumulativeSurvivalComplement(0.1, 0.5);
+        checkCumulativeSurvivalComplement(0.1, 1.0);
+        checkCumulativeSurvivalComplement(0.1, 2.0);
+        checkCumulativeSurvivalComplement(0.1, 4.0);
+        checkCumulativeSurvivalComplement(0.5, 0.1);
+        checkCumulativeSurvivalComplement(0.5, 0.5);
+        checkCumulativeSurvivalComplement(0.5, 1.0);
+        checkCumulativeSurvivalComplement(0.5, 2.0);
+        checkCumulativeSurvivalComplement(0.5, 4.0);
+        checkCumulativeSurvivalComplement(1.0, 0.1);
+        checkCumulativeSurvivalComplement(1.0, 0.5);
+        checkCumulativeSurvivalComplement(1, 1);
+        checkCumulativeSurvivalComplement(1, 2);
+        checkCumulativeSurvivalComplement(1, 4);
+        checkCumulativeSurvivalComplement(2.0, 0.1);
+        checkCumulativeSurvivalComplement(2, 1);
+        checkCumulativeSurvivalComplement(2.0, 0.5);
+        checkCumulativeSurvivalComplement(2, 2);
+        checkCumulativeSurvivalComplement(2, 4);
+        checkCumulativeSurvivalComplement(4.0, 0.1);
+        checkCumulativeSurvivalComplement(4.0, 0.5);
+        checkCumulativeSurvivalComplement(4, 1);
+        checkCumulativeSurvivalComplement(4, 2);
+        checkCumulativeSurvivalComplement(4, 4);
+    }
+
+    /** Precision tests for verifying that CDF calculates accurately in cases where 1-cdf(x) is inaccurately 1. */
+    @Test
+    void testCumulativePrecision() {
+        // Calculated using WolframAlpha
+        checkCumulativePrecision(5.0, 5.0, 0.0001, 1.2595800539968654e-18);
+        checkCumulativePrecision(4.0, 5.0, 0.00001, 6.999776002800025e-19);
+        checkCumulativePrecision(5.0, 4.0, 0.0001, 5.598600119996539e-19);
+        checkCumulativePrecision(6.0, 2.0, 0.001, 6.994000000000028e-18);
+        checkCumulativePrecision(2.0, 6.0, 1e-9, 2.0999999930000014e-17);
+    }
+
+    /**
+     * Precision tests for verifying that survivalFunction calculates accurately in cases
+     * where 1-sf(x) is inaccurately 1.
+     */
+    @Test
+    void testSurvivalPrecision() {
+        // Calculated using WolframAlpha
+        checkSurvivalPrecision(5.0, 5.0, 0.9999, 1.2595800539961496e-18);
+        checkSurvivalPrecision(4.0, 5.0, 0.9999, 5.598600119993397e-19);
+        checkSurvivalPrecision(5.0, 4.0, 0.99998, 1.1199283217964632e-17);
+        checkSurvivalPrecision(6.0, 2.0, 0.999999999, 2.0999998742158932e-17);
+        checkSurvivalPrecision(2.0, 6.0, 0.999, 6.994000000000077e-18);
+    }
+
     private void checkCumulative(double alpha, double beta, double[] x, double[] cumes) {
         final BetaDistribution d = new BetaDistribution(alpha, beta);
         for (int i = 0; i < x.length; i++) {
@@ -160,6 +229,41 @@
         }
     }
 
+    private void checkSurvival(double alpha, double beta, double[] cumes) {
+        final BetaDistribution d = new BetaDistribution(alpha, beta);
+        for (int i = 0; i < CUMULATIVE_TEST_POINTS.length; i++) {
+            Assertions.assertEquals(1 - cumes[i], d.survivalProbability(CUMULATIVE_TEST_POINTS[i]), 1e-8);
+        }
+    }
+
+    private void checkCumulativeSurvivalComplement(double alpha, double beta) {
+        final BetaDistribution d = new BetaDistribution(alpha, beta);
+        for (int i = 0; i < CUMULATIVE_TEST_POINTS.length; i++) {
+            double x = CUMULATIVE_TEST_POINTS[i];
+            Assertions.assertEquals(1, d.cumulativeProbability(x) + d.survivalProbability(x), 1e-8);
+        }
+    }
+
+    private void checkCumulativePrecision(double alpha, double beta, double value, double expected) {
+        final double tolerance = 1e-22;
+        BetaDistribution d = new BetaDistribution(alpha, beta);
+        TestUtils.assertEquals(
+                "cumulative probability not precise at " + value + " for a=" + alpha + " & b=" + beta,
+                d.cumulativeProbability(value),
+                expected,
+                tolerance);
+    }
+
+    private void checkSurvivalPrecision(double alpha, double beta, double value, double expected) {
+        final double tolerance = 1e-22;
+        BetaDistribution d = new BetaDistribution(alpha, beta);
+        TestUtils.assertEquals(
+                "survival function not precise at " + value + " for a=" + alpha + " & b=" + beta,
+                d.survivalProbability(value),
+                expected,
+                tolerance);
+    }
+
     @Test
     void testDensity() {
         final double[] x = new double[]{1e-6, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9};
diff --git a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/CauchyDistributionTest.java b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/CauchyDistributionTest.java
index 4d07667..61a283e 100644
--- a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/CauchyDistributionTest.java
+++ b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/CauchyDistributionTest.java
@@ -28,7 +28,7 @@
  */
 class CauchyDistributionTest extends ContinuousDistributionAbstractTest {
 
-    private static final double DEFAULT_TOLERANCE = 1e-7;
+    private static final double DEFAULT_TOLERANCE = 1e-8;
 
     //---------------------- Override tolerance --------------------------------
 
@@ -67,6 +67,28 @@
                              1.49599158008e-06, 0.000149550440335, 0.000933076881878, 0.00370933207799, 0.0144742330437};
     }
 
+    @Override
+    public double[] makeCumulativePrecisionTestPoints() {
+        return new double[] {-1e16};
+    }
+
+    @Override
+    public double[] makeCumulativePrecisionTestValues() {
+        // These were created using WolframAlpha
+        return new double[] {5.551115123125783e-17};
+    }
+
+    @Override
+    public double[] makeSurvivalPrecisionTestPoints() {
+        return new double[] {1e16};
+    }
+
+    @Override
+    public double[] makeSurvivalPrecisionTestValues() {
+        // These were created using WolframAlpha
+        return makeCumulativePrecisionTestValues();
+    }
+
     //-------------------- Additional test cases -------------------------------
 
     @Test
diff --git a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/ChiSquaredDistributionTest.java b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/ChiSquaredDistributionTest.java
index 921129b..e8030a3 100644
--- a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/ChiSquaredDistributionTest.java
+++ b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/ChiSquaredDistributionTest.java
@@ -79,6 +79,28 @@
                              0.000433630076361, 0.00412780610309, 0.00999340341045, 0.0193246438937, 0.0368460089216};
     }
 
+    @Override
+    public double[] makeCumulativePrecisionTestPoints() {
+        return new double[] {1e-7, 4e-7, 9e-8};
+    }
+
+    @Override
+    public double[] makeCumulativePrecisionTestValues() {
+        // These were created using WolframAlpha
+        return new double[] {1.6820882879388572e-19, 5.382681944688393e-18, 1.292572946953654e-19};
+    }
+
+    @Override
+    public double[] makeSurvivalPrecisionTestPoints() {
+        return new double[] {93, 97.3};
+    }
+
+    @Override
+    public double[] makeSurvivalPrecisionTestValues() {
+        // These were created using WolframAlpha
+        return new double[] {1.5731947657596637e-18, 1.9583114656146269e-19};
+    }
+
     //-------------------- Additional test cases -------------------------------
 
     @Test
diff --git a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/ContinuousDistributionAbstractTest.java b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/ContinuousDistributionAbstractTest.java
index b7044f8..260d7c7 100644
--- a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/ContinuousDistributionAbstractTest.java
+++ b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/ContinuousDistributionAbstractTest.java
@@ -46,6 +46,20 @@
  * makeInverseCumulativeTestPoints() -- arguments used to test inverse cdf
  * makeInverseCumulativeTestValues() -- expected inverse cdf values
  * <p>
+ * If the continuous distribution provides higher precision implementations of cumulativeProbability
+ * and/or survivalProbability, the following methods should be implemented to provide testing.
+ * To use these tests, calculate the cumulativeProbability and survivalProbability such that their naive
+ * complement is exceptionally close to `1` and consequently could lose precision due to floating point
+ * arithmetic.
+ *
+ * NOTE: The default high-precision threshold is 1e-22.
+ * <pre>
+ * makeCumulativePrecisionTestPoints() -- high precision test inputs
+ * makeCumulativePrecisionTestValues() -- high precision expected results
+ * makeSurvivalPrecisionTestPoints() -- high precision test inputs
+ * makeSurvivalPrecisionTestValues() -- high precision expected results
+ * </pre>
+ * <p>
  * To implement additional test cases with different distribution instances and
  * test data, use the setXxx methods for the instance data in test cases and
  * call the verifyXxx methods to verify results.
@@ -69,12 +83,27 @@
     /** Tolerance used in comparing expected and returned values */
     private double tolerance = 1e-4;
 
+    /** Tolerance used in high precision tests */
+    private final double highPrecisionTolerance = 1e-22;
+
     /** Arguments used to test cumulative probability density calculations */
     private double[] cumulativeTestPoints;
 
     /** Values used to test cumulative probability density calculations */
     private double[] cumulativeTestValues;
 
+    /** Arguments used to test cumulative probability precision, effectively any x where 1-cdf(x) would result in 1 */
+    private double[] cumulativePrecisionTestPoints;
+
+    /** Values used to test cumulative probability precision, usually exceptionally tiny values */
+    private double[] cumulativePrecisionTestValues;
+
+    /** Arguments used to test survival probability precision, effectively any x where 1-sf(x) would result in 1 */
+    private double[] survivalPrecisionTestPoints;
+
+    /** Values used to test survival probability precision, usually exceptionally tiny values */
+    private double[] survivalPrecisionTestValues;
+
     /** Arguments used to test inverse cumulative probability density calculations */
     private double[] inverseCumulativeTestPoints;
 
@@ -98,6 +127,34 @@
     /** Creates the default cumulative probability test expected values */
     public abstract double[] makeCumulativeTestValues();
 
+    /** Creates the default cumulative probability precision test input values */
+    public double[] makeCumulativePrecisionTestPoints() {
+        return new double[0];
+    }
+
+    /**
+     * Creates the default cumulative probability precision test expected values.
+     * Note: The default threshold is 1e-22, any expected values with much higher precision may
+     *       not test the desired results without increasing precision threshold.
+     */
+    public double[] makeCumulativePrecisionTestValues() {
+        return new double[0];
+    }
+
+    /** Creates the default survival probability precision test input values */
+    public double[] makeSurvivalPrecisionTestPoints() {
+        return new double[0];
+    }
+
+    /**
+     * Creates the default survival probability precision test expected values.
+     * Note: The default threshold is 1e-22, any expected values with much higher precision may
+     *       not test the desired results without increasing precision threshold.
+     */
+    public double[] makeSurvivalPrecisionTestValues() {
+        return new double[0];
+    }
+
     /** Creates the default density test expected values */
     public abstract double[] makeDensityTestValues();
 
@@ -138,6 +195,10 @@
         distribution = makeDistribution();
         cumulativeTestPoints = makeCumulativeTestPoints();
         cumulativeTestValues = makeCumulativeTestValues();
+        cumulativePrecisionTestPoints = makeCumulativePrecisionTestPoints();
+        cumulativePrecisionTestValues = makeCumulativePrecisionTestValues();
+        survivalPrecisionTestPoints = makeSurvivalPrecisionTestPoints();
+        survivalPrecisionTestValues = makeSurvivalPrecisionTestValues();
         inverseCumulativeTestPoints = makeInverseCumulativeTestPoints();
         inverseCumulativeTestValues = makeInverseCumulativeTestValues();
         densityTestValues = makeDensityTestValues();
@@ -191,6 +252,54 @@
         }
     }
 
+    protected void verifySurvivalProbability() {
+        for (int i = 0; i < cumulativeTestPoints.length; i++) {
+            TestUtils.assertEquals("Incorrect survival probability value returned for " +
+                            cumulativeTestPoints[i], 1 - cumulativeTestValues[i],
+                    distribution.survivalProbability(cumulativeTestPoints[i]),
+                    getTolerance());
+        }
+    }
+
+    protected void verifySurvivalAndCumulativeProbabilityComplement() {
+        for (final double x : cumulativeTestPoints) {
+            TestUtils.assertEquals("survival + cumulative probability were not close to 1.0" +
+                    x, 1.0,
+                distribution.survivalProbability(x) + distribution.cumulativeProbability(x),
+                getTolerance());
+        }
+    }
+
+    /**
+     * Verifies that survival is simply not 1-cdf by testing calculations that would underflow that calculation and
+     * result in an inaccurate answer.
+     */
+    protected void verifySurvivalProbabilityPrecision() {
+        for (int i = 0; i < survivalPrecisionTestPoints.length; i++) {
+            final double x = survivalPrecisionTestPoints[i];
+            TestUtils.assertEquals(
+                    "survival probability is not precise for value " + x,
+                    survivalPrecisionTestValues[i],
+                    distribution.survivalProbability(x),
+                    getHighPrecisionTolerance());
+        }
+    }
+
+    /**
+     * Verifies that CDF is simply not 1-survival function by testing values that would result with inaccurate results
+     * if simply calculating 1-survival function.
+     */
+    protected void verifyCumulativeProbabilityPrecision() {
+        for (int i = 0; i < cumulativePrecisionTestPoints.length; i++) {
+            final double x = cumulativePrecisionTestPoints[i];
+            TestUtils.assertEquals(
+                    "cumulative probability is not precise for value " + x,
+                    cumulativePrecisionTestValues[i],
+                    distribution.cumulativeProbability(x),
+                    getHighPrecisionTolerance());
+        }
+    }
+
     /**
      * Verifies that inverse cumulative probability density calculations match expected values
      * using current test instance data
@@ -239,6 +348,26 @@
         verifyCumulativeProbabilities();
     }
 
+    @Test
+    void testSurvivalProbability() {
+        verifySurvivalProbability();
+    }
+
+    @Test
+    void testSurvivalAndCumulativeProbabilitiesAreComplementary() {
+        verifySurvivalAndCumulativeProbabilityComplement();
+    }
+
+    @Test
+    void testCumulativeProbabilityPrecision() {
+        verifyCumulativeProbabilityPrecision();
+    }
+
+    @Test
+    void testSurvivalProbabilityPrecision() {
+        verifySurvivalProbabilityPrecision();
+    }
+
     /**
      * Verifies that inverse cumulative probability density calculations match expected values
      * using default test instance data
@@ -319,6 +448,8 @@
         Assertions.assertEquals(Double.NEGATIVE_INFINITY, distribution.logDensity(above));
         Assertions.assertEquals(0d, distribution.cumulativeProbability(below));
         Assertions.assertEquals(1d, distribution.cumulativeProbability(above));
+        Assertions.assertEquals(1d, distribution.survivalProbability(below));
+        Assertions.assertEquals(0d, distribution.survivalProbability(above));
     }
 
     /**
@@ -497,6 +628,13 @@
     }
 
     /**
+     * @return Returns the high precision tolerance
+     */
+    protected double getHighPrecisionTolerance() {
+        return highPrecisionTolerance;
+    }
+
+    /**
      * @param tolerance The tolerance to set.
      */
     protected void setTolerance(double tolerance) {
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 c62f638..2be38be 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
@@ -65,6 +65,29 @@
                              0.00200000000002, 0.00499999999997, 0.00999999999994, 0.0199999999999};
     }
 
+    @Override
+    public double[] makeCumulativePrecisionTestPoints() {
+        return new double[] {1e-15, 4e-16, 9e-16};
+    }
+
+    @Override
+    public double[] makeCumulativePrecisionTestValues() {
+        // calculated via scipy, specifically expon.cdf(x/5).
+        // WolframAlpha provided either too accurate or inaccurate values
+        return new double[] {2.0000000000000002e-16, 7.999999999999999e-17, 1.8000000000000002e-16};
+    }
+
+    @Override
+    public double[] makeSurvivalPrecisionTestPoints() {
+        return new double[] {183, 197};
+    }
+
+    @Override
+    public double[] makeSurvivalPrecisionTestValues() {
+        // These were created using WolframAlpha. NOTE lambda parameter is 1/mean
+        return new double[] {1.2729811194234181e-16, 7.741006159285781e-18};
+    }
+
     //------------ Additional tests -------------------------------------------
 
     @Test
diff --git a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/FDistributionTest.java b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/FDistributionTest.java
index ae87559..8f692b2 100644
--- a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/FDistributionTest.java
+++ b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/FDistributionTest.java
@@ -63,6 +63,28 @@
                              0.000133443915657, 0.00286681303403, 0.00969192007502, 0.0242883861471, 0.0605491314658};
     }
 
+    @Override
+    public double[] makeCumulativePrecisionTestPoints() {
+        return new double[] {1e-7, 4e-8, 9e-8};
+    }
+
+    @Override
+    public double[] makeCumulativePrecisionTestValues() {
+        // These were created using WolframAlpha
+        return new double[] {1.578691625481747e-17, 1.597523916857153e-18, 1.2131195257872846e-17};
+    }
+
+    @Override
+    public double[] makeSurvivalPrecisionTestPoints() {
+        return new double[] {1e6, 42e5, 63e5};
+    }
+
+    @Override
+    public double[] makeSurvivalPrecisionTestValues() {
+        // These were created using WolframAlpha
+        return new double[] {1.1339943867175144e-17, 1.5306104409634358e-19, 4.535143828961954e-20};
+    }
+
     //-------------------- Additional test cases -------------------------------
 
     @Test
diff --git a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/GammaDistributionTest.java b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/GammaDistributionTest.java
index 11e3b93..72abf34 100644
--- a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/GammaDistributionTest.java
+++ b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/GammaDistributionTest.java
@@ -73,6 +73,28 @@
                              0.000394468852816, 0.00366559696761, 0.00874649473311, 0.0166712508128, 0.0311798227954};
     }
 
+    @Override
+    public double[] makeCumulativePrecisionTestPoints() {
+        return new double[] {1e-4, 9e-5};
+    }
+
+    @Override
+    public double[] makeCumulativePrecisionTestValues() {
+        // These were created using WolframAlpha
+        return new double[] {2.6040625021701086e-19, 1.7085322417782863e-19};
+    }
+
+    @Override
+    public double[] makeSurvivalPrecisionTestPoints() {
+        return new double[] {99, 103};
+    }
+
+    @Override
+    public double[] makeSurvivalPrecisionTestValues() {
+        // These were created using WolframAlpha
+        return new double[] {6.833817088979342e-18, 1.0390567840208212e-18};
+    }
+
     //-------------------- Additional test cases -------------------------------
 
     @Test
diff --git a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/GumbelDistributionTest.java b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/GumbelDistributionTest.java
index e90fde0..ea90adf 100644
--- a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/GumbelDistributionTest.java
+++ b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/GumbelDistributionTest.java
@@ -54,6 +54,28 @@
         };
     }
 
+    @Override
+    public double[] makeCumulativePrecisionTestPoints() {
+        return new double[] {-7.0, -7.1};
+    }
+
+    @Override
+    public double[] makeCumulativePrecisionTestValues() {
+        // These were created using WolframAlpha, specifically =CDF[ExtremeValueDistribution[0.5, 2], x]
+        return new double[] {3.414512624812977e-19, 3.859421750095851e-20};
+    }
+
+    @Override
+    public double[] makeSurvivalPrecisionTestPoints() {
+        return new double[] {75};
+    }
+
+    @Override
+    public double[] makeSurvivalPrecisionTestValues() {
+        // Not calculated via WolframAlpha, instead utilizing scipy gumbel_r, Wolframalpha could not get precise enough
+        return new double[] {6.64554417291507e-17};
+    }
+
     //-------------------- Additional test cases -------------------------------
 
     @Test
diff --git a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/LaplaceDistributionTest.java b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/LaplaceDistributionTest.java
index ce066b5..117f4a1 100644
--- a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/LaplaceDistributionTest.java
+++ b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/LaplaceDistributionTest.java
@@ -54,6 +54,30 @@
         };
     }
 
+    @Override
+    public double[] makeCumulativePrecisionTestPoints() {
+        // Negative values only as anything > 0 falls into an imprecise calculation by being subtracted from 1
+        return new double[] {-42, -43};
+    }
+
+    @Override
+    public double[] makeCumulativePrecisionTestValues() {
+        // These were created using WolframAlpha
+        return new double[] {2.87476113214678e-19, 1.0575655187955402e-19};
+    }
+
+    @Override
+    public double[] makeSurvivalPrecisionTestPoints() {
+        // Positive values only as anything < 0 falls into an imprecise calculation by being subtracted from 1
+        return new double[] {42, 43};
+    }
+
+    @Override
+    public double[] makeSurvivalPrecisionTestValues() {
+        // These were created using WolframAlpha
+        return new double[] {2.87476113214678e-19, 1.0575655187955402e-19};
+    }
+
     //-------------------- Additional test cases -------------------------------
 
     @Test
diff --git a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/LevyDistributionTest.java b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/LevyDistributionTest.java
index 977b4dd..558a3d7 100644
--- a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/LevyDistributionTest.java
+++ b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/LevyDistributionTest.java
@@ -65,6 +65,28 @@
             -2.650679030597d, -3.644945255983d};
     }
 
+    @Override
+    public double[] makeCumulativePrecisionTestPoints() {
+        return new double[] {1.205, 1.2049};
+    }
+
+    @Override
+    public double[] makeCumulativePrecisionTestValues() {
+        // These were created using WolframAlpha
+        return new double[] {3.7440973842063723e-19, 1.6388396909072308e-19};
+    }
+
+    @Override
+    public double[] makeSurvivalPrecisionTestPoints() {
+        return new double[] {1e39, 42e37};
+    }
+
+    @Override
+    public double[] makeSurvivalPrecisionTestValues() {
+        // These were created using WolframAlpha
+        return new double[] {1.5957691216057308e-20, 2.4623252122982907e-20};
+    }
+
     //-------------------- Additional test cases -------------------------------
 
     @Test
diff --git a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/LogNormalDistributionTest.java b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/LogNormalDistributionTest.java
index f5c5cb6..711602a 100644
--- a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/LogNormalDistributionTest.java
+++ b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/LogNormalDistributionTest.java
@@ -32,7 +32,7 @@
 
     @BeforeEach
     void customSetUp() {
-        setTolerance(1e-7);
+        setTolerance(1e-10);
     }
 
     //-------------- Implementations for abstract methods ----------------------
@@ -102,6 +102,28 @@
         //return Arrays.copyOfRange(points, 1, points.length - 4);
     }
 
+    @Override
+    public double[] makeCumulativePrecisionTestPoints() {
+        return new double[] {4e-5, 7e-5};
+    }
+
+    @Override
+    public double[] makeCumulativePrecisionTestValues() {
+        // These were created using WolframAlpha
+        return new double[] {1.2366527173500762e-18, 3.9216120913158885e-17};
+    }
+
+    @Override
+    public double[] makeSurvivalPrecisionTestPoints() {
+        return new double[] {999999, 2e6};
+    }
+
+    @Override
+    public double[] makeSurvivalPrecisionTestValues() {
+        // These were created using WolframAlpha
+        return new double[] {2.924727705260493e-17, 3.8830698713006447e-19};
+    }
+
     //-------------------- Additional test cases -------------------------------
 
     private void verifyQuantiles() {
@@ -169,6 +191,15 @@
     }
 
     @Test
+    void testSurvivalProbabilityExtremes() {
+        // Use a small shape parameter so that we can exceed 40 * shape
+        setDistribution(new LogNormalDistribution(1, 0.0001));
+        setCumulativeTestPoints(new double[] {0.5, 10});
+        setCumulativeTestValues(new double[] {0, 1.0});
+        verifySurvivalProbability();
+    }
+
+    @Test
     void testParameterAccessors() {
         final LogNormalDistribution distribution = (LogNormalDistribution)getDistribution();
         Assertions.assertEquals(2.1, distribution.getScale());
diff --git a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/LogisticsDistributionTest.java b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/LogisticsDistributionTest.java
index e334b48..e0d9ef0 100644
--- a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/LogisticsDistributionTest.java
+++ b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/LogisticsDistributionTest.java
@@ -54,6 +54,28 @@
         };
     }
 
+    @Override
+    public double[] makeCumulativePrecisionTestPoints() {
+        return new double[] {-197, -203};
+    }
+
+    @Override
+    public double[] makeCumulativePrecisionTestValues() {
+        // These were created using WolframAlpha
+        return new double[] {5.188951605054656e-18, 1.5628821893349888e-18};
+    }
+
+    @Override
+    public double[] makeSurvivalPrecisionTestPoints() {
+        return new double[] {197, 203};
+    }
+
+    @Override
+    public double[] makeSurvivalPrecisionTestValues() {
+        // These were created using WolframAlpha
+        return new double[] {1.1548224173015786e-17, 3.478258278776922e-18};
+    }
+
     //-------------------- Additional test cases -------------------------------
 
     @Test
diff --git a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/NakagamiDistributionTest.java b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/NakagamiDistributionTest.java
index d9d5934..a158c80 100644
--- a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/NakagamiDistributionTest.java
+++ b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/NakagamiDistributionTest.java
@@ -54,6 +54,28 @@
         };
     }
 
+    @Override
+    public double[] makeCumulativePrecisionTestPoints() {
+        return new double[] {1e-16, 4e-17};
+    }
+
+    @Override
+    public double[] makeCumulativePrecisionTestValues() {
+        // These were created using WolframAlpha
+        return new double[] {7.978845608028653e-17, 3.1915382432114614e-17};
+    }
+
+    @Override
+    public double[] makeSurvivalPrecisionTestPoints() {
+        return new double[] {9, 8.7};
+    }
+
+    @Override
+    public double[] makeSurvivalPrecisionTestValues() {
+        // These were created using WolframAlpha
+        return new double[] {2.2571768119076845e-19, 3.318841739929575e-18};
+    }
+
     //-------------------- Additional test cases -------------------------------
 
     @Test
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 87fd6a9..940d162 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
@@ -28,7 +28,7 @@
  */
 class NormalDistributionTest extends ContinuousDistributionAbstractTest {
 
-    private static final double DEFAULT_TOLERANCE = 1e-7;
+    private static final double DEFAULT_TOLERANCE = 1e-10;
 
     //---------------------- Override tolerance --------------------------------
 
@@ -67,6 +67,28 @@
                              0.00240506434076, 0.0190372444310, 0.0417464784322, 0.0736683145538, 0.125355951380};
     }
 
+    @Override
+    public double[] makeCumulativePrecisionTestPoints() {
+        return new double[] {-10, -10.3};
+    }
+
+    @Override
+    public double[] makeCumulativePrecisionTestValues() {
+        // These were created using WolframAlpha
+        return new double[] {2.741222634611109e-18, 4.1045652533919113e-19};
+    }
+
+    @Override
+    public double[] makeSurvivalPrecisionTestPoints() {
+        return new double[] {14.5, 13.9};
+    }
+
+    @Override
+    public double[] makeSurvivalPrecisionTestValues() {
+        // These were created using WolframAlpha
+        return new double[] {4.1045652533919576e-19, 1.749552800697539e-17};
+    }
+
     //-------------------- Additional test cases -------------------------------
 
     private void verifyQuantiles() {
diff --git a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/ParetoDistributionTest.java b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/ParetoDistributionTest.java
index 73b13d3..9d962df 100644
--- a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/ParetoDistributionTest.java
+++ b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/ParetoDistributionTest.java
@@ -31,7 +31,7 @@
 
     @BeforeEach
     void customSetUp() {
-        setTolerance(1e-7);
+        setTolerance(1e-9);
     }
 
     //-------------- Implementations for abstract methods ----------------------
@@ -90,6 +90,28 @@
         return points2;
     }
 
+    @Override
+    public double[] makeCumulativePrecisionTestPoints() {
+        return new double[] {2.100000000000001, 2.100000000000005};
+    }
+
+    @Override
+    public double[] makeCumulativePrecisionTestValues() {
+        // These were not created using WolframAlpha, the calculation for Math.log underflows in java
+        return new double[] {6.217248937900875e-16, 3.2640556923979585e-15};
+    }
+
+    @Override
+    public double[] makeSurvivalPrecisionTestPoints() {
+        return new double[] {42e11, 64e11};
+    }
+
+    @Override
+    public double[] makeSurvivalPrecisionTestValues() {
+        // These were created using WolframAlpha
+        return new double[] {6.005622169907148e-18, 3.330082930386111e-18};
+    }
+
     //-------------------- Additional test cases -------------------------------
 
     private void verifyQuantiles() {
diff --git a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/TDistributionTest.java b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/TDistributionTest.java
index 0ef996a..bcab69f 100644
--- a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/TDistributionTest.java
+++ b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/TDistributionTest.java
@@ -63,6 +63,28 @@
                              0.000756494565517, 0.0109109752919, 0.0303377878006, 0.0637967988952, 0.128289492005};
     }
 
+    @Override
+    public double[] makeCumulativePrecisionTestPoints() {
+        return new double[] {-4200, -6400};
+    }
+
+    @Override
+    public double[] makeCumulativePrecisionTestValues() {
+        // These were created using WolframAlpha
+        return new double[] {7.261513520181652e-18, 8.838404680725267e-19};
+    }
+
+    @Override
+    public double[] makeSurvivalPrecisionTestPoints() {
+        return new double[] {4200, 6400};
+    }
+
+    @Override
+    public double[] makeSurvivalPrecisionTestValues() {
+        // These were created using WolframAlpha
+        return new double[] {7.261513520181652e-18, 8.838404680725267e-19};
+    }
+
     //-------------------- Additional test cases -------------------------------
 
     /**
diff --git a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/WeibullDistributionTest.java b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/WeibullDistributionTest.java
index 02df59a..c35056f 100644
--- a/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/WeibullDistributionTest.java
+++ b/commons-statistics-distribution/src/test/java/org/apache/commons/statistics/distribution/WeibullDistributionTest.java
@@ -19,6 +19,7 @@
 
 import org.apache.commons.numbers.gamma.LogGamma;
 import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 /**
@@ -28,6 +29,11 @@
  */
 class WeibullDistributionTest extends ContinuousDistributionAbstractTest {
 
+    @BeforeEach
+    void customSetUp() {
+        setTolerance(1e-10);
+    }
+
     //-------------- Implementations for abstract methods ----------------------
 
     /** Creates the default continuous distribution instance to use in tests. */
@@ -57,6 +63,28 @@
                              0.353441418887, 0.000788590320203, 0.00737060094841, 0.0177576041516, 0.0343043442574, 0.065664589369};
     }
 
+    @Override
+    public double[] makeCumulativePrecisionTestPoints() {
+        return new double[] {1e-14, 1e-15};
+    }
+
+    @Override
+    public double[] makeCumulativePrecisionTestValues() {
+        // These were created using WolframAlpha
+        return new double[] {6.506341377907031e-18, 4.1052238780858223e-19};
+    }
+
+    @Override
+    public double[] makeSurvivalPrecisionTestPoints() {
+        return new double[] {45, 47.2};
+    }
+
+    @Override
+    public double[] makeSurvivalPrecisionTestValues() {
+        // These were created using WolframAlpha
+        return new double[] {6.6352694710268576e-18, 6.444810903667567e-19};
+    }
+
     //-------------------- Additional test cases -------------------------------
 
     @Test