Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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:: 3.16
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,4 @@
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.
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