From fb0151805b598aca4ea9626cb01933a2c5c93489 Mon Sep 17 00:00:00 2001 From: CharlieZhao Date: Wed, 1 Feb 2023 11:02:44 +0800 Subject: [PATCH 01/11] gh-101410: support custom messages for domain errors in the math module This adds basic support to override default messages for domain errors in the math_1() helper. The sqrt(), atanh(), log2(), log10() and log() functions were modified as examples. New macro supports gradual changing of error messages in other 1-arg functions. Co-authored-by: Sergey B Kirpichev --- Lib/test/test_math.py | 38 +++++++++++++ ...-02-01-16-41-31.gh-issue-101410.Dt2aQE.rst | 3 + Modules/mathmodule.c | 56 ++++++++++++------- 3 files changed, 76 insertions(+), 21 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-02-01-16-41-31.gh-issue-101410.Dt2aQE.rst diff --git a/Lib/test/test_math.py b/Lib/test/test_math.py index a3eebc97ada23b..ebcf34045e602d 100644 --- a/Lib/test/test_math.py +++ b/Lib/test/test_math.py @@ -2503,6 +2503,44 @@ def test_input_exceptions(self): self.assertRaises(TypeError, math.atan2, 1.0) self.assertRaises(TypeError, math.atan2, 1.0, 2.0, 3.0) + def test_exception_messages(self): + x = -1.1 + + with self.assertRaises(ValueError, + msg=f"expected a nonnegative input, got {x}"): + math.sqrt(x) + + with self.assertRaises(ValueError, + msg=f"expected a positive input, got {x}"): + math.log(x) + with self.assertRaises(ValueError, + msg=f"expected a positive input, got {x}"): + math.log(123, x) + with self.assertRaises(ValueError, + msg=f"expected a positive input, got {x}"): + math.log2(x) + with self.assertRaises(ValueError, + msg=f"expected a positive input, got {x}"): + math.log2(x) + + x = decimal.Decimal(x) + + with self.assertRaises(ValueError, + msg=f"expected a positive input, got {x!r}"): + math.log(x) + + x = fractions.Fraction(1, 10**400) + + with self.assertRaises(ValueError, + msg=f"expected a positive input, got {float(x)!r}"): + math.log(x) + + x = 1.0 + + with self.assertRaises(ValueError, + msg=f"expected a number between -1 and 1, got {x}"): + math.atanh(x) + # Custom assertions. def assertIsNaN(self, value): diff --git a/Misc/NEWS.d/next/Library/2023-02-01-16-41-31.gh-issue-101410.Dt2aQE.rst b/Misc/NEWS.d/next/Library/2023-02-01-16-41-31.gh-issue-101410.Dt2aQE.rst new file mode 100644 index 00000000000000..31493686daec97 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-02-01-16-41-31.gh-issue-101410.Dt2aQE.rst @@ -0,0 +1,3 @@ +Support custom messages for domain errors in the :mod:`math` module +(:func:`math.sqrt`, :func:`math.log` and :func:`math.atanh` were modified as +examples). Patch by Charlie Zhao and Sergey B Kirpichev. diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index baf2dc439b8959..99f72a4e8c025e 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -921,7 +921,8 @@ is_error(double x) */ static PyObject * -math_1(PyObject *arg, double (*func) (double), int can_overflow) +math_1(PyObject *arg, double (*func) (double), int can_overflow, + const char *err_msg) { double x, r; x = PyFloat_AsDouble(arg); @@ -929,18 +930,14 @@ math_1(PyObject *arg, double (*func) (double), int can_overflow) return NULL; errno = 0; r = (*func)(x); - if (isnan(r) && !isnan(x)) { - PyErr_SetString(PyExc_ValueError, - "math domain error"); /* invalid arg */ - return NULL; - } + if (isnan(r) && !isnan(x)) + goto domain_err; /* domain error */ if (isinf(r) && isfinite(x)) { if (can_overflow) PyErr_SetString(PyExc_OverflowError, "math range error"); /* overflow */ else - PyErr_SetString(PyExc_ValueError, - "math domain error"); /* singularity */ + goto domain_err; /* singularity */ return NULL; } if (isfinite(r) && errno && is_error(r)) @@ -948,6 +945,16 @@ math_1(PyObject *arg, double (*func) (double), int can_overflow) return NULL; return PyFloat_FromDouble(r); + +domain_err: + PyObject* a = PyFloat_FromDouble(x); + + if (a) { + PyErr_Format(PyExc_ValueError, + err_msg ? err_msg : "math domain error", a); + Py_DECREF(a); + } + return NULL; } /* variant of math_1, to be used when the function being wrapped is known to @@ -1032,7 +1039,13 @@ math_2(PyObject *const *args, Py_ssize_t nargs, #define FUNC1(funcname, func, can_overflow, docstring) \ static PyObject * math_##funcname(PyObject *self, PyObject *args) { \ - return math_1(args, func, can_overflow); \ + return math_1(args, func, can_overflow, NULL); \ + }\ + PyDoc_STRVAR(math_##funcname##_doc, docstring); + +#define FUNC1D(funcname, func, can_overflow, docstring, err_msg) \ + static PyObject * math_##funcname(PyObject *self, PyObject *args) { \ + return math_1(args, func, can_overflow, err_msg); \ }\ PyDoc_STRVAR(math_##funcname##_doc, docstring); @@ -1070,9 +1083,10 @@ FUNC2(atan2, atan2, "atan2($module, y, x, /)\n--\n\n" "Return the arc tangent (measured in radians) of y/x.\n\n" "Unlike atan(y/x), the signs of both x and y are considered.") -FUNC1(atanh, atanh, 0, +FUNC1D(atanh, atanh, 0, "atanh($module, x, /)\n--\n\n" - "Return the inverse hyperbolic tangent of x.") + "Return the inverse hyperbolic tangent of x.", + "expected a number between -1 and 1, got %R") FUNC1(cbrt, cbrt, 0, "cbrt($module, x, /)\n--\n\n" "Return the cube root of x.") @@ -1205,9 +1219,10 @@ FUNC1(sin, sin, 0, FUNC1(sinh, sinh, 1, "sinh($module, x, /)\n--\n\n" "Return the hyperbolic sine of x.") -FUNC1(sqrt, sqrt, 0, +FUNC1D(sqrt, sqrt, 0, "sqrt($module, x, /)\n--\n\n" - "Return the square root of x.") + "Return the square root of x.", + "expected a nonnegative input, got %R") FUNC1(tan, tan, 0, "tan($module, x, /)\n--\n\n" "Return the tangent of x (measured in radians).") @@ -2180,7 +2195,7 @@ math_modf_impl(PyObject *module, double x) in that int is larger than PY_SSIZE_T_MAX. */ static PyObject* -loghelper(PyObject* arg, double (*func)(double)) +loghelper(PyObject* arg, double (*func)(double), const char *err_msg) { /* If it is int, do it ourselves. */ if (PyLong_Check(arg)) { @@ -2189,8 +2204,7 @@ loghelper(PyObject* arg, double (*func)(double)) /* Negative or zero inputs give a ValueError. */ if (!_PyLong_IsPositive((PyLongObject *)arg)) { - PyErr_SetString(PyExc_ValueError, - "math domain error"); + PyErr_Format(PyExc_ValueError, err_msg, arg); return NULL; } @@ -2214,7 +2228,7 @@ loghelper(PyObject* arg, double (*func)(double)) } /* Else let libm handle it by itself. */ - return math_1(arg, func, 0); + return math_1(arg, func, 0, err_msg); } @@ -2229,11 +2243,11 @@ math_log(PyObject *module, PyObject * const *args, Py_ssize_t nargs) if (!_PyArg_CheckPositional("log", nargs, 1, 2)) return NULL; - num = loghelper(args[0], m_log); + num = loghelper(args[0], m_log, "expected a positive input, got %R"); if (num == NULL || nargs == 1) return num; - den = loghelper(args[1], m_log); + den = loghelper(args[1], m_log, "expected a positive input, got %R"); if (den == NULL) { Py_DECREF(num); return NULL; @@ -2263,7 +2277,7 @@ static PyObject * math_log2(PyObject *module, PyObject *x) /*[clinic end generated code: output=5425899a4d5d6acb input=08321262bae4f39b]*/ { - return loghelper(x, m_log2); + return loghelper(x, m_log2, "expected a positive input, got %R"); } @@ -2280,7 +2294,7 @@ static PyObject * math_log10(PyObject *module, PyObject *x) /*[clinic end generated code: output=be72a64617df9c6f input=b2469d02c6469e53]*/ { - return loghelper(x, m_log10); + return loghelper(x, m_log10, "expected a positive input, got %R"); } From 05b55eb1be90404643452fc1bcb755171a3f2e7b Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 21 Sep 2024 12:32:21 +0300 Subject: [PATCH 02/11] +1 --- Modules/mathmodule.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index 99f72a4e8c025e..665b02e70202cf 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -924,6 +924,7 @@ static PyObject * math_1(PyObject *arg, double (*func) (double), int can_overflow, const char *err_msg) { + PyObject *a; double x, r; x = PyFloat_AsDouble(arg); if (x == -1.0 && PyErr_Occurred()) @@ -947,7 +948,7 @@ math_1(PyObject *arg, double (*func) (double), int can_overflow, return PyFloat_FromDouble(r); domain_err: - PyObject* a = PyFloat_FromDouble(x); + a = PyFloat_FromDouble(x); if (a) { PyErr_Format(PyExc_ValueError, From a917cefe2b708416f441df83bc15c98b4c5f6b73 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 22 Sep 2024 15:53:18 +0300 Subject: [PATCH 03/11] address review: * use PyOS_double_to_string * drop loghelper arg * don't print argument value in PyLong-branch of loghelper() --- Lib/test/test_math.py | 6 ++++++ Modules/mathmodule.c | 29 +++++++++++++++-------------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/Lib/test/test_math.py b/Lib/test/test_math.py index ebcf34045e602d..8441523bd48fff 100644 --- a/Lib/test/test_math.py +++ b/Lib/test/test_math.py @@ -2535,6 +2535,12 @@ def test_exception_messages(self): msg=f"expected a positive input, got {float(x)!r}"): math.log(x) + x = -2**1000 + + with self.assertRaises(ValueError, + msg=f"expected a positive input"): + math.log(x) + x = 1.0 with self.assertRaises(ValueError, diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index 665b02e70202cf..93d313b79b0b3e 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -924,7 +924,7 @@ static PyObject * math_1(PyObject *arg, double (*func) (double), int can_overflow, const char *err_msg) { - PyObject *a; + char *buf; double x, r; x = PyFloat_AsDouble(arg); if (x == -1.0 && PyErr_Occurred()) @@ -948,12 +948,12 @@ math_1(PyObject *arg, double (*func) (double), int can_overflow, return PyFloat_FromDouble(r); domain_err: - a = PyFloat_FromDouble(x); + buf = PyOS_double_to_string(x, 'r', 0, Py_DTSF_ADD_DOT_0, NULL); - if (a) { + if (buf) { PyErr_Format(PyExc_ValueError, - err_msg ? err_msg : "math domain error", a); - Py_DECREF(a); + err_msg ? err_msg : "math domain error", buf); + PyMem_Free(buf); } return NULL; } @@ -1087,7 +1087,7 @@ FUNC2(atan2, atan2, FUNC1D(atanh, atanh, 0, "atanh($module, x, /)\n--\n\n" "Return the inverse hyperbolic tangent of x.", - "expected a number between -1 and 1, got %R") + "expected a number between -1 and 1, got %s") FUNC1(cbrt, cbrt, 0, "cbrt($module, x, /)\n--\n\n" "Return the cube root of x.") @@ -1223,7 +1223,7 @@ FUNC1(sinh, sinh, 1, FUNC1D(sqrt, sqrt, 0, "sqrt($module, x, /)\n--\n\n" "Return the square root of x.", - "expected a nonnegative input, got %R") + "expected a nonnegative input, got %s") FUNC1(tan, tan, 0, "tan($module, x, /)\n--\n\n" "Return the tangent of x (measured in radians).") @@ -2196,7 +2196,7 @@ math_modf_impl(PyObject *module, double x) in that int is larger than PY_SSIZE_T_MAX. */ static PyObject* -loghelper(PyObject* arg, double (*func)(double), const char *err_msg) +loghelper(PyObject* arg, double (*func)(double)) { /* If it is int, do it ourselves. */ if (PyLong_Check(arg)) { @@ -2205,7 +2205,8 @@ loghelper(PyObject* arg, double (*func)(double), const char *err_msg) /* Negative or zero inputs give a ValueError. */ if (!_PyLong_IsPositive((PyLongObject *)arg)) { - PyErr_Format(PyExc_ValueError, err_msg, arg); + PyErr_SetString(PyExc_ValueError, + "expected a positive input"); return NULL; } @@ -2229,7 +2230,7 @@ loghelper(PyObject* arg, double (*func)(double), const char *err_msg) } /* Else let libm handle it by itself. */ - return math_1(arg, func, 0, err_msg); + return math_1(arg, func, 0, "expected a positive input, got %s"); } @@ -2244,11 +2245,11 @@ math_log(PyObject *module, PyObject * const *args, Py_ssize_t nargs) if (!_PyArg_CheckPositional("log", nargs, 1, 2)) return NULL; - num = loghelper(args[0], m_log, "expected a positive input, got %R"); + num = loghelper(args[0], m_log); if (num == NULL || nargs == 1) return num; - den = loghelper(args[1], m_log, "expected a positive input, got %R"); + den = loghelper(args[1], m_log); if (den == NULL) { Py_DECREF(num); return NULL; @@ -2278,7 +2279,7 @@ static PyObject * math_log2(PyObject *module, PyObject *x) /*[clinic end generated code: output=5425899a4d5d6acb input=08321262bae4f39b]*/ { - return loghelper(x, m_log2, "expected a positive input, got %R"); + return loghelper(x, m_log2); } @@ -2295,7 +2296,7 @@ static PyObject * math_log10(PyObject *module, PyObject *x) /*[clinic end generated code: output=be72a64617df9c6f input=b2469d02c6469e53]*/ { - return loghelper(x, m_log10, "expected a positive input, got %R"); + return loghelper(x, m_log10); } From e8f49b0f6a1cb2831d6d89c8dc7ee78e6857b095 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 22 Sep 2024 18:07:38 +0300 Subject: [PATCH 04/11] address review: tests (few more, reformatting, etc) --- Lib/test/test_math.py | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/Lib/test/test_math.py b/Lib/test/test_math.py index 8441523bd48fff..5dee4653cca788 100644 --- a/Lib/test/test_math.py +++ b/Lib/test/test_math.py @@ -2505,44 +2505,37 @@ def test_input_exceptions(self): def test_exception_messages(self): x = -1.1 - with self.assertRaises(ValueError, msg=f"expected a nonnegative input, got {x}"): math.sqrt(x) - with self.assertRaises(ValueError, msg=f"expected a positive input, got {x}"): math.log(x) with self.assertRaises(ValueError, msg=f"expected a positive input, got {x}"): math.log(123, x) + with self.assertRaises(ValueError, + msg=f"expected a positive input, got {x}"): + math.log(x, 123) with self.assertRaises(ValueError, msg=f"expected a positive input, got {x}"): math.log2(x) with self.assertRaises(ValueError, msg=f"expected a positive input, got {x}"): math.log2(x) - - x = decimal.Decimal(x) - + x = decimal.Decimal('-1.1') with self.assertRaises(ValueError, msg=f"expected a positive input, got {x!r}"): math.log(x) - x = fractions.Fraction(1, 10**400) - with self.assertRaises(ValueError, msg=f"expected a positive input, got {float(x)!r}"): math.log(x) - - x = -2**1000 - + x = -123 with self.assertRaises(ValueError, msg=f"expected a positive input"): math.log(x) - x = 1.0 - with self.assertRaises(ValueError, msg=f"expected a number between -1 and 1, got {x}"): math.atanh(x) From 0bdbc72260137044820678832d8ef1193db06543 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 23 Sep 2024 05:47:09 +0300 Subject: [PATCH 05/11] address review: fix typo + cleanup --- Lib/test/test_math.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_math.py b/Lib/test/test_math.py index 5dee4653cca788..8ec643e4639da6 100644 --- a/Lib/test/test_math.py +++ b/Lib/test/test_math.py @@ -2522,14 +2522,14 @@ def test_exception_messages(self): math.log2(x) with self.assertRaises(ValueError, msg=f"expected a positive input, got {x}"): - math.log2(x) + math.log10(x) x = decimal.Decimal('-1.1') with self.assertRaises(ValueError, msg=f"expected a positive input, got {x!r}"): math.log(x) x = fractions.Fraction(1, 10**400) with self.assertRaises(ValueError, - msg=f"expected a positive input, got {float(x)!r}"): + msg=f"expected a positive input, got {float(x)}"): math.log(x) x = -123 with self.assertRaises(ValueError, From e9bf987cce75b136e7551c0cafe4ab80b0ca9839 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 30 Sep 2024 04:42:07 +0300 Subject: [PATCH 06/11] address review: assertRaises -> assertRaisesRegex --- Lib/test/test_math.py | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/Lib/test/test_math.py b/Lib/test/test_math.py index 8ec643e4639da6..acc0a5ffb07fa6 100644 --- a/Lib/test/test_math.py +++ b/Lib/test/test_math.py @@ -2505,39 +2505,39 @@ def test_input_exceptions(self): def test_exception_messages(self): x = -1.1 - with self.assertRaises(ValueError, - msg=f"expected a nonnegative input, got {x}"): + with self.assertRaisesRegex(ValueError, + f"expected a nonnegative input, got {x}"): math.sqrt(x) - with self.assertRaises(ValueError, - msg=f"expected a positive input, got {x}"): + with self.assertRaisesRegex(ValueError, + f"expected a positive input, got {x}"): math.log(x) - with self.assertRaises(ValueError, - msg=f"expected a positive input, got {x}"): + with self.assertRaisesRegex(ValueError, + f"expected a positive input, got {x}"): math.log(123, x) - with self.assertRaises(ValueError, - msg=f"expected a positive input, got {x}"): + with self.assertRaisesRegex(ValueError, + f"expected a positive input, got {x}"): math.log(x, 123) - with self.assertRaises(ValueError, - msg=f"expected a positive input, got {x}"): + with self.assertRaisesRegex(ValueError, + f"expected a positive input, got {x}"): math.log2(x) - with self.assertRaises(ValueError, - msg=f"expected a positive input, got {x}"): + with self.assertRaisesRegex(ValueError, + f"expected a positive input, got {x}"): math.log10(x) x = decimal.Decimal('-1.1') - with self.assertRaises(ValueError, - msg=f"expected a positive input, got {x!r}"): + with self.assertRaisesRegex(ValueError, + f"expected a positive input, got {x}"): math.log(x) x = fractions.Fraction(1, 10**400) - with self.assertRaises(ValueError, - msg=f"expected a positive input, got {float(x)}"): + with self.assertRaisesRegex(ValueError, + f"expected a positive input, got {float(x)}"): math.log(x) x = -123 - with self.assertRaises(ValueError, - msg=f"expected a positive input"): + with self.assertRaisesRegex(ValueError, + f"expected a positive input"): math.log(x) x = 1.0 - with self.assertRaises(ValueError, - msg=f"expected a number between -1 and 1, got {x}"): + with self.assertRaisesRegex(ValueError, + f"expected a number between -1 and 1, got {x}"): math.atanh(x) # Custom assertions. From 55396f9c4e88a919e6ecfef77aafc3834f0d6adc Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 5 Oct 2024 12:48:26 +0300 Subject: [PATCH 07/11] Added support for error messages in math_1a(); gamma() as an example --- Lib/test/test_math.py | 3 +++ Modules/mathmodule.c | 44 ++++++++++++++++++++++++++++++------------- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/Lib/test/test_math.py b/Lib/test/test_math.py index acc0a5ffb07fa6..9c9b873c755bc0 100644 --- a/Lib/test/test_math.py +++ b/Lib/test/test_math.py @@ -2535,6 +2535,9 @@ def test_exception_messages(self): with self.assertRaisesRegex(ValueError, f"expected a positive input"): math.log(x) + with self.assertRaisesRegex(ValueError, + f"expected a float or nonnegative integer, got {x}"): + math.gamma(x) x = 1.0 with self.assertRaisesRegex(ValueError, f"expected a number between -1 and 1, got {x}"): diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index 96cfa4f957db31..54a51300270ae3 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -851,12 +851,15 @@ PyDoc_STRVAR(math_lcm_doc, * true (1), but may return false (0) without setting up an exception. */ static int -is_error(double x) +is_error(double x, int raise_edom) { int result = 1; /* presumption of guilt */ assert(errno); /* non-zero errno is a precondition for calling */ - if (errno == EDOM) - PyErr_SetString(PyExc_ValueError, "math domain error"); + if (errno == EDOM) { + if (raise_edom) { + PyErr_Format(PyExc_ValueError, "math domain error"); + } + } else if (errno == ERANGE) { /* ANSI C generally requires libm functions to set ERANGE @@ -941,7 +944,7 @@ math_1(PyObject *arg, double (*func) (double), int can_overflow, goto domain_err; /* singularity */ return NULL; } - if (isfinite(r) && errno && is_error(r)) + if (isfinite(r) && errno && is_error(r, 1)) /* this branch unnecessary on most platforms */ return NULL; @@ -963,7 +966,7 @@ math_1(PyObject *arg, double (*func) (double), int can_overflow, errno = ERANGE for overflow). */ static PyObject * -math_1a(PyObject *arg, double (*func) (double)) +math_1a(PyObject *arg, double (*func) (double), const char *err_msg) { double x, r; x = PyFloat_AsDouble(arg); @@ -971,8 +974,16 @@ math_1a(PyObject *arg, double (*func) (double)) return NULL; errno = 0; r = (*func)(x); - if (errno && is_error(r)) + if (errno && is_error(r, err_msg ? 0 : 1)) { + if (err_msg && errno == EDOM) { + char *buf = PyOS_double_to_string(x, 'r', 0, Py_DTSF_ADD_DOT_0, NULL); + if (buf) { + PyErr_Format(PyExc_ValueError, err_msg, buf); + PyMem_Free(buf); + } + } return NULL; + } return PyFloat_FromDouble(r); } @@ -1032,7 +1043,7 @@ math_2(PyObject *const *args, Py_ssize_t nargs, else errno = 0; } - if (errno && is_error(r)) + if (errno && is_error(r, 1)) return NULL; else return PyFloat_FromDouble(r); @@ -1052,7 +1063,13 @@ math_2(PyObject *const *args, Py_ssize_t nargs, #define FUNC1A(funcname, func, docstring) \ static PyObject * math_##funcname(PyObject *self, PyObject *args) { \ - return math_1a(args, func); \ + return math_1a(args, func, NULL); \ + }\ + PyDoc_STRVAR(math_##funcname##_doc, docstring); + +#define FUNC1AD(funcname, func, docstring, err_msg) \ + static PyObject * math_##funcname(PyObject *self, PyObject *args) { \ + return math_1a(args, func, err_msg); \ }\ PyDoc_STRVAR(math_##funcname##_doc, docstring); @@ -1198,9 +1215,10 @@ math_floor(PyObject *module, PyObject *number) return PyLong_FromDouble(floor(x)); } -FUNC1A(gamma, m_tgamma, +FUNC1AD(gamma, m_tgamma, "gamma($module, x, /)\n--\n\n" - "Gamma function at x.") + "Gamma function at x.", + "expected a float or nonnegative integer, got %s") FUNC1A(lgamma, m_lgamma, "lgamma($module, x, /)\n--\n\n" "Natural logarithm of absolute value of Gamma function at x.") @@ -2150,7 +2168,7 @@ math_ldexp_impl(PyObject *module, double x, PyObject *i) errno = ERANGE; } - if (errno && is_error(r)) + if (errno && is_error(r, 1)) return NULL; return PyFloat_FromDouble(r); } @@ -2378,7 +2396,7 @@ math_fmod_impl(PyObject *module, double x, double y) else errno = 0; } - if (errno && is_error(r)) + if (errno && is_error(r, 1)) return NULL; else return PyFloat_FromDouble(r); @@ -3015,7 +3033,7 @@ math_pow_impl(PyObject *module, double x, double y) } } - if (errno && is_error(r)) + if (errno && is_error(r, 1)) return NULL; else return PyFloat_FromDouble(r); From 4905f1b9004f2281b63fac8cdc533b3811ff50e1 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 5 Oct 2024 15:03:47 +0300 Subject: [PATCH 08/11] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Modules/mathmodule.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index 54a51300270ae3..8b921120c37406 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -857,7 +857,7 @@ is_error(double x, int raise_edom) assert(errno); /* non-zero errno is a precondition for calling */ if (errno == EDOM) { if (raise_edom) { - PyErr_Format(PyExc_ValueError, "math domain error"); + PyErr_SetString(PyExc_ValueError, "math domain error"); } } @@ -927,7 +927,6 @@ static PyObject * math_1(PyObject *arg, double (*func) (double), int can_overflow, const char *err_msg) { - char *buf; double x, r; x = PyFloat_AsDouble(arg); if (x == -1.0 && PyErr_Occurred()) @@ -951,13 +950,16 @@ math_1(PyObject *arg, double (*func) (double), int can_overflow, return PyFloat_FromDouble(r); domain_err: - buf = PyOS_double_to_string(x, 'r', 0, Py_DTSF_ADD_DOT_0, NULL); - - if (buf) { - PyErr_Format(PyExc_ValueError, - err_msg ? err_msg : "math domain error", buf); - PyMem_Free(buf); - } + if (err_msg) { + char *buf = PyOS_double_to_string(x, 'r', 0, Py_DTSF_ADD_DOT_0, NULL); + if (buf) { + PyErr_Format(PyExc_ValueError, err_msg, buf); + PyMem_Free(buf); + } + } + else { + PyErr_SetString(PyExc_ValueError, "math domain error"); + } return NULL; } From 96e70f4edb0dff05674615e5969d4332750885b6 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 5 Oct 2024 16:01:28 +0300 Subject: [PATCH 09/11] Update Modules/mathmodule.c --- Modules/mathmodule.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index 8b921120c37406..8cf5ae836c46b0 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -978,6 +978,7 @@ math_1a(PyObject *arg, double (*func) (double), const char *err_msg) r = (*func)(x); if (errno && is_error(r, err_msg ? 0 : 1)) { if (err_msg && errno == EDOM) { + assert(!PyErr_Occurred()); /* exception is not set by is_error() */ char *buf = PyOS_double_to_string(x, 'r', 0, Py_DTSF_ADD_DOT_0, NULL); if (buf) { PyErr_Format(PyExc_ValueError, err_msg, buf); From 169815d6a35a444bd07e3f608b6653672941009d Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 23 Jan 2025 14:38:39 +0300 Subject: [PATCH 10/11] address review: tabs in math_1 --- Modules/mathmodule.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index 6530c4865a953d..cdbdccacf0865e 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -960,8 +960,8 @@ math_1(PyObject *arg, double (*func) (double), int can_overflow, if (err_msg) { char *buf = PyOS_double_to_string(x, 'r', 0, Py_DTSF_ADD_DOT_0, NULL); if (buf) { - PyErr_Format(PyExc_ValueError, err_msg, buf); - PyMem_Free(buf); + PyErr_Format(PyExc_ValueError, err_msg, buf); + PyMem_Free(buf); } } else { From 8d28fd8cba178d35efcea743d05e4e2d90f523ea Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 23 Jan 2025 16:16:29 +0300 Subject: [PATCH 11/11] address review: improve message in loghelper() --- Lib/test/test_math.py | 2 +- Modules/mathmodule.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_math.py b/Lib/test/test_math.py index 05ca591ff3a5ae..2c57d288bc03ff 100644 --- a/Lib/test/test_math.py +++ b/Lib/test/test_math.py @@ -2533,7 +2533,7 @@ def test_exception_messages(self): math.log(x) x = -123 with self.assertRaisesRegex(ValueError, - f"expected a positive input"): + f"expected a positive input, got {x}"): math.log(x) with self.assertRaisesRegex(ValueError, f"expected a float or nonnegative integer, got {x}"): diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index cdbdccacf0865e..b4c15a143f9838 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -2232,8 +2232,8 @@ loghelper(PyObject* arg, double (*func)(double)) /* Negative or zero inputs give a ValueError. */ if (!_PyLong_IsPositive((PyLongObject *)arg)) { - PyErr_SetString(PyExc_ValueError, - "expected a positive input"); + PyErr_Format(PyExc_ValueError, + "expected a positive input, got %S", arg); return NULL; }