Added erf(double,double) to Erf and used this to improve tail probability accuracy in NormalDistributionImpl. JIRA: MATH-364. Reported and patched by Christian Winter.

git-svn-id: https://svn.apache.org/repos/asf/commons/proper/math/trunk@1163888 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Phil Steitz 2011-09-01 01:24:37 +00:00
parent 32da645aa8
commit 5786c5c944
5 changed files with 85 additions and 7 deletions

View File

@ -245,6 +245,9 @@
<contributor> <contributor>
<name>J&#246;rg Weimar</name> <name>J&#246;rg Weimar</name>
</contributor> </contributor>
<contributor>
<name>Christian Winter</name>
</contributor>
<contributor> <contributor>
<name>Xiaogang Zhang</name> <name>Xiaogang Zhang</name>
</contributor> </contributor>

View File

@ -21,6 +21,7 @@ import java.io.Serializable;
import org.apache.commons.math.MathException; import org.apache.commons.math.MathException;
import org.apache.commons.math.exception.NotStrictlyPositiveException; import org.apache.commons.math.exception.NotStrictlyPositiveException;
import org.apache.commons.math.exception.NumberIsTooLargeException;
import org.apache.commons.math.exception.util.LocalizedFormats; import org.apache.commons.math.exception.util.LocalizedFormats;
import org.apache.commons.math.special.Erf; import org.apache.commons.math.special.Erf;
import org.apache.commons.math.util.FastMath; import org.apache.commons.math.util.FastMath;
@ -40,8 +41,10 @@ public class NormalDistributionImpl extends AbstractContinuousDistribution
public static final double DEFAULT_INVERSE_ABSOLUTE_ACCURACY = 1e-9; public static final double DEFAULT_INVERSE_ABSOLUTE_ACCURACY = 1e-9;
/** Serializable version identifier. */ /** Serializable version identifier. */
private static final long serialVersionUID = 8589540077390120676L; private static final long serialVersionUID = 8589540077390120676L;
/** &sqrt;(2 &pi;) */ /** &radic;(2 &pi;) */
private static final double SQRT2PI = FastMath.sqrt(2 * FastMath.PI); private static final double SQRT2PI = FastMath.sqrt(2 * FastMath.PI);
/** &radic;(2) */
private static final double SQRT2 = FastMath.sqrt(2.0);
/** Mean of this distribution. */ /** Mean of this distribution. */
private final double mean; private final double mean;
/** Standard deviation of this distribution. */ /** Standard deviation of this distribution. */
@ -125,7 +128,22 @@ public class NormalDistributionImpl extends AbstractContinuousDistribution
if (FastMath.abs(dev) > 40 * standardDeviation) { if (FastMath.abs(dev) > 40 * standardDeviation) {
return dev < 0 ? 0.0d : 1.0d; return dev < 0 ? 0.0d : 1.0d;
} }
return 0.5 * (1 + Erf.erf(dev / (standardDeviation * FastMath.sqrt(2)))); return 0.5 * (1 + Erf.erf(dev / (standardDeviation * SQRT2)));
}
/**
* {@inheritDoc}
*/
@Override
public double cumulativeProbability(double x0, double x1) throws MathException {
if (x0 > x1) {
throw new NumberIsTooLargeException(LocalizedFormats.LOWER_ENDPOINT_ABOVE_UPPER_ENDPOINT,
x0, x1, true);
}
final double denom = standardDeviation * SQRT2;
final double v0 = (x0 - mean) / denom;
final double v1 = (x1 - mean) / denom;
return 0.5 * Erf.erf(v0, v1);
} }
/** /**

View File

@ -25,6 +25,19 @@ import org.apache.commons.math.util.FastMath;
* @version $Id$ * @version $Id$
*/ */
public class Erf { public class Erf {
/**
* The number {@code X_CRIT} is used by {@link #erf(double, double)} internally.
* This number solves {@code erf(x)=0.5} within 1ulp.
* More precisely, the current implementations of
* {@link #erf(double)} and {@link #erfc(double)} satisfy:<br/>
* {@code erf(X_CRIT) < 0.5},<br/>
* {@code erf(Math.nextUp(X_CRIT) > 0.5},<br/>
* {@code erfc(X_CRIT) = 0.5}, and<br/>
* {@code erfc(Math.nextUp(X_CRIT) < 0.5}
*/
private static final double X_CRIT = 0.4769362762044697;
/** /**
* Default constructor. Prohibit instantiation. * Default constructor. Prohibit instantiation.
*/ */
@ -54,11 +67,8 @@ public class Erf {
if (FastMath.abs(x) > 40) { if (FastMath.abs(x) > 40) {
return x > 0 ? 1 : -1; return x > 0 ? 1 : -1;
} }
double ret = Gamma.regularizedGammaP(0.5, x * x, 1.0e-15, 10000); final double ret = Gamma.regularizedGammaP(0.5, x * x, 1.0e-15, 10000);
if (x < 0) { return x < 0 ? -ret : ret;
ret = -ret;
}
return ret;
} }
/** /**
@ -91,5 +101,30 @@ public class Erf {
final double ret = Gamma.regularizedGammaQ(0.5, x * x, 1.0e-15, 10000); final double ret = Gamma.regularizedGammaQ(0.5, x * x, 1.0e-15, 10000);
return x < 0 ? 2 - ret : ret; return x < 0 ? 2 - ret : ret;
} }
/**
* Returns the difference between erf(x1) and erf(x2).
*
* The implementation uses either erf(double) or erfc(double)
* depending on which provides the most precise result.
*
* @param x1 the first value
* @param x2 the second value
* @return erf(x2) - erf(x1)
*/
public static double erf(double x1, double x2) {
if(x1 > x2) {
return -erf(x2, x1);
}
return
x1 < -X_CRIT ?
x2 < 0.0 ?
erfc(-x2) - erfc(-x1) :
erf(x2) - erf(x1) :
x2 > X_CRIT && x1 > 0.0 ?
erfc(x1) - erfc(x2) :
erf(x2) - erf(x1);
}
} }

