Author: erans Date: Mon Nov 26 13:25:27 2012 New Revision: 1413600 URL: http://svn.apache.org/viewvc?rev=1413600&view=rev Log: MATH-905 Avoid overflow on the whole range covered by the equivalent functions in the standard "Math" class.
Modified: commons/proper/math/trunk/src/main/java/org/apache/commons/math3/util/FastMath.java commons/proper/math/trunk/src/test/java/org/apache/commons/math3/util/FastMathTest.java Modified: commons/proper/math/trunk/src/main/java/org/apache/commons/math3/util/FastMath.java URL: http://svn.apache.org/viewvc/commons/proper/math/trunk/src/main/java/org/apache/commons/math3/util/FastMath.java?rev=1413600&r1=1413599&r2=1413600&view=diff ============================================================================== --- commons/proper/math/trunk/src/main/java/org/apache/commons/math3/util/FastMath.java (original) +++ commons/proper/math/trunk/src/main/java/org/apache/commons/math3/util/FastMath.java Mon Nov 26 13:25:27 2012 @@ -78,6 +78,8 @@ import java.io.PrintStream; * @since 2.2 */ public class FastMath { + /** StrictMath.log(Double.MAX_VALUE): {@value} */ + private static final double LOG_MAX_VALUE = StrictMath.log(Double.MAX_VALUE); /** Archimede's constant PI, ratio of circle circumference to diameter. */ public static final double PI = 105414357.0 / 33554432.0 + 1.984187159361080883e-9; @@ -389,15 +391,25 @@ public class FastMath { // for numbers with magnitude 20 or so, // exp(-z) can be ignored in comparison with exp(z) - if (x > 20.0) { - return exp(x)/2.0; - } - - if (x < -20) { - return exp(-x)/2.0; + if (x > 20) { + if (x >= LOG_MAX_VALUE) { + // Avoid overflow (MATH-905). + final double t = exp(0.5 * x); + return (0.5 * t) * t; + } else { + return 0.5 * exp(x); + } + } else if (x < -20) { + if (x <= -LOG_MAX_VALUE) { + // Avoid overflow (MATH-905). + final double t = exp(-0.5 * x); + return (0.5 * t) * t; + } else { + return 0.5 * exp(-x); + } } - double hiPrec[] = new double[2]; + final double hiPrec[] = new double[2]; if (x < 0.0) { x = -x; } @@ -449,12 +461,22 @@ public class FastMath { // for values of z larger than about 20, // exp(-z) can be ignored in comparison with exp(z) - if (x > 20.0) { - return exp(x)/2.0; - } - - if (x < -20) { - return -exp(-x)/2.0; + if (x > 20) { + if (x >= LOG_MAX_VALUE) { + // Avoid overflow (MATH-905). + final double t = exp(0.5 * x); + return (0.5 * t) * t; + } else { + return 0.5 * exp(x); + } + } else if (x < -20) { + if (x <= -LOG_MAX_VALUE) { + // Avoid overflow (MATH-905). + final double t = exp(-0.5 * x); + return (-0.5 * t) * t; + } else { + return -0.5 * exp(-x); + } } if (x == 0) { Modified: commons/proper/math/trunk/src/test/java/org/apache/commons/math3/util/FastMathTest.java URL: http://svn.apache.org/viewvc/commons/proper/math/trunk/src/test/java/org/apache/commons/math3/util/FastMathTest.java?rev=1413600&r1=1413599&r2=1413600&view=diff ============================================================================== --- commons/proper/math/trunk/src/test/java/org/apache/commons/math3/util/FastMathTest.java (original) +++ commons/proper/math/trunk/src/test/java/org/apache/commons/math3/util/FastMathTest.java Mon Nov 26 13:25:27 2012 @@ -158,6 +158,50 @@ public class FastMathTest { } @Test + public void testMath905LargePositive() { + final double start = StrictMath.log(Double.MAX_VALUE); + final double endT = StrictMath.sqrt(2) * StrictMath.sqrt(Double.MAX_VALUE); + final double end = 2 * StrictMath.log(endT); + + double maxErr = 0; + for (double x = start; x < end; x += 1e-3) { + final double tst = FastMath.cosh(x); + final double ref = Math.cosh(x); + maxErr = FastMath.max(maxErr, FastMath.abs(ref - tst) / FastMath.ulp(ref)); + } + Assert.assertEquals(0, maxErr, 3); + + for (double x = start; x < end; x += 1e-3) { + final double tst = FastMath.sinh(x); + final double ref = Math.sinh(x); + maxErr = FastMath.max(maxErr, FastMath.abs(ref - tst) / FastMath.ulp(ref)); + } + Assert.assertEquals(0, maxErr, 3); + } + + @Test + public void testMath905LargeNegative() { + final double start = -StrictMath.log(Double.MAX_VALUE); + final double endT = StrictMath.sqrt(2) * StrictMath.sqrt(Double.MAX_VALUE); + final double end = -2 * StrictMath.log(endT); + + double maxErr = 0; + for (double x = start; x > end; x -= 1e-3) { + final double tst = FastMath.cosh(x); + final double ref = Math.cosh(x); + maxErr = FastMath.max(maxErr, FastMath.abs(ref - tst) / FastMath.ulp(ref)); + } + Assert.assertEquals(0, maxErr, 3); + + for (double x = start; x > end; x -= 1e-3) { + final double tst = FastMath.sinh(x); + final double ref = Math.sinh(x); + maxErr = FastMath.max(maxErr, FastMath.abs(ref - tst) / FastMath.ulp(ref)); + } + Assert.assertEquals(0, maxErr, 3); + } + + @Test public void testHyperbolicInverses() { double maxErr = 0; for (double x = -30; x < 30; x += 0.01) {