[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