Skip to content

Commit e5d4fa2

Browse files
skirpichevvstinnerhpkfft
authored
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 <vstinner@python.org> Co-authored-by: hpkfft.com <paul@hpkfft.com>
1 parent d85fa1a commit e5d4fa2

9 files changed

Lines changed: 76 additions & 19 deletions

File tree

Doc/c-api/complex.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,3 +197,6 @@ the :ref:`Number Protocol <number>` API or use native complex types, like
197197
Set :c:data:`errno` to :c:macro:`!ERANGE` on overflows.
198198
199199
.. deprecated:: 3.15
200+
201+
.. versionchanged:: next
202+
This function leaves :c:data:`errno` unchanged on success.

Doc/whatsnew/3.16.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1018,6 +1018,10 @@ Porting to Python 3.16
10181018
if the value cannot be marshalled.
10191019
(Contributed by Serhiy Storchaka in :gh:`155907`.)
10201020

1021+
* :c:func:`_Py_c_abs` no longer sets :c:data:`errno` to zero on success,
1022+
but rather leaves it unchanged.
1023+
(Contributed by Sergey B Kirpichev in :gh:`155526`.)
1024+
10211025
Deprecated C APIs
10221026
-----------------
10231027

Lib/test/test_capi/test_complex.py

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -281,18 +281,34 @@ def test_py_c_abs(self):
281281
# Test _Py_c_abs()
282282
_py_c_abs = _testcapi._py_c_abs
283283

284-
self.assertEqual(_py_c_abs(-1), (1.0, 0))
285-
self.assertEqual(_py_c_abs(1j), (1.0, 0))
286-
287-
self.assertEqual(_py_c_abs(complex('+inf+1j')), (INF, 0))
288-
self.assertEqual(_py_c_abs(complex('-inf+1j')), (INF, 0))
289-
self.assertEqual(_py_c_abs(complex('1.25+infj')), (INF, 0))
290-
self.assertEqual(_py_c_abs(complex('1.25-infj')), (INF, 0))
291-
292-
self.assertTrue(isnan(_py_c_abs(complex('1.25+nanj'))[0]))
293-
self.assertTrue(isnan(_py_c_abs(complex('nan-1j'))[0]))
294-
295-
self.assertEqual(_py_c_abs(complex(*[DBL_MAX]*2))[1], errno.ERANGE)
284+
def c_abs(num):
285+
# On success, _Py_c_abs() doesn't use errno and leaves errno
286+
# unchanged
287+
_testcapi.set_errno(0)
288+
result, errno = _py_c_abs(num)
289+
self.assertEqual(errno, 0)
290+
return result
291+
292+
try:
293+
self.assertEqual(c_abs(-1), 1.0)
294+
self.assertEqual(c_abs(1j), 1.0)
295+
self.assertEqual(c_abs(complex('+inf+1j')), INF)
296+
self.assertEqual(c_abs(complex('-inf+1j')), INF)
297+
self.assertEqual(c_abs(complex('1.25+infj')), INF)
298+
self.assertEqual(c_abs(complex('1.25-infj')), INF)
299+
self.assertTrue(isnan(c_abs(complex('1.25+nanj'))))
300+
self.assertTrue(isnan(c_abs(complex('nan-1j'))))
301+
302+
# Set errno to ERANGE on overflow
303+
_testcapi.set_errno(0)
304+
self.assertEqual(_py_c_abs(complex(*[DBL_MAX]*2)),
305+
(INF, errno.ERANGE))
306+
307+
# Preserve errno on success
308+
_testcapi.set_errno(errno.EACCES)
309+
self.assertEqual(_py_c_abs(1j), (1.0, errno.EACCES))
310+
finally:
311+
_testcapi.set_errno(0)
296312

297313

298314
if __name__ == "__main__":

