[Jython-checkins] jython: Rigorous tests for math, and passing sinh, cosh, tanh.

jeff.allen jython-checkins at python.org
Tue Jan 6 20:22:59 CET 2015


https://hg.python.org/jython/rev/7402bd620d52
changeset:   7506:7402bd620d52
user:        Jeff Allen <ja.py at farowl.co.uk>
date:        Sun Jan 04 15:42:03 2015 +0000
summary:
  Rigorous tests for math, and passing sinh, cosh, tanh.

It turns out that Java provides satisfactory implementations of
sinh, cosh, and tanh and we don't kneed our own. Defined
expectations for accuracy match those in the java.Math javadoc,
but in practice 1.7u60 passed at 1 ulp.

files:
  Lib/test/test_math_jy.py         |  103 +++++++++++++++++-
  Misc/make_cmath_testcases.py     |    4 +-
  src/org/python/modules/math.java |   30 +----
  3 files changed, 100 insertions(+), 37 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,12 +5,23 @@
 import math
 import unittest
 from test import test_support
+from test.test_math import (MathTests, ulps_check,
+                            parse_testfile, test_file)
+
 from java.lang import Math
 
 inf = float('inf')
 ninf = float('-inf')
 nan = float('nan')
 
+# Optional tests use mpmath
+try:
+    import mpmath
+    HAVE_MPMATH = True
+except:
+    HAVE_MPMATH = False
+
+
 class MathTestCase(unittest.TestCase):
 
     def test_frexp(self):
@@ -40,28 +51,104 @@
         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):
+    def ftest(self, name, value, expected, ulps_err=1):
+
         if expected != 0. :
             # Tolerate small deviation in proportion to expected
-            tol = Math.ulp(expected)
+            ulp_unit = Math.ulp(expected)
         else :
             # On zero, allow 2**-52. Maybe allow different slack based on name
-            tol = Math.ulp(1.)
+            ulp_unit = Math.ulp(1.)
 
-        if abs(value-expected) > tol:
+        # Complex expressions accumulate errors
+        if name in ('cosh(2)-2*cosh(1)**2', 'sinh(1)**2-cosh(1)**2') :
+            # ... quite steeply in these cases
+            ulps_err *= 5
+
+        err = value-expected
+
+        if abs(err) > ulps_err * ulp_unit:
             # Use %r to display full precision.
-            message = '%s returned %r, expected %r' % (name, value, expected)
+            message = '%s returned %r, expected %r (%r ulps)' % \
+                (name, value, expected, round(err/ulp_unit, 1))
             self.fail(message)
 
     def testConstants(self):
-        self.ftest('pi', math.pi, Math.PI) # 3.141592653589793238462643
-        self.ftest('e', math.e, Math.E)   # 2.718281828459045235360287
+        # Override MathTests.testConstants requiring equality with java.Math
+        self.assertEqual(math.pi, Math.PI)
+        self.assertEqual(math.e, Math.E)
 
+    def test_testfile(self, math_module=math, ulps_err=None):
+        # Rigorous variant of MathTests.test_testfile requiring accuracy in ulps.
+        fail_fmt = "{}:{}({!r}): expected {!r}, got {!r}"
+        failures = []
+
+        for id, fn, ar, ai, er, ei, flags in parse_testfile(test_file):
+            # Skip if either the input or result is complex, or if
+            # flags is nonempty
+            if ai != 0. or ei != 0. or flags:
+                continue
+            if fn in ['rect', 'polar']:
+                # no real versions of rect, polar
+                continue
+
+            if ulps_err is not None :
+                fn_ulps_err = ulps_err
+            else :
+                # java.Math mostly promises 1 ulp, except for:
+                if fn in ['atan2'] :
+                    fn_ulps_err = 2
+                elif fn in ['cosh', 'sinh', 'tanh'] :
+                    fn_ulps_err = 2.5
+                else :
+                    fn_ulps_err = 1
+
+            func = getattr(math_module, fn)
+            arg = ar
+            expected = er
+
+            if 'invalid' in flags or 'divide-by-zero' in flags:
+                expected = 'ValueError'
+            elif 'overflow' in flags:
+                expected = 'OverflowError'
+
+            try:
+                got = float(func(arg))
+            except ValueError:
+                got = 'ValueError'
+            except OverflowError:
+                got = 'OverflowError'
+
+            accuracy_failure = None
+            if isinstance(got, float) and isinstance(expected, float):
+                if math.isnan(expected) and math.isnan(got):
+                    continue
+                accuracy_failure = ulps_check(expected, got, fn_ulps_err)
+                if accuracy_failure is None:
+                    continue
+
+            if isinstance(got, str) and isinstance(expected, str):
+                if got == expected:
+                    continue
+
+            fail_msg = fail_fmt.format(id, fn, arg, expected, got)
+            if accuracy_failure is not None:
+                fail_msg += ' ({})'.format(accuracy_failure)
+            failures.append(fail_msg)
+
+        if failures:
+            self.fail('Failures in test_testfile:\n  ' +
+                      '\n  '.join(failures))
+
+    @unittest.skipUnless(HAVE_MPMATH, "requires mpmath module")
+    def test_testfile_mpmath(self):
+        # Run the mpmath module on the same material: consistency check during development.
+        with mpmath.workprec(100) :
+            self.test_testfile(mpmath, 1, 1)
 
 
 def test_main():
diff --git a/Misc/make_cmath_testcases.py b/Misc/make_cmath_testcases.py
--- a/Misc/make_cmath_testcases.py
+++ b/Misc/make_cmath_testcases.py
@@ -1,5 +1,5 @@
 # This work is based on test_math.py in the Python test set.
-# It a provides a tool to generate additional
+# It a provides a tool to generate additional real test cases.
 
 import math
 import mpmath
@@ -116,7 +116,7 @@
 def test_main():
     with mpmath.workprec(100):
         generate_cases()
-    
+
 if __name__ == '__main__':
     test_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
@@ -197,13 +197,7 @@
     }
 
     public static double cosh(double v) {
-        if (isinf(v)) {
-            return INF;
-        }
-        if (isnan(v)) {
-            return v;
-        }
-        return HALF * (Math.exp(v) + Math.exp(-v));
+        return Math.cosh(v);
     }
 
     public static double exp(double v) {
@@ -366,29 +360,11 @@
     }
 
     public static double sinh(double v) {
-        if (isnan(v)) {
-            return v;
-        }
-        if (isinf(v)) {
-            return v;
-        }
-        return HALF * (Math.exp(v) - Math.exp(-v));
+        return Math.sinh(v);
     }
 
     public static double tanh(double v) {
-        if (isnan(v)) {
-            return v;
-        }
-        if (isinf(v)) {
-            if (isninf(v)) {
-                return MINUS_ONE;
-            }
-            return ONE;
-        }
-        if (v == MINUS_ZERO) {
-            return v;
-        }
-        return sinh(v) / cosh(v);
+        return Math.tanh(v);
     }
 
     public static double fabs(double v) {

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


More information about the Jython-checkins mailing list