diff --git a/Doc/library/cmath.rst b/Doc/library/cmath.rst index 26518a0458fd81..0b1c6a866829f2 100644 --- a/Doc/library/cmath.rst +++ b/Doc/library/cmath.rst @@ -38,6 +38,14 @@ the function is then applied to the result of the conversion. 1.4142135623730951j +Most functions compute and return the C99 Annex G recommended result. +If the standard recommends raising ``FE_DIVBYZERO`` or ``FE_INVALID`` +floating-point exceptions, a :exc:`ValueError` is raised and the recommended +result is available as the ``value`` attribute of the exception object. +If a range error occurs due to an overflow in any component of the result, +an :exc:`OverflowError` is raised. + + ==================================================== ============================================ **Conversions to and from polar coordinates** -------------------------------------------------------------------------------------------------- diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index daf3e8fb6c2c2b..418a9672ef2662 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -89,6 +89,14 @@ New modules Improved modules ================ +cmath +----- + +* Provide C99 Annex G return values for :mod:`cmath`'s functions as the + ``value`` attribute of exception objects. + (Contributed by Sergey B Kirpichev in :gh:`133895`.) + + dbm --- diff --git a/Lib/test/test_cmath.py b/Lib/test/test_cmath.py index a96a5780b31b6f..35bac069570e36 100644 --- a/Lib/test/test_cmath.py +++ b/Lib/test/test_cmath.py @@ -318,22 +318,23 @@ def polar_complex(z): if 'divide-by-zero' in flags or 'invalid' in flags: try: actual = function(arg) - except ValueError: - continue + except ValueError as exc: + actual = exc.value else: self.fail('ValueError not raised in test ' '{}: {}(complex({!r}, {!r}))'.format(id, fn, ar, ai)) - if 'overflow' in flags: + elif 'overflow' in flags: try: actual = function(arg) - except OverflowError: - continue + except OverflowError as exc: + actual = exc.value if fn != 'polar' else complex(*exc.value) else: self.fail('OverflowError not raised in test ' '{}: {}(complex({!r}, {!r}))'.format(id, fn, ar, ai)) - actual = function(arg) + else: + actual = function(arg) if 'ignore-real-sign' in flags: actual = complex(abs(actual.real), actual.imag) diff --git a/Misc/NEWS.d/next/Library/2025-06-01-10-38-00.gh-issue-133895.0X7c-V.rst b/Misc/NEWS.d/next/Library/2025-06-01-10-38-00.gh-issue-133895.0X7c-V.rst new file mode 100644 index 00000000000000..d645f6dfd6e6b3 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-06-01-10-38-00.gh-issue-133895.0X7c-V.rst @@ -0,0 +1,2 @@ +Provide C99 Annex G return values for :mod:`cmath`'s functions as the +``value`` attribute of exception objects. Patch by Sergey B Kirpichev. diff --git a/Modules/clinic/cmathmodule.c.h b/Modules/clinic/cmathmodule.c.h index 7f9e65baf120ea..54b65943b484a4 100644 --- a/Modules/clinic/cmathmodule.c.h +++ b/Modules/clinic/cmathmodule.c.h @@ -34,17 +34,14 @@ cmath_acos(PyObject *module, PyObject *arg) /* modifications for z */ errno = 0; _return_value = cmath_acos_impl(module, z); - if (errno == EDOM) { - PyErr_SetString(PyExc_ValueError, "math domain error"); - goto exit; - } - else if (errno == ERANGE) { - PyErr_SetString(PyExc_OverflowError, "math range error"); + return_value = PyComplex_FromCComplex(_return_value); + if (errno) { + if (return_value) { + set_cmath_error(return_value); + } + return_value = NULL; goto exit; } - else { - return_value = PyComplex_FromCComplex(_return_value); - } exit: return return_value; @@ -76,17 +73,14 @@ cmath_acosh(PyObject *module, PyObject *arg) /* modifications for z */ errno = 0; _return_value = cmath_acosh_impl(module, z); - if (errno == EDOM) { - PyErr_SetString(PyExc_ValueError, "math domain error"); - goto exit; - } - else if (errno == ERANGE) { - PyErr_SetString(PyExc_OverflowError, "math range error"); + return_value = PyComplex_FromCComplex(_return_value); + if (errno) { + if (return_value) { + set_cmath_error(return_value); + } + return_value = NULL; goto exit; } - else { - return_value = PyComplex_FromCComplex(_return_value); - } exit: return return_value; @@ -118,17 +112,14 @@ cmath_asin(PyObject *module, PyObject *arg) /* modifications for z */ errno = 0; _return_value = cmath_asin_impl(module, z); - if (errno == EDOM) { - PyErr_SetString(PyExc_ValueError, "math domain error"); - goto exit; - } - else if (errno == ERANGE) { - PyErr_SetString(PyExc_OverflowError, "math range error"); + return_value = PyComplex_FromCComplex(_return_value); + if (errno) { + if (return_value) { + set_cmath_error(return_value); + } + return_value = NULL; goto exit; } - else { - return_value = PyComplex_FromCComplex(_return_value); - } exit: return return_value; @@ -160,17 +151,14 @@ cmath_asinh(PyObject *module, PyObject *arg) /* modifications for z */ errno = 0; _return_value = cmath_asinh_impl(module, z); - if (errno == EDOM) { - PyErr_SetString(PyExc_ValueError, "math domain error"); - goto exit; - } - else if (errno == ERANGE) { - PyErr_SetString(PyExc_OverflowError, "math range error"); + return_value = PyComplex_FromCComplex(_return_value); + if (errno) { + if (return_value) { + set_cmath_error(return_value); + } + return_value = NULL; goto exit; } - else { - return_value = PyComplex_FromCComplex(_return_value); - } exit: return return_value; @@ -202,17 +190,14 @@ cmath_atan(PyObject *module, PyObject *arg) /* modifications for z */ errno = 0; _return_value = cmath_atan_impl(module, z); - if (errno == EDOM) { - PyErr_SetString(PyExc_ValueError, "math domain error"); - goto exit; - } - else if (errno == ERANGE) { - PyErr_SetString(PyExc_OverflowError, "math range error"); + return_value = PyComplex_FromCComplex(_return_value); + if (errno) { + if (return_value) { + set_cmath_error(return_value); + } + return_value = NULL; goto exit; } - else { - return_value = PyComplex_FromCComplex(_return_value); - } exit: return return_value; @@ -244,17 +229,14 @@ cmath_atanh(PyObject *module, PyObject *arg) /* modifications for z */ errno = 0; _return_value = cmath_atanh_impl(module, z); - if (errno == EDOM) { - PyErr_SetString(PyExc_ValueError, "math domain error"); - goto exit; - } - else if (errno == ERANGE) { - PyErr_SetString(PyExc_OverflowError, "math range error"); + return_value = PyComplex_FromCComplex(_return_value); + if (errno) { + if (return_value) { + set_cmath_error(return_value); + } + return_value = NULL; goto exit; } - else { - return_value = PyComplex_FromCComplex(_return_value); - } exit: return return_value; @@ -286,17 +268,14 @@ cmath_cos(PyObject *module, PyObject *arg) /* modifications for z */ errno = 0; _return_value = cmath_cos_impl(module, z); - if (errno == EDOM) { - PyErr_SetString(PyExc_ValueError, "math domain error"); - goto exit; - } - else if (errno == ERANGE) { - PyErr_SetString(PyExc_OverflowError, "math range error"); + return_value = PyComplex_FromCComplex(_return_value); + if (errno) { + if (return_value) { + set_cmath_error(return_value); + } + return_value = NULL; goto exit; } - else { - return_value = PyComplex_FromCComplex(_return_value); - } exit: return return_value; @@ -328,17 +307,14 @@ cmath_cosh(PyObject *module, PyObject *arg) /* modifications for z */ errno = 0; _return_value = cmath_cosh_impl(module, z); - if (errno == EDOM) { - PyErr_SetString(PyExc_ValueError, "math domain error"); - goto exit; - } - else if (errno == ERANGE) { - PyErr_SetString(PyExc_OverflowError, "math range error"); + return_value = PyComplex_FromCComplex(_return_value); + if (errno) { + if (return_value) { + set_cmath_error(return_value); + } + return_value = NULL; goto exit; } - else { - return_value = PyComplex_FromCComplex(_return_value); - } exit: return return_value; @@ -370,17 +346,14 @@ cmath_exp(PyObject *module, PyObject *arg) /* modifications for z */ errno = 0; _return_value = cmath_exp_impl(module, z); - if (errno == EDOM) { - PyErr_SetString(PyExc_ValueError, "math domain error"); - goto exit; - } - else if (errno == ERANGE) { - PyErr_SetString(PyExc_OverflowError, "math range error"); + return_value = PyComplex_FromCComplex(_return_value); + if (errno) { + if (return_value) { + set_cmath_error(return_value); + } + return_value = NULL; goto exit; } - else { - return_value = PyComplex_FromCComplex(_return_value); - } exit: return return_value; @@ -412,17 +385,14 @@ cmath_log10(PyObject *module, PyObject *arg) /* modifications for z */ errno = 0; _return_value = cmath_log10_impl(module, z); - if (errno == EDOM) { - PyErr_SetString(PyExc_ValueError, "math domain error"); - goto exit; - } - else if (errno == ERANGE) { - PyErr_SetString(PyExc_OverflowError, "math range error"); + return_value = PyComplex_FromCComplex(_return_value); + if (errno) { + if (return_value) { + set_cmath_error(return_value); + } + return_value = NULL; goto exit; } - else { - return_value = PyComplex_FromCComplex(_return_value); - } exit: return return_value; @@ -454,17 +424,14 @@ cmath_sin(PyObject *module, PyObject *arg) /* modifications for z */ errno = 0; _return_value = cmath_sin_impl(module, z); - if (errno == EDOM) { - PyErr_SetString(PyExc_ValueError, "math domain error"); - goto exit; - } - else if (errno == ERANGE) { - PyErr_SetString(PyExc_OverflowError, "math range error"); + return_value = PyComplex_FromCComplex(_return_value); + if (errno) { + if (return_value) { + set_cmath_error(return_value); + } + return_value = NULL; goto exit; } - else { - return_value = PyComplex_FromCComplex(_return_value); - } exit: return return_value; @@ -496,17 +463,14 @@ cmath_sinh(PyObject *module, PyObject *arg) /* modifications for z */ errno = 0; _return_value = cmath_sinh_impl(module, z); - if (errno == EDOM) { - PyErr_SetString(PyExc_ValueError, "math domain error"); - goto exit; - } - else if (errno == ERANGE) { - PyErr_SetString(PyExc_OverflowError, "math range error"); + return_value = PyComplex_FromCComplex(_return_value); + if (errno) { + if (return_value) { + set_cmath_error(return_value); + } + return_value = NULL; goto exit; } - else { - return_value = PyComplex_FromCComplex(_return_value); - } exit: return return_value; @@ -538,17 +502,14 @@ cmath_sqrt(PyObject *module, PyObject *arg) /* modifications for z */ errno = 0; _return_value = cmath_sqrt_impl(module, z); - if (errno == EDOM) { - PyErr_SetString(PyExc_ValueError, "math domain error"); - goto exit; - } - else if (errno == ERANGE) { - PyErr_SetString(PyExc_OverflowError, "math range error"); + return_value = PyComplex_FromCComplex(_return_value); + if (errno) { + if (return_value) { + set_cmath_error(return_value); + } + return_value = NULL; goto exit; } - else { - return_value = PyComplex_FromCComplex(_return_value); - } exit: return return_value; @@ -580,17 +541,14 @@ cmath_tan(PyObject *module, PyObject *arg) /* modifications for z */ errno = 0; _return_value = cmath_tan_impl(module, z); - if (errno == EDOM) { - PyErr_SetString(PyExc_ValueError, "math domain error"); - goto exit; - } - else if (errno == ERANGE) { - PyErr_SetString(PyExc_OverflowError, "math range error"); + return_value = PyComplex_FromCComplex(_return_value); + if (errno) { + if (return_value) { + set_cmath_error(return_value); + } + return_value = NULL; goto exit; } - else { - return_value = PyComplex_FromCComplex(_return_value); - } exit: return return_value; @@ -622,17 +580,14 @@ cmath_tanh(PyObject *module, PyObject *arg) /* modifications for z */ errno = 0; _return_value = cmath_tanh_impl(module, z); - if (errno == EDOM) { - PyErr_SetString(PyExc_ValueError, "math domain error"); - goto exit; - } - else if (errno == ERANGE) { - PyErr_SetString(PyExc_OverflowError, "math range error"); + return_value = PyComplex_FromCComplex(_return_value); + if (errno) { + if (return_value) { + set_cmath_error(return_value); + } + return_value = NULL; goto exit; } - else { - return_value = PyComplex_FromCComplex(_return_value); - } exit: return return_value; @@ -744,7 +699,7 @@ PyDoc_STRVAR(cmath_rect__doc__, #define CMATH_RECT_METHODDEF \ {"rect", _PyCFunction_CAST(cmath_rect), METH_FASTCALL, cmath_rect__doc__}, -static PyObject * +static Py_complex cmath_rect_impl(PyObject *module, double r, double phi); static PyObject * @@ -753,6 +708,7 @@ cmath_rect(PyObject *module, PyObject *const *args, Py_ssize_t nargs) PyObject *return_value = NULL; double r; double phi; + Py_complex _return_value; if (!_PyArg_CheckPositional("rect", nargs, 2, 2)) { goto exit; @@ -777,7 +733,15 @@ cmath_rect(PyObject *module, PyObject *const *args, Py_ssize_t nargs) goto exit; } } - return_value = cmath_rect_impl(module, r, phi); + _return_value = cmath_rect_impl(module, r, phi); + return_value = PyComplex_FromCComplex(_return_value); + if (errno) { + if (return_value) { + set_cmath_error(return_value); + } + return_value = NULL; + goto exit; + } exit: return return_value; @@ -985,4 +949,4 @@ cmath_isclose(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObjec exit: return return_value; } -/*[clinic end generated code: output=631db17fb1c79d66 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=1f9be4ea0ab6951b input=a9049054013a1b77]*/ diff --git a/Modules/cmathmodule.c b/Modules/cmathmodule.c index 81cbf0d554de3c..f86017b18cbec3 100644 --- a/Modules/cmathmodule.c +++ b/Modules/cmathmodule.c @@ -16,6 +16,48 @@ /* For _Py_log1p with workarounds for buggy handling of zeros. */ #include "_math.h" +/* A little helper to set 'value' attribute on exceptions. + Warning: steal a reference to value. */ + +static void +set_cmath_error(PyObject *value) +{ + if (errno == EDOM || errno == ERANGE) { + PyObject *exc_type; + PyObject *exc_string; + + if (errno == EDOM) { + exc_type = PyExc_ValueError; + exc_string = PyUnicode_FromString("math domain error"); + } + else { + exc_type = PyExc_OverflowError; + exc_string = PyUnicode_FromString("math range error"); + } + if (!exc_string) { + Py_DECREF(value); + return; + } + PyObject *exc = PyObject_CallOneArg(exc_type, exc_string); + + Py_DECREF(exc_string); + if (!exc) { + Py_DECREF(value); + return; + } + if (PyObject_SetAttrString(exc, "value", value)) { + Py_DECREF(value); + return; + } + Py_DECREF(value); + PyErr_SetRaisedException(exc); + } + else { /* Unexpected math error */ + Py_DECREF(value); + PyErr_SetFromErrno(PyExc_ValueError); + } +} + #include "clinic/cmathmodule.c.h" /*[clinic input] module cmath @@ -34,20 +76,17 @@ class Py_complex_protected_return_converter(CReturnConverter): def render(self, function, data): self.declare(data) data.return_conversion.append(""" -if (errno == EDOM) { - PyErr_SetString(PyExc_ValueError, "math domain error"); - goto exit; -} -else if (errno == ERANGE) { - PyErr_SetString(PyExc_OverflowError, "math range error"); +return_value = PyComplex_FromCComplex(_return_value); +if (errno) { + if (return_value) { + set_cmath_error(return_value); + } + return_value = NULL; goto exit; } -else { - return_value = PyComplex_FromCComplex(_return_value); -} """.strip()) [python start generated code]*/ -/*[python end generated code: output=da39a3ee5e6b4b0d input=8b27adb674c08321]*/ +/*[python end generated code: output=da39a3ee5e6b4b0d input=fff1087cce9b4c85]*/ #if (FLT_RADIX != 2 && FLT_RADIX != 16) #error "Modules/cmathmodule.c expects FLT_RADIX to be 2 or 16" @@ -438,6 +477,9 @@ cmath_cosh_impl(PyObject *module, Py_complex z) x_minus_one = z.real - copysign(1., z.real); r.real = cos(z.imag) * cosh(x_minus_one) * Py_MATH_E; r.imag = sin(z.imag) * sinh(x_minus_one) * Py_MATH_E; + if (isnan(r.imag)) { + r.imag = copysign(0., z.real*z.imag); + } } else { r.real = cos(z.imag) * cosh(z.real); r.imag = sin(z.imag) * sinh(z.real); @@ -674,6 +716,9 @@ cmath_sinh_impl(PyObject *module, Py_complex z) x_minus_one = z.real - copysign(1., z.real); r.real = cos(z.imag) * sinh(x_minus_one) * Py_MATH_E; r.imag = sin(z.imag) * cosh(x_minus_one) * Py_MATH_E; + if (isnan(r.imag)) { + r.imag = copysign(0., z.imag); + } } else { r.real = cos(z.imag) * sinh(z.real); r.imag = sin(z.imag) * cosh(z.real); @@ -888,9 +933,16 @@ cmath_log_impl(PyObject *module, Py_complex x, PyObject *y_obj) y = c_log(y); x = _Py_c_quot(x, y); } - if (errno != 0) - return math_error(); - return PyComplex_FromCComplex(x); + + PyObject *res = PyComplex_FromCComplex(x); + + if (errno) { + if (res) { + set_cmath_error(res); + } + return NULL; + } + return res; } @@ -952,10 +1004,16 @@ cmath_polar_impl(PyObject *module, Py_complex z) errno = 0; phi = atan2(z.imag, z.real); /* should not cause any exception */ r = _Py_c_abs(z); /* sets errno to ERANGE on overflow */ - if (errno != 0) - return math_error(); - else - return Py_BuildValue("dd", r, phi); + + PyObject *res = Py_BuildValue("dd", r, phi); + + if (errno != 0) { + if (res) { + set_cmath_error(res); + } + return NULL; + } + return res; } /* @@ -972,7 +1030,7 @@ cmath_polar_impl(PyObject *module, Py_complex z) static Py_complex rect_special_values[7][7]; /*[clinic input] -cmath.rect +cmath.rect -> Py_complex_protected r: double phi: double @@ -981,9 +1039,9 @@ cmath.rect Convert from polar coordinates to rectangular coordinates. [clinic start generated code]*/ -static PyObject * +static Py_complex cmath_rect_impl(PyObject *module, double r, double phi) -/*[clinic end generated code: output=385a0690925df2d5 input=24c5646d147efd69]*/ +/*[clinic end generated code: output=74ff3d17585f3388 input=50e60c5d28c834e6]*/ { Py_complex z; errno = 0; @@ -1027,11 +1085,7 @@ cmath_rect_impl(PyObject *module, double r, double phi) z.imag = r * sin(phi); errno = 0; } - - if (errno != 0) - return math_error(); - else - return PyComplex_FromCComplex(z); + return z; } /*[clinic input]