https://github.com/python/cpython/commit/e5d4fa281c573b764b827f3defae260787024e43
commit: e5d4fa281c573b764b827f3defae260787024e43
branch: main
author: Sergey B Kirpichev <[email protected]>
committer: vstinner <[email protected]>
date: 2026-09-11T17:46:59+02:00
summary:

gh-155526: Don't check errno in abs(complex) (#155527)

abs(complex) no longer raises OverflowError if errno was set to ERANGE
by some library call but abs() doesn't overflow.

_Py_c_abs() no longer sets errno to zero on success, but rather leaves it
unchanged.

Co-authored-by: Victor Stinner <[email protected]>
Co-authored-by: hpkfft.com <[email protected]>

files:
A Misc/NEWS.d/next/C_API/2026-09-10-15-19-16.gh-issue-155526.kMKhyp.rst
A 
Misc/NEWS.d/next/Core_and_Builtins/2026-08-11-06-04-26.gh-issue-155526.W7ZHXu.rst
M Doc/c-api/complex.rst
M Doc/whatsnew/3.16.rst
M Lib/test/test_capi/test_complex.py
M Lib/test/test_complex.py
M Modules/_testcapi/complex.c
M Modules/cmathmodule.c
M Objects/complexobject.c

diff --git a/Doc/c-api/complex.rst b/Doc/c-api/complex.rst
index 10f96c7cb75e882..7a11e6c8a7a13b4 100644
--- a/Doc/c-api/complex.rst
+++ b/Doc/c-api/complex.rst
@@ -197,3 +197,6 @@ the :ref:`Number Protocol <number>` API or use native 
complex types, like
    Set :c:data:`errno` to :c:macro:`!ERANGE` on overflows.
 
    .. deprecated:: 3.15
+
+   .. versionchanged:: next
+      This function leaves :c:data:`errno` unchanged on success.
diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst
index 69fb89ab56446e0..1098b152e51eb41 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,
+  but rather leaves it unchanged.
+  (Contributed by Sergey B Kirpichev in :gh:`155526`.)
+
 Deprecated C APIs
 -----------------
 
diff --git a/Lib/test/test_capi/test_complex.py 
b/Lib/test/test_capi/test_complex.py
index c3189a67cc7e2d3..0aca6006bec06be 100644
--- a/Lib/test/test_capi/test_complex.py
+++ b/Lib/test/test_capi/test_complex.py
@@ -281,18 +281,34 @@ 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)
+        def c_abs(num):
+            # On success, _Py_c_abs() doesn't use errno and leaves errno
+            # unchanged
+            _testcapi.set_errno(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)),
+                             (INF, errno.ERANGE))
+
+            # Preserve errno on success
+            _testcapi.set_errno(errno.EACCES)
+            self.assertEqual(_py_c_abs(1j), (1.0, 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/C_API/2026-09-10-15-19-16.gh-issue-155526.kMKhyp.rst 
b/Misc/NEWS.d/next/C_API/2026-09-10-15-19-16.gh-issue-155526.kMKhyp.rst
new file mode 100644
index 000000000000000..1a2c2737bbaf97f
--- /dev/null
+++ b/Misc/NEWS.d/next/C_API/2026-09-10-15-19-16.gh-issue-155526.kMKhyp.rst
@@ -0,0 +1,2 @@
+: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.
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..ae0c08973a8195b
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-11-06-04-26.gh-issue-155526.W7ZHXu.rst
@@ -0,0 +1,3 @@
+Fix spurious :exc:`OverflowError` for ``abs(nanj)`` in case :c:data:`errno` was
+previously set to :c:macro:`!ERANGE` by some library call.
+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");

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to