Skip to content

Commit 42391fe

Browse files
committed
gh-156693: correct complex powers with infinite bases
Now we raise OverflowError's only if infinite result (i.e. cmath.isinf(result) is True) come from finite input. As proposed in issue #156145, we don't use platforms `errno` to manage exceptions anymore. This patch also adds private API functions `_Py_c_isnan()`, `_Py_c_isinf()` and `_Py_c_isfinite()`.
1 parent 03503dc commit 42391fe

5 files changed

Lines changed: 82 additions & 37 deletions

File tree

Include/internal/pycore_complexobject.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ PyAPI_FUNC(Py_complex) _Py_cr_prod(Py_complex, double);
2727
PyAPI_FUNC(Py_complex) _Py_cr_quot(Py_complex, double);
2828
PyAPI_FUNC(Py_complex) _Py_rc_quot(double, Py_complex);
2929

30+
PyAPI_FUNC(bool) _Py_c_isnan(Py_complex);
31+
PyAPI_FUNC(bool) _Py_c_isinf(Py_complex);
32+
PyAPI_FUNC(bool) _Py_c_isfinite(Py_complex);
3033

3134
#ifdef __cplusplus
3235
}

Lib/test/test_complex.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,14 @@ def test_pow(self):
362362
self.assertRaises(TypeError, pow, None, 1j)
363363
self.assertAlmostEqual(pow(1j, 0.5), 0.7071067811865476+0.7071067811865475j)
364364

365+
# No overflows for infinite base
366+
r = pow(complex(INF), 2) # integer power
367+
self.assertEqual(r.real, INF)
368+
self.assertTrue(isnan(r.imag))
369+
r = pow(complex(INF), 2.25) # generic algorithm
370+
self.assertEqual(r.real, INF)
371+
self.assertTrue(isnan(r.imag))
372+
365373
a = 3.33+4.43j
366374
self.assertEqual(a ** 0j, 1)
367375
self.assertEqual(a ** 0.+0.j, 1)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Correct complex powers of infinite numbers to not raise
2+
:exc:`OverflowError`'s. As proposed in :gh:`156145`, we don't use platforms
3+
:data:`errno` to manage exceptions. Patch by Sergey B Kirpichev.

Modules/cmathmodule.c

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ special_type(double d)
140140
}
141141

