[Jython-checkins] jython: Tighten up on edge cases in math.acosh and atanh.

jeff.allen jython-checkins at python.org
Wed Dec 31 02:41:09 CET 2014


https://hg.python.org/jython/rev/705cee780e1f
changeset:   7487:705cee780e1f
user:        Jeff Allen <ja.py at farowl.co.uk>
date:        Tue Dec 30 18:12:49 2014 +0000
summary:
  Tighten up on edge cases in math.acosh and atanh.

Amends test_math_jy to run the CPython test_math.MathTests again,
but expecting accurate results. Our acosh and atanh are re-written to
pass this stricter test. Loosely related to issue #2237.

files:
  Lib/test/test_math_jy.py         |  31 +++++++-
  src/org/python/modules/math.java |  79 +++++++++++++------
  2 files changed, 84 insertions(+), 26 deletions(-)


diff --git a/Lib/test/test_math_jy.py b/Lib/test/test_math_jy.py
--- a/Lib/test/test_math_jy.py
+++ b/Lib/test/test_math_jy.py
@@ -5,6 +5,7 @@
 import math
 import unittest
 from test import test_support
+from java.lang import Math
 
 inf = float('inf')
 ninf = float('-inf')
@@ -23,6 +24,8 @@
 
     def test_hypot(self):
         self.assert_(math.isnan(math.hypot(nan, nan)))
+        self.assert_(math.isnan(math.hypot(4, nan)))
+        self.assert_(math.isnan(math.hypot(nan, 4)))
         self.assertEqual(inf, math.hypot(inf, 4))
         self.assertEqual(inf, math.hypot(4, inf))
         self.assertEqual(inf, math.hypot(ninf, 4))
@@ -37,9 +40,35 @@
         self.assertRaises(ValueError, math.log, -1.5)
         self.assertRaises(ValueError, math.log, -0.5)
 
+from test.test_math import MathTests
+
+class MathAccuracy(MathTests):
+    # Run the CPython tests but expect accurate results
+
+    def ftest(self, name, value, expected):
+        if expected != 0. :
+            # Tolerate small deviation in proportion to expected
+            tol = Math.ulp(expected)
+        else :
+            # On zero, allow 2**-52. Maybe allow different slack based on name
+            tol = Math.ulp(1.)
+
+        if abs(value-expected) > tol:
+            # Use %r to display full precision.
+            message = '%s returned %r, expected %r' % (name, value, expected)
+            self.fail(message)
+
+    def testConstants(self):
+        self.ftest('pi', math.pi, Math.PI) # 3.141592653589793238462643
+        self.ftest('e', math.e, Math.E)   # 2.718281828459045235360287
+
+
 
 def test_main():
-    test_support.run_unittest(MathTestCase)
+    test_support.run_unittest(
+            MathTestCase,
+            MathAccuracy,
+        )
 
 
 if __name__ == '__main__':
diff --git a/src/org/python/modules/math.java b/src/org/python/modules/math.java
--- a/src/org/python/modules/math.java
+++ b/src/org/python/modules/math.java
@@ -5,6 +5,7 @@
 
 import org.python.core.ClassDictInit;
 import org.python.core.Py;
+import org.python.core.PyException;
 import org.python.core.PyFloat;
 import org.python.core.PyInteger;
 import org.python.core.PyLong;
@@ -24,6 +25,8 @@
     private static final double MINUS_ONE = -1.0;
     private static final double TWO = 2.0;
     private static final double EIGHT = 8.0;
+    private static final double LN2 = 0.693147180559945309417232121458; // Ref OEIS A002162
+
     private static final double INF = Double.POSITIVE_INFINITY;
     private static final double NINF = Double.NEGATIVE_INFINITY;
     private static final double NAN = Double.NaN;
@@ -73,26 +76,35 @@
         return Math.acos(v);
     }
 
-    public static double acosh(double v) {
-        final double ln2 = 6.93147180559945286227e-01;
-        final double large = 1 << 28;
+    /**
+     * Compute <i>cosh<sup>-1</sup>y</i>.
+     *
+     * @param y
+     * @return x such that <i>cosh x = y</i>
+     */
+    public static double acosh(double y) {
+        if (y < 1.0) {
+            throw mathDomainValueError();
 
-        if (isninf(v)) {
-            throwMathDomainValueError();
+        } else {
+            // acosh(y) = ln[y + sqrt(y**2 - 1)]
+            if (y < 2.) {
+                // Rearrange as acosh(1+u) = ln[1 + u + sqrt(u(2+u))]
+                final double u = y - 1.;
+                double s = Math.sqrt(u * (2. + u));
+                return Math.log1p(u + s);
+
+            } else if (y < 0x1p27) {
+                // Rearrange as acosh(y) = ln[ y ( 1 + sqrt[1-(1/y)**2] )]
+                final double u = 1. / y;
+                double t = Math.sqrt((1. + u) * (1. - u));
+                return Math.log(y * (1. + t));
+
+            } else {
+                // As above but t indistinguishable from 1.0 so ...
+                return Math.log(y) + LN2;
+            }
         }
-        if (v == ZERO || v == MINUS_ONE) {
-            throwMathDomainValueError();
-        }
-        if (isnan(v) || isinf(v) || (v < 1.0)) {
-            return v;
-        }
-        if (v == 1.0) {
-            return 0;
-        }
-        if (v >= large) {
-            return log(v) + ln2;
-        }
-        return log(v + sqrt(v * v - 1));
     }
 
     public static double asin(double v) {
@@ -141,14 +153,22 @@
         return Math.atan(v);
     }
 
-    public static double atanh(double v) {
-        if (isnan(v)) {
-            return v;
+    /**
+     * Compute <i>tanh<sup>-1</sup>y</i>.
+     *
+     * @param y
+     * @return x such that <i>tanh x = y</i>
+     */
+    public static double atanh(double y) {
+        double absy = Math.abs(y);
+        if (absy >= 1.0) {
+            throw mathDomainValueError();
+        } else {
+            // 2x = ln[(1+y)/(1-y)] = ln[1 + 2y/(1-y)]
+            double u = (absy + absy) / (1. - absy);
+            double x = 0.5 * Math.log1p(u);
+            return Math.copySign(x, y);
         }
-        if (isinf(v) || Math.abs(v) == ONE) {
-            throwMathDomainValueError();
-        }
-        return log((1 + v) / (1 - v)) / 2;
     }
 
     public static double atan2(double v, double w) {
@@ -584,6 +604,15 @@
         return signum;
     }
 
+    /**
+     * Returns a ValueError("math domain error"), ready to throw from the client code.
+     *
+     * @return ValueError("math domain error")
+     */
+    private static PyException mathDomainValueError() {
+        return Py.ValueError("math domain error");
+    }
+
     private static void throwMathDomainValueError() {
         throw Py.ValueError("math domain error");
     }

-- 
Repository URL: https://hg.python.org/jython


More information about the Jython-checkins mailing list