diff --git a/Include/internal/pycore_complexobject.h b/Include/internal/pycore_complexobject.h index f595f6ab7a674d..c850b8a6928371 100644 --- a/Include/internal/pycore_complexobject.h +++ b/Include/internal/pycore_complexobject.h @@ -27,6 +27,29 @@ PyAPI_FUNC(Py_complex) _Py_cr_prod(Py_complex, double); PyAPI_FUNC(Py_complex) _Py_cr_quot(Py_complex, double); PyAPI_FUNC(Py_complex) _Py_rc_quot(double, Py_complex); +static inline bool +_Py_c_isnan(Py_complex a) +{ + return isnan(a.real) || isnan(a.imag); +} + +static inline bool +_Py_c_isinf(Py_complex a) +{ + return isinf(a.real) || isinf(a.imag); +} + +static inline bool +_Py_c_isfinite(Py_complex a) +{ + return isfinite(a.real) && isfinite(a.imag); +} + +static inline bool +_Py_c_iszero(Py_complex a) +{ + return a.real == 0.0 && a.imag == 0.0; +} #ifdef __cplusplus } diff --git a/Lib/test/test_capi/test_complex.py b/Lib/test/test_capi/test_complex.py index c3189a67cc7e2d..cf032a4282138d 100644 --- a/Lib/test/test_capi/test_complex.py +++ b/Lib/test/test_capi/test_complex.py @@ -263,6 +263,7 @@ def test_py_c_pow(self): self.assertEqual(_py_c_pow(1, 1j), (1+0j, 0)) self.assertEqual(_py_c_pow(0j, 1), (0j, 0)) self.assertAlmostEqual(_py_c_pow(1j, 2)[0], -1.0+0j) + self.assertEqual(_py_c_pow(complex('nan+0j'), 0j)[0], 1+0j) r, e = _py_c_pow(1+1j, -1) self.assertAlmostEqual(r, 0.5-0.5j) diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index bb307191dffcc1..3520fb1b63d28b 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -351,8 +351,10 @@ def test_pow(self): self.assertEqual(pow(0+0j, 2000+0j), 0.0) self.assertEqual(pow(0, 0+0j), 1.0) self.assertEqual(pow(-1, 0+0j), 1.0) + self.assertEqual(pow(0j, complex(INF)), 0j) self.assertRaises(ZeroDivisionError, pow, 0+0j, 1j) self.assertRaises(ZeroDivisionError, pow, 0+0j, -1000) + self.assertRaises(ZeroDivisionError, pow, 0+0j, -1) self.assertAlmostEqual(pow(1j, -1), 1/1j) self.assertAlmostEqual(pow(1j, 200), 1) self.assertRaises(ValueError, pow, 1+1j, 1+1j, 1+1j) @@ -362,6 +364,14 @@ def test_pow(self): self.assertRaises(TypeError, pow, None, 1j) self.assertAlmostEqual(pow(1j, 0.5), 0.7071067811865476+0.7071067811865475j) + # No overflows for infinite base + r = pow(complex(INF), 2) # integer power + self.assertEqual(r.real, INF) + self.assertTrue(isnan(r.imag)) + r = pow(complex(INF), 2.25) # generic algorithm + self.assertEqual(r.real, INF) + self.assertTrue(isnan(r.imag)) + a = 3.33+4.43j self.assertEqual(a ** 0j, 1) self.assertEqual(a ** 0.+0.j, 1) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-08-31-07-33-05.gh-issue-156693.BuDcDc.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-31-07-33-05.gh-issue-156693.BuDcDc.rst new file mode 100644 index 00000000000000..9a77ddac655671 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-31-07-33-05.gh-issue-156693.BuDcDc.rst @@ -0,0 +1,2 @@ +Correct powers of infinite complex numbers to not raise +:exc:`OverflowError`'s. Patch by Sergey B Kirpichev. diff --git a/Modules/cmathmodule.c b/Modules/cmathmodule.c index f6e1475b00ecfb..2bba9eacf5622a 100644 --- a/Modules/cmathmodule.c +++ b/Modules/cmathmodule.c @@ -140,7 +140,7 @@ special_type(double d) } #define SPECIAL_VALUE(z, table) \ - if (!isfinite((z).real) || !isfinite((z).imag)) { \ + if (!_Py_c_isfinite(z)) { \ errno = 0; \ return table[special_type((z).real)] \ [special_type((z).imag)]; \ @@ -446,7 +446,7 @@ cmath_cosh_impl(PyObject *module, Py_complex z) double x_minus_one; /* special treatment for cosh(+/-inf + iy) if y is not a NaN */ - if (!isfinite(z.real) || !isfinite(z.imag)) { + if (!_Py_c_isfinite(z)) { if (isinf(z.real) && isfinite(z.imag) && (z.imag != 0.)) { if (z.real > 0) { @@ -482,7 +482,7 @@ cmath_cosh_impl(PyObject *module, Py_complex z) r.imag = sin(z.imag) * sinh(z.real); } /* detect overflow, and set errno accordingly */ - if (isinf(r.real) || isinf(r.imag)) + if (_Py_c_isinf(r)) errno = ERANGE; else errno = 0; @@ -515,7 +515,7 @@ cmath_exp_impl(PyObject *module, Py_complex z) Py_complex r; double l; - if (!isfinite(z.real) || !isfinite(z.imag)) { + if (!_Py_c_isfinite(z)) { if (isinf(z.real) && isfinite(z.imag) && (z.imag != 0.)) { if (z.real > 0) { @@ -552,7 +552,7 @@ cmath_exp_impl(PyObject *module, Py_complex z) r.imag = l*sin(z.imag); } /* detect overflow, and set errno accordingly */ - if (isinf(r.real) || isinf(r.imag)) + if (_Py_c_isinf(r)) errno = ERANGE; else errno = 0; @@ -708,7 +708,7 @@ cmath_sinh_impl(PyObject *module, Py_complex z) /* special treatment for sinh(+/-inf + iy) if y is finite and nonzero */ - if (!isfinite(z.real) || !isfinite(z.imag)) { + if (!_Py_c_isfinite(z)) { if (isinf(z.real) && isfinite(z.imag) && (z.imag != 0.)) { if (z.real > 0) { @@ -742,7 +742,7 @@ cmath_sinh_impl(PyObject *module, Py_complex z) r.imag = sin(z.imag) * cosh(z.real); } /* detect overflow, and set errno accordingly */ - if (isinf(r.real) || isinf(r.imag)) + if (_Py_c_isinf(r)) errno = ERANGE; else errno = 0; @@ -803,7 +803,7 @@ cmath_sqrt_impl(PyObject *module, Py_complex z) SPECIAL_VALUE(z, sqrt_special_values); - if (z.real == 0. && z.imag == 0.) { + if (_Py_c_iszero(z)) { r.real = 0.; r.imag = z.imag; return r; @@ -894,7 +894,7 @@ cmath_tanh_impl(PyObject *module, Py_complex z) /* special treatment for tanh(+/-inf + iy) if y is finite and nonzero */ - if (!isfinite(z.real) || !isfinite(z.imag)) { + if (!_Py_c_isfinite(z)) { if (isinf(z.real) && isfinite(z.imag) && (z.imag != 0.)) { if (z.real > 0) { @@ -1129,7 +1129,7 @@ static PyObject * cmath_isfinite_impl(PyObject *module, Py_complex z) /*[clinic end generated code: output=ac76611e2c774a36 input=e224f5c36d94f5da]*/ { - return PyBool_FromLong(isfinite(z.real) && isfinite(z.imag)); + return PyBool_FromLong(_Py_c_isfinite(z)); } /*[clinic input] @@ -1142,7 +1142,7 @@ static PyObject * cmath_isnan_impl(PyObject *module, Py_complex z) /*[clinic end generated code: output=e7abf6e0b28beab7 input=71799f5d284c9baf]*/ { - return PyBool_FromLong(isnan(z.real) || isnan(z.imag)); + return PyBool_FromLong(_Py_c_isnan(z)); } /*[clinic input] @@ -1155,7 +1155,7 @@ static PyObject * cmath_isinf_impl(PyObject *module, Py_complex z) /*[clinic end generated code: output=502a75a79c773469 input=363df155c7181329]*/ { - return PyBool_FromLong(isinf(z.real) || isinf(z.imag)); + return PyBool_FromLong(_Py_c_isinf(z)); } /*[clinic input] @@ -1211,7 +1211,7 @@ cmath_isclose_impl(PyObject *module, Py_complex a, Py_complex b, above. */ - if (isinf(a.real) || isinf(a.imag) || isinf(b.real) || isinf(b.imag)) { + if (_Py_c_isinf(a) || _Py_c_isinf(b)) { return 0; } diff --git a/Objects/complexobject.c b/Objects/complexobject.c index 3612c2699a557d..ab35f9c9e94ab8 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -98,7 +98,7 @@ _Py_c_prod(Py_complex z, Py_complex w) if (isnan(r.real) && isnan(r.imag)) { int recalc = 0; - if (isinf(a) || isinf(b)) { /* z is infinite */ + if (_Py_c_isinf(z)) { /* "Box" the infinity and change nans in the other factor to 0 */ a = copysign(isinf(a) ? 1.0 : 0.0, a); b = copysign(isinf(b) ? 1.0 : 0.0, b); @@ -110,7 +110,7 @@ _Py_c_prod(Py_complex z, Py_complex w) } recalc = 1; } - if (isinf(c) || isinf(d)) { /* w is infinite */ + if (_Py_c_isinf(w)) { /* "Box" the infinity and change nans in the other factor to 0 */ c = copysign(isinf(c) ? 1.0 : 0.0, c); d = copysign(isinf(d) ? 1.0 : 0.0, d); @@ -220,28 +220,22 @@ _Py_c_quot(Py_complex a, Py_complex b) /* At least one of b.real or b.imag is a NaN */ r.real = r.imag = Py_NAN; } - /* Recover infinities and zeros that computed as nan+nanj. See e.g. the C11, Annex G.5.2, routine _Cdivd(). */ if (isnan(r.real) && isnan(r.imag)) { - if ((isinf(a.real) || isinf(a.imag)) - && isfinite(b.real) && isfinite(b.imag)) - { + if (_Py_c_isinf(a) && _Py_c_isfinite(b)) { const double x = copysign(isinf(a.real) ? 1.0 : 0.0, a.real); const double y = copysign(isinf(a.imag) ? 1.0 : 0.0, a.imag); r.real = INFINITY * (x*b.real + y*b.imag); r.imag = INFINITY * (y*b.real - x*b.imag); } - else if ((isinf(abs_breal) || isinf(abs_bimag)) - && isfinite(a.real) && isfinite(a.imag)) - { + else if (_Py_c_isinf(b) && _Py_c_isfinite(a)) { const double x = copysign(isinf(b.real) ? 1.0 : 0.0, b.real); const double y = copysign(isinf(b.imag) ? 1.0 : 0.0, b.imag); r.real = 0.0 * (a.real*x + a.imag*y); r.imag = 0.0 * (a.imag*x - a.real*y); } } - return r; } @@ -291,9 +285,7 @@ _Py_rc_quot(double a, Py_complex b) r.real = r.imag = Py_NAN; } - if (isnan(r.real) && isnan(r.imag) && isfinite(a) - && (isinf(abs_breal) || isinf(abs_bimag))) - { + if (isnan(r.real) && isnan(r.imag) && isfinite(a) && _Py_c_isinf(b)) { const double x = copysign(isinf(b.real) ? 1.0 : 0.0, b.real); const double y = copysign(isinf(b.imag) ? 1.0 : 0.0, b.imag); r.real = 0.0 * (a*x); @@ -311,11 +303,11 @@ _Py_c_pow(Py_complex a, Py_complex b) { Py_complex r; double vabs,len,at,phase; - if (b.real == 0. && b.imag == 0.) { + if (_Py_c_iszero(b)) { r.real = 1.; r.imag = 0.; } - else if (a.real == 0. && a.imag == 0.) { + else if (_Py_c_iszero(a)) { if (b.imag != 0. || b.real < 0.) errno = EDOM; r.real = 0.; @@ -333,7 +325,9 @@ _Py_c_pow(Py_complex a, Py_complex b) r.real = len*cos(phase); r.imag = len*sin(phase); - _Py_ADJUST_ERANGE2(r.real, r.imag); + if (_Py_c_isinf(r) && _Py_c_isfinite(a) && _Py_c_isfinite(b)) { + _Py_ADJUST_ERANGE2(r.real, r.imag); + } } return r; } @@ -370,7 +364,7 @@ _Py_c_abs(Py_complex z) /* sets errno = ERANGE on overflow; otherwise errno = 0 */ double result; - if (!isfinite(z.real) || !isfinite(z.imag)) { + if (!_Py_c_isfinite(z)) { /* C99 rules: if either the real or the imaginary part is an infinity, return infinity, even if the other part is a NaN. */ @@ -753,12 +747,13 @@ complex_pow(PyObject *v, PyObject *w, PyObject *z) // a faster and more accurate algorithm. if (b.imag == 0.0 && b.real == floor(b.real) && fabs(b.real) <= 100.0) { p = c_powi(a, (long)b.real); - _Py_ADJUST_ERANGE2(p.real, p.imag); + if (_Py_c_isinf(p) && _Py_c_isfinite(a) && isfinite(b.real)) { + _Py_ADJUST_ERANGE2(p.real, p.imag); + } } else { p = _Py_c_pow(a, b); } - if (errno == EDOM) { PyErr_SetString(PyExc_ZeroDivisionError, "zero to a negative or complex power");