142142
#define SPECIAL_VALUE(z, table) \
143-
if (!isfinite((z).real) || !isfinite((z).imag)) { \
143+
if (!_Py_c_isfinite(z)) { \
144144
errno = 0; \
145145
return table[special_type((z).real)] \
146146
[special_type((z).imag)]; \
@@ -446,7 +446,7 @@ cmath_cosh_impl(PyObject *module, Py_complex z)
446446
double x_minus_one;
447447

448448
/* special treatment for cosh(+/-inf + iy) if y is not a NaN */
449-
if (!isfinite(z.real) || !isfinite(z.imag)) {
449+
if (!_Py_c_isfinite(z)) {
450450
if (isinf(z.real) && isfinite(z.imag) &&
451451
(z.imag != 0.)) {
452452
if (z.real > 0) {
@@ -482,7 +482,7 @@ cmath_cosh_impl(PyObject *module, Py_complex z)
482482
r.imag = sin(z.imag) * sinh(z.real);
483483
}
484484
/* detect overflow, and set errno accordingly */
485-
if (isinf(r.real) || isinf(r.imag))
485+
if (_Py_c_isinf(r))
486486
errno = ERANGE;
487487
else
488488
errno = 0;
@@ -515,7 +515,7 @@ cmath_exp_impl(PyObject *module, Py_complex z)
515515
Py_complex r;
516516
double l;
517517

518-
if (!isfinite(z.real) || !isfinite(z.imag)) {
518+
if (!_Py_c_isfinite(z)) {
519519
if (isinf(z.real) && isfinite(z.imag)
520520
&& (z.imag != 0.)) {
521521
if (z.real > 0) {
@@ -552,7 +552,7 @@ cmath_exp_impl(PyObject *module, Py_complex z)
552552
r.imag = l*sin(z.imag);
553553
}
554554
/* detect overflow, and set errno accordingly */
555-
if (isinf(r.real) || isinf(r.imag))
555+
if (_Py_c_isinf(r))
556556
errno = ERANGE;
557557
else
558558
errno = 0;
@@ -708,7 +708,7 @@ cmath_sinh_impl(PyObject *module, Py_complex z)
708708

709709
/* special treatment for sinh(+/-inf + iy) if y is finite and
710710
nonzero */
711-
if (!isfinite(z.real) || !isfinite(z.imag)) {
711+
if (!_Py_c_isfinite(z)) {
712712
if (isinf(z.real) && isfinite(z.imag)
713713
&& (z.imag != 0.)) {
714714
if (z.real > 0) {
@@ -742,7 +742,7 @@ cmath_sinh_impl(PyObject *module, Py_complex z)
742742
r.imag = sin(z.imag) * cosh(z.real);
743743
}
744744
/* detect overflow, and set errno accordingly */
745-
if (isinf(r.real) || isinf(r.imag))
745+
if (_Py_c_isinf(r))
746746
errno = ERANGE;
747747
else
748748
errno = 0;
@@ -894,7 +894,7 @@ cmath_tanh_impl(PyObject *module, Py_complex z)
894894

895895
/* special treatment for tanh(+/-inf + iy) if y is finite and
896896
nonzero */
897-
if (!isfinite(z.real) || !isfinite(z.imag)) {
897+
if (!_Py_c_isfinite(z)) {
898898
if (isinf(z.real) && isfinite(z.imag)
899899
&& (z.imag != 0.)) {
900900
if (z.real > 0) {
@@ -1129,7 +1129,7 @@ static PyObject *
11291129
cmath_isfinite_impl(PyObject *module, Py_complex z)
11301130
/*[clinic end generated code: output=ac76611e2c774a36 input=e224f5c36d94f5da]*/
11311131
{
1132-
return PyBool_FromLong(isfinite(z.real) && isfinite(z.imag));
1132+
return PyBool_FromLong(_Py_c_isfinite(z));
11331133
}
11341134

11351135
/*[clinic input]
@@ -1142,7 +1142,7 @@ static PyObject *
11421142
cmath_isnan_impl(PyObject *module, Py_complex z)
11431143
/*[clinic end generated code: output=e7abf6e0b28beab7 input=71799f5d284c9baf]*/
11441144
{
1145-
return PyBool_FromLong(isnan(z.real) || isnan(z.imag));
1145+
return PyBool_FromLong(_Py_c_isnan(z));
11461146
}
11471147

11481148
/*[clinic input]
@@ -1155,7 +1155,7 @@ static PyObject *
11551155
cmath_isinf_impl(PyObject *module, Py_complex z)
11561156
/*[clinic end generated code: output=502a75a79c773469 input=363df155c7181329]*/
11571157
{
1158-
return PyBool_FromLong(isinf(z.real) || isinf(z.imag));
1158+
return PyBool_FromLong(_Py_c_isinf(z));
11591159
}
11601160

11611161
/*[clinic input]
@@ -1211,7 +1211,7 @@ cmath_isclose_impl(PyObject *module, Py_complex a, Py_complex b,
12111211
above.
12121212
*/
12131213

1214-
if (isinf(a.real) || isinf(a.imag) || isinf(b.real) || isinf(b.imag)) {
1214+
if (_Py_c_isinf(a) || _Py_c_isinf(b)) {
12151215
return 0;
12161216
}
12171217

Objects/complexobject.c

Lines changed: 56 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,24 @@ class complex "PyComplexObject *" "&PyComplex_Type"
2828

2929
static Py_complex c_1 = {1., 0.};
3030

31+
bool
32+
_Py_c_isnan(Py_complex a)
33+
{
34+
return isnan(a.real) || isnan(a.imag);
35+
}
36+
37+
bool
38+
_Py_c_isinf(Py_complex a)
39+
{
40+
return isinf(a.real) || isinf(a.imag);
41+
}
42+
43+
bool
44+
_Py_c_isfinite(Py_complex a)
45+
{
46+
return isfinite(a.real) && isfinite(a.imag);
47+
}
48+
3149
Py_complex
3250
_Py_c_sum(Py_complex a, Py_complex b)
3351
{
@@ -98,7 +116,7 @@ _Py_c_prod(Py_complex z, Py_complex w)
98116
if (isnan(r.real) && isnan(r.imag)) {
99117
int recalc = 0;
100118

101-
if (isinf(a) || isinf(b)) { /* z is infinite */
119+
if (_Py_c_isinf(z)) {
102120
/* "Box" the infinity and change nans in the other factor to 0 */
103121
a = copysign(isinf(a) ? 1.0 : 0.0, a);
104122
b = copysign(isinf(b) ? 1.0 : 0.0, b);
@@ -110,7 +128,7 @@ _Py_c_prod(Py_complex z, Py_complex w)
110128
}
111129
recalc = 1;
112130
}
113-
if (isinf(c) || isinf(d)) { /* w is infinite */
131+
if (_Py_c_isinf(w)) {
114132
/* "Box" the infinity and change nans in the other factor to 0 */
115133
c = copysign(isinf(c) ? 1.0 : 0.0, c);
116134
d = copysign(isinf(d) ? 1.0 : 0.0, d);
@@ -224,17 +242,13 @@ _Py_c_quot(Py_complex a, Py_complex b)
224242
/* Recover infinities and zeros that computed as nan+nanj. See e.g.
225243
the C11, Annex G.5.2, routine _Cdivd(). */
226244
if (isnan(r.real) && isnan(r.imag)) {
227-
if ((isinf(a.real) || isinf(a.imag))
228-
&& isfinite(b.real) && isfinite(b.imag))
229-
{
245+
if (_Py_c_isinf(a) && _Py_c_isfinite(b)) {
230246
const double x = copysign(isinf(a.real) ? 1.0 : 0.0, a.real);
231247
const double y = copysign(isinf(a.imag) ? 1.0 : 0.0, a.imag);
232248
r.real = INFINITY * (x*b.real + y*b.imag);
233249
r.imag = INFINITY * (y*b.real - x*b.imag);
234250
}
235-
else if ((isinf(abs_breal) || isinf(abs_bimag))
236-
&& isfinite(a.real) && isfinite(a.imag))
237-
{
251+
else if (_Py_c_isinf(b) && _Py_c_isfinite(a)) {
238252
const double x = copysign(isinf(b.real) ? 1.0 : 0.0, b.real);
239253
const double y = copysign(isinf(b.imag) ? 1.0 : 0.0, b.imag);
240254
r.real = 0.0 * (a.real*x + a.imag*y);
@@ -291,9 +305,7 @@ _Py_rc_quot(double a, Py_complex b)
291305
r.real = r.imag = Py_NAN;
292306
}
293307

294-
if (isnan(r.real) && isnan(r.imag) && isfinite(a)
295-
&& (isinf(abs_breal) || isinf(abs_bimag)))
296-
{
308+
if (isnan(r.real) && isnan(r.imag) && isfinite(a) && _Py_c_isinf(b)) {
297309
const double x = copysign(isinf(b.real) ? 1.0 : 0.0, b.real);
298310
const double y = copysign(isinf(b.imag) ? 1.0 : 0.0, b.imag);
299311
r.real = 0.0 * (a*x);
@@ -306,20 +318,25 @@ _Py_rc_quot(double a, Py_complex b)
306318
#pragma optimize("", on)
307319
#endif
308320

309-
Py_complex
310-
_Py_c_pow(Py_complex a, Py_complex b)
321+
static Py_complex
322+
c_pow(Py_complex a, Py_complex b)
311323
{
312324
Py_complex r;
313325
double vabs,len,at,phase;
326+
314327
if (b.real == 0. && b.imag == 0.) {
315328
r.real = 1.;
316329
r.imag = 0.;
317330
}
318331
else if (a.real == 0. && a.imag == 0.) {
319-
if (b.imag != 0. || b.real < 0.)
320-
errno = EDOM;
321-
r.real = 0.;
322-
r.imag = 0.;
332+
if (b.imag != 0. || b.real < 0.) {
333+
r.real = NAN;
334+
r.imag = NAN;
335+
}
336+
else {
337+
r.real = 0.;
338+
r.imag = 0.;
339+
}
323340
}
324341
else {
325342
vabs = hypot(a.real,a.imag);
@@ -332,8 +349,25 @@ _Py_c_pow(Py_complex a, Py_complex b)
332349
}
333350
r.real = len*cos(phase);
334351
r.imag = len*sin(phase);
352+
}
353+
return r;
354+
}
335355

336-
_Py_ADJUST_ERANGE2(r.real, r.imag);
356+
Py_complex
357+
_Py_c_pow(Py_complex a, Py_complex b)
358+
{
359+
int saved_errno = errno;
360+
Py_complex r = c_pow(a, b);
361+
362+
if (_Py_c_isnan(r) && a.real == 0 && a.imag == 0) {
363+
errno = EDOM;
364+
r.real = r.imag = 0.0; /* and set r as documented */
365+
}
366+
else if (_Py_c_isinf(r) && _Py_c_isfinite(a) && _Py_c_isfinite(b)) {
367+
errno = ERANGE;
368+
}
369+
else {
370+
errno = saved_errno;
337371
}
338372
return r;
339373
}
@@ -370,7 +404,7 @@ _Py_c_abs(Py_complex z)
370404
/* sets errno = ERANGE on overflow; otherwise errno = 0 */
371405
double result;
372406

373-
if (!isfinite(z.real) || !isfinite(z.imag)) {
407+
if (!_Py_c_isfinite(z)) {
374408
/* C99 rules: if either the real or the imaginary part is an
375409
infinity, return infinity, even if the other part is a
376410
NaN. */
@@ -748,23 +782,20 @@ complex_pow(PyObject *v, PyObject *w, PyObject *z)
748782
PyErr_SetString(PyExc_ValueError, "complex modulo");
749783
return NULL;
750784
}
751-
errno = 0;
752785
// Check whether the exponent has a small integer value, and if so use
753786
// a faster and more accurate algorithm.
754787
if (b.imag == 0.0 && b.real == floor(b.real) && fabs(b.real) <= 100.0) {
755788
p = c_powi(a, (long)b.real);
756-
_Py_ADJUST_ERANGE2(p.real, p.imag);
757789
}
758790
else {
759-
p = _Py_c_pow(a, b);
791+
p = c_pow(a, b);
760792
}
761-
762-
if (errno == EDOM) {
793+
if (_Py_c_isnan(p) && a.real == 0 && a.imag == 0) {
763794
PyErr_SetString(PyExc_ZeroDivisionError,
764795
"zero to a negative or complex power");
765796
return NULL;
766797
}
767-
else if (errno == ERANGE) {
798+
else if (_Py_c_isinf(p) && _Py_c_isfinite(a) && _Py_c_isfinite(b)) {
768799
PyErr_SetString(PyExc_OverflowError,
769800
"complex exponentiation");
770801
return NULL;

0 commit comments

Comments
 (0)