Skip to content

Commit 3459e93

Browse files
committed
cmath.exp conforms to test_cmath
... and in the remotest corners of the complex plane.
1 parent 26b209d commit 3459e93

File tree

2 files changed

+107
-7
lines changed

2 files changed

+107
-7
lines changed

src/org/python/modules/cmath.java

Lines changed: 104 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import org.python.core.Py;
44
import org.python.core.PyComplex;
5+
import org.python.core.PyException;
56
import org.python.core.PyFloat;
67
import org.python.core.PyInstance;
78
import org.python.core.PyObject;
@@ -19,6 +20,8 @@ public class cmath {
1920

2021
/** 2<sup>-&#189;</sup> (Ref: Abramowitz &amp; Stegun [1972], p2). */
2122
private static final double ROOT_HALF = 0.70710678118654752440;
23+
/** ln({@link Double#MAX_VALUE}) or a little less */
24+
private static final double NEARLY_LN_DBL_MAX = 709.4361393;
2225

2326
private static PyComplex c_prodi(PyComplex x) {
2427
return (PyComplex)x.__mul__(i);
@@ -140,10 +143,65 @@ public static PyComplex cosh(PyObject in) {
140143
* math.sinh(x.real));
141144
}
142145

143-
public static PyComplex exp(PyObject in) {
144-
PyComplex x = complexFromPyObject(in);
145-
double l = Math.exp(x.real);
146-
return new PyComplex(l * Math.cos(x.imag), l * Math.sin(x.imag));
146+
/**
147+
* Return the exponential value e<sup>z</sup>.
148+
*
149+
* @param z
150+
* @return e<sup>z</sup>
151+
*/
152+
public static PyComplex exp(PyObject z) {
153+
PyComplex zz = complexFromPyObject(z);
154+
double x = zz.real, y = zz.imag, r, u, v;
155+
/*
156+
* This has a lot of corner-cases, and some of them make little sense sense, but it matches
157+
* CPython and passes the regression tests.
158+
*/
159+
if (y == 0.) {
160+
// Real value: use a real solution. (This may raise a range error.)
161+
u = math.exp(x);
162+
// v follows sign of y.
163+
v = y;
164+
165+
} else {
166+
// The trig calls will not throw, although if y is infinite, they return nan.
167+
double cosy = Math.cos(y), siny = Math.sin(y);
168+
169+
if (x == Double.NEGATIVE_INFINITY) {
170+
// w = (0,0) but "signed" by the direction cosines (even in they are nan).
171+
u = Math.copySign(0., cosy);
172+
v = Math.copySign(0., siny);
173+
174+
} else if (x == Double.POSITIVE_INFINITY) {
175+
if (!Double.isNaN(cosy)) {
176+
// w = (inf,inf), but "signed" by the direction cosines.
177+
u = Math.copySign(x, cosy);
178+
v = Math.copySign(x, siny);
179+
} else {
180+
// Provisionally w = (inf,nan), which will raise domain error if y!=nan.
181+
u = x;
182+
v = Double.NaN;
183+
}
184+
185+
} else if (x > NEARLY_LN_DBL_MAX) {
186+
// r = e**x would overflow but maybe not r*cos(y) and r*sin(y).
187+
r = Math.exp(x - 1); // = r / e
188+
u = r * cosy * Math.E;
189+
v = r * siny * Math.E;
190+
if (Double.isInfinite(u) || Double.isInfinite(v)) {
191+
// A finite x gave rise to an infinite u or v.
192+
throw math.mathRangeError();
193+
}
194+
195+
} else {
196+
// Normal case, without risk of overflow.
197+
// Compute r = exp(x), and return w = u + iv = r (cos(y) + i*sin(y))
198+
r = Math.exp(x);
199+
u = r * cosy;
200+
v = r * siny;
201+
}
202+
}
203+
// If that generated a nan, and there wasn't one in the argument, raise domain error.
204+
return exceptNaN(new PyComplex(u, v), zz);
147205
}
148206

149207
public static PyComplex log(PyObject in) {
@@ -396,4 +454,46 @@ public static PyComplex tanh(PyObject in) {
396454

397455
return new PyComplex(((rs * rc) + (is * ic)) / d, ((is * rc) - (rs * ic)) / d);
398456
}
457+
458+
/**
459+
* Turn a <code>NaN</code> result into a thrown <code>ValueError</code>, a math domain error, if
460+
* the original argument was not itself <code>NaN</code>. A <code>PyComplex</code> is a
461+
* <code>NaN</code> if either component is a <code>NaN</code>.
462+
*
463+
* @param result to return (if we return)
464+
* @param arg to include in check
465+
* @return result if <code>arg</code> was <code>NaN</code> or <code>result</code> was not
466+
* <code>NaN</code>
467+
* @throws PyException (ValueError) if <code>result</code> was <code>NaN</code> and
468+
* <code>arg</code> was not <code>NaN</code>
469+
*/
470+
private static PyComplex exceptNaN(PyComplex result, PyComplex arg) throws PyException {
471+
if ((Double.isNaN(result.real) || Double.isNaN(result.imag))
472+
&& !(Double.isNaN(arg.real) || Double.isNaN(arg.imag))) {
473+
throw math.mathDomainError();
474+
} else {
475+
return result;
476+
}
477+
}
478+
479+
/**
480+
* Turn an infinite result into a thrown <code>OverflowError</code>, a math range error, if the
481+
* original argument was not itself infinite. A <code>PyComplex</code> is infinite if either
482+
* component is infinite.
483+
*
484+
* @param result to return (if we return)
485+
* @param arg to include in check
486+
* @return result if <code>arg</code> was infinite or <code>result</code> was not infinite
487+
* @throws PyException (ValueError) if <code>result</code> was infinite and <code>arg</code> was
488+
* not infinite
489+
*/
490+
private static PyComplex exceptInf(PyComplex result, PyComplex arg) {
491+
if ((Double.isInfinite(result.real) || Double.isInfinite(result.imag))
492+
&& !(Double.isInfinite(arg.real) || Double.isInfinite(arg.imag))) {
493+
throw math.mathRangeError();
494+
} else {
495+
return result;
496+
}
497+
}
498+
399499
}

src/org/python/modules/math.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,7 @@ private static boolean ispinf(double v) {
515515
*
516516
* @return ValueError("math domain error")
517517
*/
518-
private static PyException mathDomainError() {
518+
static PyException mathDomainError() {
519519
return Py.ValueError("math domain error");
520520
}
521521

@@ -524,7 +524,7 @@ private static PyException mathDomainError() {
524524
*
525525
* @return OverflowError("math range error")
526526
*/
527-
private static PyException mathRangeError() {
527+
static PyException mathRangeError() {
528528
return Py.OverflowError("math range error");
529529
}
530530

@@ -575,7 +575,7 @@ private static double exceptNaN(double result, double arg) throws PyException {
575575
*/
576576
private static double exceptInf(double result, double arg) {
577577
if (Double.isInfinite(result) && !Double.isInfinite(arg)) {
578-
throw Py.OverflowError("math range error");
578+
throw mathRangeError();
579579
} else {
580580
return result;
581581
}

0 commit comments

Comments
 (0)