Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
5aa9e73
gh-155526: correct errno handling in complex_abs()
skirpichev Aug 11, 2026
9702894
Merge branch 'master' into complex-abs-errno/155526
skirpichev Aug 11, 2026
576f6cd
address review: restore errno + readability
skirpichev Aug 11, 2026
31d9fc3
Merge branch 'master' into complex-abs-errno/155526
skirpichev Aug 27, 2026
3b851ac
+ test special components for complex_abs()
skirpichev Aug 27, 2026
c4f365e
Merge branch 'master' into complex-abs-errno/155526
skirpichev Aug 31, 2026
fde8fc7
+ don't use errno in complex_abs()
skirpichev Aug 31, 2026
5fa214f
ignore errno from atan2 in cmath_polar_impl()
skirpichev Sep 5, 2026
23d948e
Merge branch 'master' into complex-abs-errno/155526
skirpichev Sep 5, 2026
28ba074
restore errno on success
skirpichev Sep 6, 2026
4539933
Merge branch 'master' into complex-abs-errno/155526
skirpichev Sep 8, 2026
ad46155
clarify news
skirpichev Sep 8, 2026
4d3badf
Apply batched suggestions from code review
skirpichev Sep 9, 2026
c0c0657
Merge branch 'master' into complex-abs-errno/155526
skirpichev Sep 9, 2026
6b3219d
address review: versionchanged and whatsnew entry
skirpichev Sep 9, 2026
674b980
Apply batched suggestions from code review
vstinner Sep 9, 2026
7c6cceb
Apply suggestion from @hpkfft
vstinner Sep 9, 2026
8c982dd
Apply suggestion from @skirpichev
skirpichev Sep 10, 2026
386719a
Merge branch 'main' into complex-abs-errno/155526
skirpichev Sep 10, 2026
bf92f8e
Apply suggestion from @skirpichev
skirpichev Sep 10, 2026
c1a6753
Update Doc/c-api/complex.rst
skirpichev Sep 10, 2026
654cc70
address review: split news
skirpichev Sep 10, 2026
38eac1f
Merge branch 'master' into complex-abs-errno/155526
skirpichev Sep 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Doc/c-api/complex.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
4 changes: 4 additions & 0 deletions Doc/whatsnew/3.16.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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`.)
Comment thread
skirpichev marked this conversation as resolved.

Deprecated C APIs
-----------------

Expand Down
40 changes: 28 additions & 12 deletions Lib/test/test_capi/test_complex.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__":
Expand Down
25 changes: 25 additions & 0 deletions Lib/test/test_complex.py
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -9,6 +11,7 @@

from random import random
from math import isnan, copysign
import cmath
import operator

INF = float("inf")
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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.
1 change: 0 additions & 1 deletion Modules/_testcapi/complex.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
2 changes: 1 addition & 1 deletion Modules/cmathmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
15 changes: 10 additions & 5 deletions Objects/complexobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -379,32 +379,34 @@ 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 */
Comment thread
skirpichev marked this conversation as resolved.
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
infinity, return infinity, even if the other part is a
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;
}

Expand Down Expand Up @@ -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");
Expand Down
Loading