Lib/test/test_complex.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import errno
12
import unittest
23
import sys
34
from test import support
5+
from test.support import import_helper
46
from test.support.testcase import ComplexesAreIdenticalMixin
57
from test.support.numbers import (
68
VALID_UNDERSCORE_LITERALS,
@@ -9,6 +11,7 @@
911

1012
from random import random
1113
from math import isnan, copysign
14+
import cmath
1215
import operator
1316

1417
INF = float("inf")
@@ -860,8 +863,30 @@ def test_abs(self):
860863
for num in nums:
861864
self.assertAlmostEqual((num.real**2 + num.imag**2) ** 0.5, abs(num))
862865

866+
for x in 0.0, -0.0, INF, -INF, NAN:
867+
for y in 0.0, -0.0, INF, -INF, NAN:
868+
with self.subTest(x=x, y=y):
869+
z = complex(x, y)
870+
r = abs(z)
871+
if cmath.isfinite(z):
872+
self.assertFloatsAreIdentical(r, 0.0)
873+
elif cmath.isinf(z):
874+
self.assertEqual(r, INF)
875+
else:
876+
self.assertTrue(cmath.isnan(z))
877+
self.assertTrue(isnan(r))
878+
863879
self.assertRaises(OverflowError, abs, complex(DBL_MAX, DBL_MAX))
864880

881+
def test_abs_errno_handling(self):
882+
_testcapi = import_helper.import_module('_testcapi')
883+
z = complex('nan')
884+
_testcapi.set_errno(errno.ERANGE)
885+
try:
886+
self.assertTrue(isnan(abs(z)))
887+
finally:
888+
_testcapi.set_errno(0)
889+
865890
def test_repr_str(self):
866891
def test(v, expected, test_fn=self.assertEqual):
867892
test_fn(repr(v), expected)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:c:func:`_Py_c_abs` no longer sets :c:data:`errno` to zero on success, but
2+
rather leaves it unchanged. Patch by Sergey B Kirpichev.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix spurious :exc:`OverflowError` for ``abs(nanj)`` in case :c:data:`errno` was
2+
previously set to :c:macro:`!ERANGE` by some library call.
3+
Patch by Sergey B Kirpichev.

Modules/_testcapi/complex.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@ _py_c_abs(PyObject *Py_UNUSED(module), PyObject* obj)
7676
return NULL;
7777
}
7878

79-
errno = 0;
8079
res = _Py_c_abs(complex);
8180
return Py_BuildValue("di", res, errno);
8281
}

Modules/cmathmodule.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1029,8 +1029,8 @@ cmath_polar_impl(PyObject *module, Py_complex z)
10291029
{
10301030
double r, phi;
10311031

1032-
errno = 0;
10331032
phi = atan2(z.imag, z.real); /* should not cause any exception */
1033+
errno = 0;
10341034
r = _Py_c_abs(z); /* sets errno to ERANGE on overflow */
10351035
if (errno != 0)
10361036
return math_error();

Objects/complexobject.c

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -379,32 +379,34 @@ c_powi(Py_complex x, long n)
379379
double
380380
_Py_c_abs(Py_complex z)
381381
{
382-
/* sets errno = ERANGE on overflow; otherwise errno = 0 */
382+
/* sets errno = ERANGE on overflow */
383383
double result;
384+
int saved_errno = errno;
384385

385386
if (!isfinite(z.real) || !isfinite(z.imag)) {
386387
/* C99 rules: if either the real or the imaginary part is an
387388
infinity, return infinity, even if the other part is a
388389
NaN. */
389390
if (isinf(z.real)) {
390391
result = fabs(z.real);
391-
errno = 0;
392+
errno = saved_errno;
392393
return result;
393394
}
394395
if (isinf(z.imag)) {
395396
result = fabs(z.imag);
396-
errno = 0;
397+
errno = saved_errno;
397398
return result;
398399
}
399400
/* either the real or imaginary part is a NaN,
400401
and neither is infinite. Result should be NaN. */
402+
errno = saved_errno;
401403
return Py_NAN;
402404
}
403405
result = hypot(z.real, z.imag);
404406
if (!isfinite(result))
405407
errno = ERANGE;
406408
else
407-
errno = 0;
409+
errno = saved_errno;
408410
return result;
409411
}
410412

@@ -812,7 +814,10 @@ static PyObject *
812814
complex_abs(PyObject *op)
813815
{
814816
PyComplexObject *v = _PyComplexObject_CAST(op);
815-
double result = _Py_c_abs(v->cval);
817+
double result;
818+
819+
errno = 0;
820+
result = _Py_c_abs(v->cval);
816821
if (errno == ERANGE) {
817822
PyErr_SetString(PyExc_OverflowError,
818823
"absolute value too large");

0 commit comments

Comments
 (0)