Skip to content

Commit 22bfcfe

Browse files
committed
Re-work cmath.tan and cmath.tanh for accuracy and corner cases.
1 parent 00ae498 commit 22bfcfe

File tree

1 file changed

+98
-26
lines changed

1 file changed

+98
-26
lines changed

src/org/python/modules/cmath.java

Lines changed: 98 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -694,36 +694,108 @@ public static PyComplex sqrt(PyObject w) {
694694
}
695695
}
696696

697-
public static PyComplex tan(PyObject in) {
698-
PyComplex x = complexFromPyObject(in);
699-
700-
double sr = Math.sin(x.real);
701-
double cr = Math.cos(x.real);
702-
double shi = math.sinh(x.imag);
703-
double chi = math.cosh(x.imag);
704-
double rs = sr * chi;
705-
double is = cr * shi;
706-
double rc = cr * chi;
707-
double ic = -sr * shi;
708-
double d = rc * rc + ic * ic;
697+
/**
698+
* Return the tangent of z.
699+
*
700+
* @param z
701+
* @return tan <i>z</i>
702+
*/
703+
public static PyComplex tan(PyObject z) {
704+
return tanOrTanh(complexFromPyObject(z), false);
705+
}
709706

710-
return new PyComplex(((rs * rc) + (is * ic)) / d, ((is * rc) - (rs * ic)) / d);
707+
/**
708+
* Return the hyperbolic tangent of z.
709+
*
710+
* @param z
711+
* @return tanh <i>z</i>
712+
*/
713+
public static PyComplex tanh(PyObject z) {
714+
return tanOrTanh(complexFromPyObject(z), true);
711715
}
712716

713-
public static PyComplex tanh(PyObject in) {
714-
PyComplex x = complexFromPyObject(in);
717+
/**
718+
* Helper to compute either tan <i>z</i> or tanh <i>z</i>. The expression used is:
719+
* <p>
720+
* tanh(<i>x+iy</i>) = (sinh <i>x</i> cosh <i>x + i</i> sin <i>y</i> cos <i>y</i>) /
721+
* (sinh<sup>2</sup><i>x +</i> cos<sup>2</sup><i>y</i>)
722+
* <p>
723+
* A simplification is made for x sufficiently large that <i>e<sup>2|x|</sup></i>&#x0226B;1 that
724+
* deals satisfactorily with large or infinite <i>x</i>. When computing tan, we evaluate
725+
* <i>i</i> tan <i>iz</i> instead, and the approximation applies to
726+
* <i>e<sup>2|y|</sup></i>&#x0226B;1.
727+
*
728+
* @param z
729+
* @param h <code>true</code> to compute tanh <i>z</i>, <code>false</code> to compute tan
730+
* <i>z</i>.
731+
* @return tan or tanh <i>z</i>
732+
*/
733+
private static PyComplex tanOrTanh(PyComplex z, boolean h) {
734+
double x, y, u, v, s;
735+
PyComplex w;
715736

716-
double si = Math.sin(x.imag);
717-
double ci = Math.cos(x.imag);
718-
double shr = math.sinh(x.real);
719-
double chr = math.cosh(x.real);
720-
double rs = ci * shr;
721-
double is = si * chr;
722-
double rc = ci * chr;
723-
double ic = si * shr;
724-
double d = rc * rc + ic * ic;
725-
726-
return new PyComplex(((rs * rc) + (is * ic)) / d, ((is * rc) - (rs * ic)) / d);
737+
if (h) {
738+
// We compute w = tanh(z). Let w = u + iv and z = x + iy.
739+
x = z.real;
740+
y = z.imag;
741+
// Then the function body computes tanh(x+iy), according to:
742+
// s = sinh**2 x + cos**2 y
743+
// u = sinh x cosh x / s,
744+
// v = sin y cos y / s,
745+
// And we return w = u + iv.
746+
} else {
747+
// We compute w = tan(z). Unusually, let z = y - ix, so x + iy = iz.
748+
y = z.real;
749+
x = -z.imag;
750+
// Then the function body computes tanh(x+iy) = tanh(iz) = i tan(z) as before,
751+
// but we finally return w = v - iu = tan(z).
752+
}
753+
754+
if (y == 0.) {
755+
// Real argument for tanh (or imaginary for tan).
756+
u = Math.tanh(x);
757+
// v is zero but follows the sign of y (which could be -0.0).
758+
v = y;
759+
760+
} else if (x == 0. && !Double.isNaN(y)) {
761+
// Imaginary argument for tanh (or real for tan): imaginary result at this point.
762+
v = Math.tan(y); // May raise domain error
763+
// u is zero but follows sign of x (which could be -0.0).
764+
u = x;
765+
766+
} else {
767+
// The trig calls will not throw, although if y is infinite, they return nan.
768+
double cosy = Math.cos(y), siny = Math.sin(y), absx = Math.abs(x);
769+
770+
if (absx > ATLEAST_27LN2) {
771+
// e**2x is much greater than 1: exponential approximation to sinh and cosh.
772+
s = 0.25 * Math.exp(2 * absx);
773+
u = Math.copySign(1., x);
774+
if (s == Double.POSITIVE_INFINITY) {
775+
// Either x is inf or 2x is large enough to overflow exp(). v=0, but signed:
776+
v = Math.copySign(0., siny * cosy);
777+
} else {
778+
v = siny * cosy / s;
779+
}
780+
781+
} else {
782+
// Normal case: possible overflow in s near (x,y) = (0,pi/2) is harmless.
783+
double sinhx = Math.sinh(x), coshx = Math.cosh(x);
784+
s = sinhx * sinhx + cosy * cosy;
785+
u = sinhx * coshx / s;
786+
v = siny * cosy / s;
787+
}
788+
}
789+
790+
// Compose the result w according to whether we're computing tan(z) or tanh(z).
791+
if (h) {
792+
w = new PyComplex(u, v); // w = u + iv = tanh(x+iy).
793+
} else {
794+
w = new PyComplex(v, -u); // w = v - iu = tan(y-ix) = tan(z)
795+
}
796+
797+
// If that generated a nan, and there wasn't one in the argument, raise a domain error.
798+
return exceptNaN(w, z);
727799
}
728800

729801
/**

0 commit comments

Comments
 (0)