Skip to content

Commit e089772

Browse files
committed
Re-work cmath.sin and cmath.sinh for accuracy and corner cases.
1 parent 030199f commit e089772

1 file changed

Lines changed: 110 additions & 8 deletions

File tree

src/org/python/modules/cmath.java

Lines changed: 110 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@ public class cmath {
2222
private static final double ROOT_HALF = 0.70710678118654752440;
2323
/** ln({@link Double#MAX_VALUE}) or a little less */
2424
private static final double NEARLY_LN_DBL_MAX = 709.4361393;
25+
/**
26+
* For x larger than this, <i>e<sup>-x</sup></i> is negligible compared with
27+
* <i>e<sup>x</sup></i>, or equivalently 1 is negligible compared with <i>e<sup>2x</sup></i>, in
28+
* IEEE-754 floating point. Beyond this, sinh <i>x</i> and cosh <i>x</i> are adequately
29+
* approximated by 0.5<i>e<sup>x</sup></i>. The smallest theoretical value is 27 ln(2).
30+
*/
31+
private static final double ATLEAST_27LN2 = 18.72;
32+
private static final double HALF_E2 = 0.5 * Math.E * Math.E;
2533

2634
/** log<sub>10</sub>e (Ref: Abramowitz &amp; Stegun [1972], p3). */
2735
private static final double LOG10E = 0.43429448190325182765;
@@ -381,16 +389,110 @@ private static double logHypot(double u, double v) {
381389
}
382390
}
383391

384-
public static PyComplex sin(PyObject in) {
385-
PyComplex x = complexFromPyObject(in);
386-
return new PyComplex(Math.sin(x.real) * math.cosh(x.imag), Math.cos(x.real)
387-
* math.sinh(x.imag));
392+
/**
393+
* Return the sine of z.
394+
* @param z
395+
* @return sin <i>z</i>
396+
*/
397+
public static PyComplex sin(PyObject z) {
398+
return sinOrSinh(complexFromPyObject(z), false);
388399
}
389400

390-
public static PyComplex sinh(PyObject in) {
391-
PyComplex x = complexFromPyObject(in);
392-
return new PyComplex(Math.cos(x.imag) * math.sinh(x.real), Math.sin(x.imag)
393-
* math.cosh(x.real));
401+
/**
402+
* Return the hyperbolic sine of z.
403+
* @param z
404+
* @return sinh <i>z</i>
405+
*/
406+
public static PyComplex sinh(PyObject z) {
407+
return sinOrSinh(complexFromPyObject(z), true);
408+
}
409+
410+
/**
411+
* Helper to compute either sin <i>z</i> or sinh <i>z</i>.
412+
*
413+
* @param z
414+
* @param h <code>true</code> to compute sinh <i>z</i>, <code>false</code> to compute sin
415+
* <i>z</i>.
416+
* @return
417+
*/
418+
private static PyComplex sinOrSinh(PyComplex z, boolean h) {
419+
double x, y, u, v;
420+
PyComplex w;
421+
422+
if (h) {
423+
// We compute w = sinh(z). Let w = u + iv and z = x + iy. We compute w = sinh(z) from:
424+
x = z.real;
425+
y = z.imag;
426+
// Then the function body computes sinh(x+iy), according to:
427+
// u = sinh(x) cos(y),
428+
// v = cosh(x) sin(y),
429+
// And we return w = u + iv.
430+
} else {
431+
// We compute w = sin(z). Unusually, let z = y - ix.
432+
y = z.real;
433+
x = -z.imag;
434+
// Then the function body computes sinh(x+iy) = sinh(iz) = i sin(z), according to:
435+
// u = sinh(x) cos(y),
436+
// v = cosh(x) sin(y).
437+
// But we finally return w = v - iu = sin(z)
438+
}
439+
440+
if (y == 0.) {
441+
// Real argument for sinh (or imaginary for sin): use real library.
442+
u = math.sinh(x); // This will raise a range error on overflow.
443+
// v follows the sign of y (which could be -0.0).
444+
v = y;
445+
446+
} else if (x == 0.) {
447+
// Imaginary argument for sinh (or real for sin): imaginary result at this point.
448+
v = Math.sin(y);
449+
// u follows sign of x (which could be -0.0).
450+
u = x;
451+
452+
} else {
453+
454+
// The trig calls will not throw, although if y is infinite, they return nan.
455+
double cosy = Math.cos(y), siny = Math.sin(y), absx = Math.abs(x);
456+
457+
if (absx == Double.POSITIVE_INFINITY) {
458+
if (!Double.isNaN(cosy)) {
459+
// w = (inf,inf), but "rotated" by the direction cosines.
460+
u = x * cosy;
461+
v = Math.abs(x) * siny;
462+
} else {
463+
// Provisionally w = (inf,nan), which will raise domain error if y!=nan.
464+
u = x;
465+
v = Double.NaN;
466+
}
467+
468+
} else if (absx > ATLEAST_27LN2) {
469+
// Use 0.5*e**x approximation. This is also the region where we risk overflow.
470+
double r = Math.exp(absx - 2.);
471+
// r approximates 2cosh(x)/e**2: multiply in this order to avoid inf:
472+
v = r * siny * HALF_E2;
473+
// r approximates 2sinh(|x|)/e**2: put back the proper sign of x in passing.
474+
u = Math.copySign(r, x) * cosy * HALF_E2;
475+
if (Double.isInfinite(u) || Double.isInfinite(v)) {
476+
// A finite x gave rise to an infinite u or v.
477+
throw math.mathRangeError();
478+
}
479+
480+
} else {
481+
// Normal case, without risk of overflow.
482+
u = Math.sinh(x) * cosy;
483+
v = Math.cosh(x) * siny;
484+
}
485+
}
486+
487+
// Compose the result w according to whether we're computing sin(z) or sinh(z).
488+
if (h) {
489+
w = new PyComplex(u, v); // w = u + iv = sinh(x+iy).
490+
} else {
491+
w = new PyComplex(v, -u); // w = v - iu = sin(y-ix) = sin(z)
492+
}
493+
494+
// If that generated a nan, and there wasn't one in the argument, raise a domain error.
495+
return exceptNaN(w, z);
394496
}
395497

396498
/**

0 commit comments

Comments
 (0)