diff --git a/Lib/test/test_capi/test_complex.py b/Lib/test/test_capi/test_complex.py index c3189a67cc7e2d3..7d64237acd38864 100644 --- a/Lib/test/test_capi/test_complex.py +++ b/Lib/test/test_capi/test_complex.py @@ -281,18 +281,33 @@ def test_py_c_abs(self): # Test _Py_c_abs() _py_c_abs = _testcapi._py_c_abs - self.assertEqual(_py_c_abs(-1), (1.0, 0)) - self.assertEqual(_py_c_abs(1j), (1.0, 0)) - - self.assertEqual(_py_c_abs(complex('+inf+1j')), (INF, 0)) - self.assertEqual(_py_c_abs(complex('-inf+1j')), (INF, 0)) - self.assertEqual(_py_c_abs(complex('1.25+infj')), (INF, 0)) - self.assertEqual(_py_c_abs(complex('1.25-infj')), (INF, 0)) - - self.assertTrue(isnan(_py_c_abs(complex('1.25+nanj'))[0])) - self.assertTrue(isnan(_py_c_abs(complex('nan-1j'))[0])) - - self.assertEqual(_py_c_abs(complex(*[DBL_MAX]*2))[1], errno.ERANGE) + try: + _testcapi.set_errno(0) + self.assertEqual(_py_c_abs(-1), (1.0, 0)) + _testcapi.set_errno(0) + self.assertEqual(_py_c_abs(1j), (1.0, 0)) + + _testcapi.set_errno(0) + self.assertEqual(_py_c_abs(complex('+inf+1j')), (INF, 0)) + _testcapi.set_errno(0) + self.assertEqual(_py_c_abs(complex('-inf+1j')), (INF, 0)) + _testcapi.set_errno(0) + self.assertEqual(_py_c_abs(complex('1.25+infj')), (INF, 0)) + _testcapi.set_errno(0) + self.assertEqual(_py_c_abs(complex('1.25-infj')), (INF, 0)) + + _testcapi.set_errno(0) + self.assertTrue(isnan(_py_c_abs(complex('1.25+nanj'))[0])) + _testcapi.set_errno(0) + self.assertTrue(isnan(_py_c_abs(complex('nan-1j'))[0])) + + _testcapi.set_errno(0) + self.assertEqual(_py_c_abs(complex(*[DBL_MAX]*2))[1], errno.ERANGE) + + _testcapi.set_errno(errno.EACCES) # preserve errno + self.assertEqual(_py_c_abs(1j), (1, errno.EACCES)) + finally: + _testcapi.set_errno(0) if __name__ == "__main__": diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index 3d02bb6ec2389ba..c8d348a48225981 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -1,6 +1,8 @@ +import errno import unittest import sys from test import support +from test.support import import_helper from test.support.testcase import ComplexesAreIdenticalMixin from test.support.numbers import ( VALID_UNDERSCORE_LITERALS, @@ -9,6 +11,7 @@ from random import random from math import isnan, copysign +import cmath import operator INF = float("inf") @@ -860,8 +863,30 @@ def test_abs(self): for num in nums: self.assertAlmostEqual((num.real**2 + num.imag**2) ** 0.5, abs(num)) + for x in 0.0, -0.0, INF, -INF, NAN: + for y in 0.0, -0.0, INF, -INF, NAN: + with self.subTest(x=x, y=y): + z = complex(x, y) + r = abs(z) + if cmath.isfinite(z): + self.assertFloatsAreIdentical(r, 0.0) + elif cmath.isinf(z): + self.assertEqual(r, INF) + else: + self.assertTrue(cmath.isnan(z)) + self.assertTrue(isnan(r)) + self.assertRaises(OverflowError, abs, complex(DBL_MAX, DBL_MAX)) + def test_abs_errno_handling(self): + _testcapi = import_helper.import_module('_testcapi') + z = complex('nan') + _testcapi.set_errno(errno.ERANGE) + try: + self.assertTrue(isnan(abs(z))) + finally: + _testcapi.set_errno(0) + def test_repr_str(self): def test(v, expected, test_fn=self.assertEqual): test_fn(repr(v), expected) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-08-11-06-04-26.gh-issue-155526.W7ZHXu.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-11-06-04-26.gh-issue-155526.W7ZHXu.rst new file mode 100644 index 000000000000000..624cd2c19744b33 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-11-06-04-26.gh-issue-155526.W7ZHXu.rst @@ -0,0 +1,2 @@ +Correct ``errno`` handling in ``abs(complex)``. Patch by Sergey B +Kirpichev. diff --git a/Modules/_testcapi/complex.c b/Modules/_testcapi/complex.c index fb5234d03cf0676..f1bbeb4804a1918 100644 --- a/Modules/_testcapi/complex.c +++ b/Modules/_testcapi/complex.c @@ -76,7 +76,6 @@ _py_c_abs(PyObject *Py_UNUSED(module), PyObject* obj) return NULL; } - errno = 0; res = _Py_c_abs(complex); return Py_BuildValue("di", res, errno); } diff --git a/Modules/cmathmodule.c b/Modules/cmathmodule.c index f6e1475b00ecfbb..e756b550e7753e1 100644 --- a/Modules/cmathmodule.c +++ b/Modules/cmathmodule.c @@ -1029,8 +1029,8 @@ cmath_polar_impl(PyObject *module, Py_complex z) { double r, phi; - errno = 0; phi = atan2(z.imag, z.real); /* should not cause any exception */ + errno = 0; r = _Py_c_abs(z); /* sets errno to ERANGE on overflow */ if (errno != 0) return math_error(); diff --git a/Objects/complexobject.c b/Objects/complexobject.c index 9328baf013c972a..4d2b5dc8e4613f3 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -379,8 +379,9 @@ c_powi(Py_complex x, long n) double _Py_c_abs(Py_complex z) { - /* sets errno = ERANGE on overflow; otherwise errno = 0 */ + /* sets errno = ERANGE on overflow */ double result; + int saved_errno = errno; if (!isfinite(z.real) || !isfinite(z.imag)) { /* C99 rules: if either the real or the imaginary part is an @@ -388,23 +389,24 @@ _Py_c_abs(Py_complex z) NaN. */ if (isinf(z.real)) { result = fabs(z.real); - errno = 0; + errno = saved_errno; return result; } if (isinf(z.imag)) { result = fabs(z.imag); - errno = 0; + errno = saved_errno; return result; } /* either the real or imaginary part is a NaN, and neither is infinite. Result should be NaN. */ + errno = saved_errno; return Py_NAN; } result = hypot(z.real, z.imag); if (!isfinite(result)) errno = ERANGE; else - errno = 0; + errno = saved_errno; return result; } @@ -812,7 +814,10 @@ static PyObject * complex_abs(PyObject *op) { PyComplexObject *v = _PyComplexObject_CAST(op); - double result = _Py_c_abs(v->cval); + double result; + + errno = 0; + result = _Py_c_abs(v->cval); if (errno == ERANGE) { PyErr_SetString(PyExc_OverflowError, "absolute value too large");