[Jython-checkins] jython: Re-work cmath.tan and cmath.tanh for accuracy and corner cases.

jeff.allen jython-checkins at python.org
Tue Jan 20 23:07:15 CET 2015


https://hg.python.org/jython/rev/4a24e60cb215
changeset:   7548:4a24e60cb215
user:        Jeff Allen <ja.py at farowl.co.uk>
date:        Sun Jan 11 18:35:01 2015 +0000
summary:
  Re-work cmath.tan and cmath.tanh for accuracy and corner cases.

files:
  src/org/python/modules/cmath.java |  124 ++++++++++++++---
  1 files changed, 98 insertions(+), 26 deletions(-)


diff --git a/src/org/python/modules/cmath.java b/src/org/python/modules/cmath.java
--- a/src/org/python/modules/cmath.java
+++ b/src/org/python/modules/cmath.java
@@ -694,36 +694,108 @@
         }
     }
 
-    public static PyComplex tan(PyObject in) {
-        PyComplex x = complexFromPyObject(in);
-
-        double sr = Math.sin(x.real);
-        double cr = Math.cos(x.real);
-        double shi = math.sinh(x.imag);
-        double chi = math.cosh(x.imag);
-        double rs = sr * chi;
-        double is = cr * shi;
-        double rc = cr * chi;
-        double ic = -sr * shi;
-        double d = rc * rc + ic * ic;
-
-        return new PyComplex(((rs * rc) + (is * ic)) / d, ((is * rc) - (rs * ic)) / d);
+    /**
+     * Return the tangent of z.
+     *
+     * @param z
+     * @return tan <i>z</i>
+     */
+    public static PyComplex tan(PyObject z) {
+        return tanOrTanh(complexFromPyObject(z), false);
     }
 
-    public static PyComplex tanh(PyObject in) {
-        PyComplex x = complexFromPyObject(in);
+    /**
+     * Return the hyperbolic tangent of z.
+     *
+     * @param z
+     * @return tanh <i>z</i>
+     */
+    public static PyComplex tanh(PyObject z) {
+        return tanOrTanh(complexFromPyObject(z), true);
+    }
 
-        double si = Math.sin(x.imag);
-        double ci = Math.cos(x.imag);
-        double shr = math.sinh(x.real);
-        double chr = math.cosh(x.real);
-        double rs = ci * shr;
-        double is = si * chr;
-        double rc = ci * chr;
-        double ic = si * shr;
-        double d = rc * rc + ic * ic;
+    /**
+     * Helper to compute either tan <i>z</i> or tanh <i>z</i>. The expression used is:
+     * <p>
+     * tanh(<i>x+iy</i>) = (sinh <i>x</i> cosh <i>x + i</i> sin <i>y</i> cos <i>y</i>) /
+     * (sinh<sup>2</sup><i>x +</i> cos<sup>2</sup><i>y</i>)
+     * <p>
+     * A simplification is made for x sufficiently large that <i>e<sup>2|x|</sup></i>&#x0226B;1 that
+     * deals satisfactorily with large or infinite <i>x</i>. When computing tan, we evaluate
+     * <i>i</i> tan <i>iz</i> instead, and the approximation applies to
+     * <i>e<sup>2|y|</sup></i>&#x0226B;1.
+     *
+     * @param z
+     * @param h <code>true</code> to compute tanh <i>z</i>, <code>false</code> to compute tan
+     *            <i>z</i>.
+     * @return tan or tanh <i>z</i>
+     */
+    private static PyComplex tanOrTanh(PyComplex z, boolean h) {
+        double x, y, u, v, s;
+        PyComplex w;
 
-        return new PyComplex(((rs * rc) + (is * ic)) / d, ((is * rc) - (rs * ic)) / d);
+        if (h) {
+            // We compute w = tanh(z). Let w = u + iv and z = x + iy.
+            x = z.real;
+            y = z.imag;
+            // Then the function body computes tanh(x+iy), according to:
+            // s = sinh**2 x + cos**2 y
+            // u = sinh x cosh x / s,
+            // v = sin y cos y / s,
+            // And we return w = u + iv.
+        } else {
+            // We compute w = tan(z). Unusually, let z = y - ix, so x + iy = iz.
+            y = z.real;
+            x = -z.imag;
+            // Then the function body computes tanh(x+iy) = tanh(iz) = i tan(z) as before,
+            // but we finally return w = v - iu = tan(z).
+        }
+
+        if (y == 0.) {
+            // Real argument for tanh (or imaginary for tan).
+            u = Math.tanh(x);
+            // v is zero but follows the sign of y (which could be -0.0).
+            v = y;
+
+        } else if (x == 0. && !Double.isNaN(y)) {
+            // Imaginary argument for tanh (or real for tan): imaginary result at this point.
+            v = Math.tan(y); // May raise domain error
+            // u is zero but follows sign of x (which could be -0.0).
+            u = x;
+
+        } else {
+            // The trig calls will not throw, although if y is infinite, they return nan.
+            double cosy = Math.cos(y), siny = Math.sin(y), absx = Math.abs(x);
+
+            if (absx > ATLEAST_27LN2) {
+                // e**2x is much greater than 1: exponential approximation to sinh and cosh.
+                s = 0.25 * Math.exp(2 * absx);
+                u = Math.copySign(1., x);
+                if (s == Double.POSITIVE_INFINITY) {
+                    // Either x is inf or 2x is large enough to overflow exp(). v=0, but signed:
+                    v = Math.copySign(0., siny * cosy);
+                } else {
+                    v = siny * cosy / s;
+                }
+
+            } else {
+                // Normal case: possible overflow in s near (x,y) = (0,pi/2) is harmless.
+                double sinhx = Math.sinh(x), coshx = Math.cosh(x);
+                s = sinhx * sinhx + cosy * cosy;
+                u = sinhx * coshx / s;
+                v = siny * cosy / s;
+            }
+        }
+
+        // Compose the result w according to whether we're computing tan(z) or tanh(z).
+        if (h) {
+            w = new PyComplex(u, v);    // w = u + iv = tanh(x+iy).
+        } else {
+            w = new PyComplex(v, -u);   // w = v - iu = tan(y-ix) = tan(z)
+        }
+
+        // If that generated a nan, and there wasn't one in the argument, raise a domain error.
+        return exceptNaN(w, z);
     }
 
     /**

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


More information about the Jython-checkins mailing list