From 5aa9e730ecb921fe2dce8179f7cff82dec773977 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 11 Aug 2026 06:05:05 +0300 Subject: [PATCH 01/13] gh-155526: correct errno handling in complex_abs() --- Lib/test/test_complex.py | 7 +++++++ .../2026-08-11-06-04-26.gh-issue-155526.W7ZHXu.rst | 2 ++ Objects/complexobject.c | 14 +++++++------- 3 files changed, 16 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-08-11-06-04-26.gh-issue-155526.W7ZHXu.rst diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index bb307191dffcc14..1862288e32e6107 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -1,6 +1,7 @@ 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, @@ -791,6 +792,12 @@ def test_abs(self): 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(34) + self.assertTrue(isnan(abs(z))) + 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/Objects/complexobject.c b/Objects/complexobject.c index 3612c2699a557db..c3c4e6abbc890cc 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -367,7 +367,7 @@ 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; if (!isfinite(z.real) || !isfinite(z.imag)) { @@ -376,12 +376,10 @@ _Py_c_abs(Py_complex z) NaN. */ if (isinf(z.real)) { result = fabs(z.real); - errno = 0; return result; } if (isinf(z.imag)) { result = fabs(z.imag); - errno = 0; return result; } /* either the real or imaginary part is a NaN, @@ -389,10 +387,9 @@ _Py_c_abs(Py_complex z) return Py_NAN; } result = hypot(z.real, z.imag); - if (!isfinite(result)) + if (!isfinite(result)) { errno = ERANGE; - else - errno = 0; + } return result; } @@ -796,7 +793,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"); From 576f6cd356483914dd8c17f1a6b10dfa3d79faa6 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 11 Aug 2026 10:55:37 +0300 Subject: [PATCH 02/13] address review: restore errno + readability --- Lib/test/test_complex.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index 1862288e32e6107..b08488ff14358a1 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -1,3 +1,4 @@ +import errno import unittest import sys from test import support @@ -795,8 +796,11 @@ def test_abs(self): def test_abs_errno_handling(self): _testcapi = import_helper.import_module('_testcapi') z = complex('nan') - _testcapi.set_errno(34) - self.assertTrue(isnan(abs(z))) + _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): From 3b851ac2d9bfa286ee5ac6ebb1cd4ec857f3baa5 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 27 Aug 2026 06:25:02 +0300 Subject: [PATCH 03/13] + test special components for complex_abs() --- Lib/test/test_complex.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py index b08488ff14358a1..1916260465c1641 100644 --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -11,6 +11,7 @@ from random import random from math import isnan, copysign +import cmath import operator INF = float("inf") @@ -791,6 +792,19 @@ 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): From fde8fc7668c6466aa5c52b31c70c6de9720ace4e Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 31 Aug 2026 08:59:16 +0300 Subject: [PATCH 04/13] + don't use errno in complex_abs() --- Objects/complexobject.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Objects/complexobject.c b/Objects/complexobject.c index c3c4e6abbc890cc..6301106d3093bbb 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -795,9 +795,11 @@ complex_abs(PyObject *op) PyComplexObject *v = _PyComplexObject_CAST(op); double result; - errno = 0; - result = _Py_c_abs(v->cval); - if (errno == ERANGE) { + result = hypot(v->cval.real, v->cval.imag); + /* Testing FE_OVERFLOW floating-point exception is slow. */ + if (isfinite(v->cval.real) && isfinite(v->cval.imag) + && !isfinite(result)) + { PyErr_SetString(PyExc_OverflowError, "absolute value too large"); return NULL; From 5fa214f8f93bdfbff9349afc3df9f1ff33300a37 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 5 Sep 2026 19:23:38 +0300 Subject: [PATCH 05/13] ignore errno from atan2 in cmath_polar_impl() --- Modules/cmathmodule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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(); From 28ba0748c99c825ecd1cb8c16adc0b65d5a4a550 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 6 Sep 2026 04:39:11 +0300 Subject: [PATCH 06/13] restore errno on success --- Lib/test/test_capi/test_complex.py | 39 +++++++++++++++++++++--------- Modules/_testcapi/complex.c | 1 - Objects/complexobject.c | 17 +++++++------ 3 files changed, 37 insertions(+), 20 deletions(-) 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/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/Objects/complexobject.c b/Objects/complexobject.c index 1494e5afefa867d..4d2b5dc8e4613f3 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -381,6 +381,7 @@ _Py_c_abs(Py_complex z) { /* 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,20 +389,24 @@ _Py_c_abs(Py_complex z) NaN. */ if (isinf(z.real)) { result = fabs(z.real); + errno = saved_errno; return result; } if (isinf(z.imag)) { result = fabs(z.imag); + 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)) { + if (!isfinite(result)) errno = ERANGE; - } + else + errno = saved_errno; return result; } @@ -811,11 +816,9 @@ complex_abs(PyObject *op) PyComplexObject *v = _PyComplexObject_CAST(op); double result; - result = hypot(v->cval.real, v->cval.imag); - /* Testing FE_OVERFLOW floating-point exception is slow. */ - if (isfinite(v->cval.real) && isfinite(v->cval.imag) - && !isfinite(result)) - { + errno = 0; + result = _Py_c_abs(v->cval); + if (errno == ERANGE) { PyErr_SetString(PyExc_OverflowError, "absolute value too large"); return NULL; From ad461550806a0d7ace5cc66914f901588f8b9f82 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 8 Sep 2026 04:30:20 +0300 Subject: [PATCH 07/13] clarify news --- .../2026-08-11-06-04-26.gh-issue-155526.W7ZHXu.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 index 624cd2c19744b33..2b0b6e072381736 100644 --- 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 @@ -1,2 +1,3 @@ -Correct ``errno`` handling in ``abs(complex)``. Patch by Sergey B +Fix spurious :exc:`OverflowError` for ``abs(nan)`` in case :c:data:`errno` was +previously set to :c:macro:`!ERANGE` by some library call. Patch by Sergey B Kirpichev. From 4d3badf62050056230877cf1c9baeef2ce9bc8c8 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 9 Sep 2026 03:31:01 +0300 Subject: [PATCH 08/13] Apply batched suggestions from code review Co-authored-by: Victor Stinner --- Lib/test/test_capi/test_complex.py | 43 +++++++++++++++--------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/Lib/test/test_capi/test_complex.py b/Lib/test/test_capi/test_complex.py index 7d64237acd38864..0aca6006bec06be 100644 --- a/Lib/test/test_capi/test_complex.py +++ b/Lib/test/test_capi/test_complex.py @@ -281,31 +281,32 @@ def test_py_c_abs(self): # Test _Py_c_abs() _py_c_abs = _testcapi._py_c_abs - try: - _testcapi.set_errno(0) - self.assertEqual(_py_c_abs(-1), (1.0, 0)) + def c_abs(num): + # On success, _Py_c_abs() doesn't use errno and leaves errno + # unchanged _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])) + result, errno = _py_c_abs(num) + self.assertEqual(errno, 0) + return result + try: + self.assertEqual(c_abs(-1), 1.0) + self.assertEqual(c_abs(1j), 1.0) + self.assertEqual(c_abs(complex('+inf+1j')), INF) + self.assertEqual(c_abs(complex('-inf+1j')), INF) + self.assertEqual(c_abs(complex('1.25+infj')), INF) + self.assertEqual(c_abs(complex('1.25-infj')), INF) + self.assertTrue(isnan(c_abs(complex('1.25+nanj')))) + self.assertTrue(isnan(c_abs(complex('nan-1j')))) + + # Set errno to ERANGE on overflow _testcapi.set_errno(0) - self.assertEqual(_py_c_abs(complex(*[DBL_MAX]*2))[1], errno.ERANGE) + self.assertEqual(_py_c_abs(complex(*[DBL_MAX]*2)), + (INF, errno.ERANGE)) - _testcapi.set_errno(errno.EACCES) # preserve errno - self.assertEqual(_py_c_abs(1j), (1, errno.EACCES)) + # Preserve errno on success + _testcapi.set_errno(errno.EACCES) + self.assertEqual(_py_c_abs(1j), (1.0, errno.EACCES)) finally: _testcapi.set_errno(0) From 6b3219dc88c74fa9e5df1de1c6b17ec356101480 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 9 Sep 2026 04:03:50 +0300 Subject: [PATCH 09/13] address review: versionchanged and whatsnew entry --- Doc/c-api/complex.rst | 3 +++ Doc/whatsnew/3.16.rst | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/Doc/c-api/complex.rst b/Doc/c-api/complex.rst index 10f96c7cb75e882..2835eed8bfadeef 100644 --- a/Doc/c-api/complex.rst +++ b/Doc/c-api/complex.rst @@ -197,3 +197,6 @@ the :ref:`Number Protocol ` API or use native complex types, like Set :c:data:`errno` to :c:macro:`!ERANGE` on overflows. .. deprecated:: 3.15 + + .. versionchanged:: 3.16 + This function leave :c:data:`errno` unchanged on success. diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst index 243bd078d37c998..d18ff0b9ab6b594 100644 --- a/Doc/whatsnew/3.16.rst +++ b/Doc/whatsnew/3.16.rst @@ -1018,6 +1018,10 @@ Porting to Python 3.16 if the value cannot be marshalled. (Contributed by Serhiy Storchaka in :gh:`155907`.) +* :c:func:`_Py_c_abs` no longer sets :c:data:`errno` to zero on success, + rather leave it unchanged. + (Contributed by Sergey B Kirpichev in :gh:`155526`.) + Deprecated C APIs ----------------- From 674b980a9a69b4d29b40b18471e9834b70e7f3bb Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 9 Sep 2026 18:24:48 +0200 Subject: [PATCH 10/13] Apply batched suggestions from code review Co-authored-by: hpkfft.com --- Doc/c-api/complex.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/c-api/complex.rst b/Doc/c-api/complex.rst index 2835eed8bfadeef..4bcb1b033d81330 100644 --- a/Doc/c-api/complex.rst +++ b/Doc/c-api/complex.rst @@ -199,4 +199,4 @@ the :ref:`Number Protocol ` API or use native complex types, like .. deprecated:: 3.15 .. versionchanged:: 3.16 - This function leave :c:data:`errno` unchanged on success. + This function leaves :c:data:`errno` unchanged on success. From 7c6cceb6c825b8cc329e18d744ff69a0f814c746 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 9 Sep 2026 18:25:07 +0200 Subject: [PATCH 11/13] Apply suggestion from @hpkfft Co-authored-by: hpkfft.com --- Doc/whatsnew/3.16.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst index d18ff0b9ab6b594..a4e6cc9d1afc4bb 100644 --- a/Doc/whatsnew/3.16.rst +++ b/Doc/whatsnew/3.16.rst @@ -1019,7 +1019,7 @@ Porting to Python 3.16 (Contributed by Serhiy Storchaka in :gh:`155907`.) * :c:func:`_Py_c_abs` no longer sets :c:data:`errno` to zero on success, - rather leave it unchanged. + but rather leaves it unchanged. (Contributed by Sergey B Kirpichev in :gh:`155526`.) Deprecated C APIs From 8c982dde6c4eee5d8ac0e12bcf5d57be6f78131c Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 10 Sep 2026 03:10:15 +0300 Subject: [PATCH 12/13] Apply suggestion from @skirpichev --- .../2026-08-11-06-04-26.gh-issue-155526.W7ZHXu.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 index 2b0b6e072381736..1c785038c7747f0 100644 --- 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 @@ -1,3 +1,4 @@ Fix spurious :exc:`OverflowError` for ``abs(nan)`` in case :c:data:`errno` was -previously set to :c:macro:`!ERANGE` by some library call. Patch by Sergey B -Kirpichev. +previously set to :c:macro:`!ERANGE` by some library call. +:c:func:`_Py_c_abs` no longer sets :c:data:`errno` to zero on success, +but rather leaves it unchanged. Patch by Sergey B Kirpichev. From bf92f8e4813a299095f58bdb13de7e6da2e87935 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 10 Sep 2026 03:18:31 +0300 Subject: [PATCH 13/13] Apply suggestion from @skirpichev --- .../2026-08-11-06-04-26.gh-issue-155526.W7ZHXu.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 1c785038c7747f0..6707a10f9831dcb 100644 --- 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 @@ -1,4 +1,4 @@ -Fix spurious :exc:`OverflowError` for ``abs(nan)`` in case :c:data:`errno` was +Fix spurious :exc:`OverflowError` for ``abs(nanj)`` in case :c:data:`errno` was previously set to :c:macro:`!ERANGE` by some library call. :c:func:`_Py_c_abs` no longer sets :c:data:`errno` to zero on success, but rather leaves it unchanged. Patch by Sergey B Kirpichev.