View File

@ -52,6 +52,10 @@ The <action> type attribute can be add,update,fix,remove.
If the output is not quite correct, check for invisible trailing spaces! If the output is not quite correct, check for invisible trailing spaces!
--> -->
<release version="3.0" date="TBD" description="TBD"> <release version="3.0" date="TBD" description="TBD">
<action dev="psteitz" type="update" issue="MATH-364" due-to="Christian Winter">
Added erf(double,double) to Erf and used this to improve tail probability
accuracy in NormalDistributionImpl.
</action>
<action dev="psteitz" type="fix" issue="MATH-654"> <action dev="psteitz" type="fix" issue="MATH-654">
Enabled reseeding of the random generators used by EmpiricalDistributionImpl Enabled reseeding of the random generators used by EmpiricalDistributionImpl
and ValueServer. Modified ValueServer to pass its RandomData instance to and ValueServer. Modified ValueServer to pass its RandomData instance to

View File

@ -195,4 +195,22 @@ public class ErfTest {
TestUtils.assertRelativelyEquals(ref[i][1], result, 1E-13); TestUtils.assertRelativelyEquals(ref[i][1], result, 1E-13);
} }
} }
/**
* Test the implementation of Erf.erf(double, double) for consistency with results
* obtained from Erf.erf(double) and Erf.erfc(double).
*/
@Test
public void testTwoArgumentErf() throws Exception {
double[] xi = new double[]{-2.0, -1.0, -0.9, -0.1, 0.0, 0.1, 0.9, 1.0, 2.0};
for(double x1 : xi) {
for(double x2 : xi) {
double a = Erf.erf(x1, x2);
double b = Erf.erf(x2) - Erf.erf(x1);
double c = Erf.erfc(x1) - Erf.erfc(x2);
Assert.assertEquals(a, b, 1E-15);
Assert.assertEquals(a, c, 1E-15);
}
}
}
} }