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..3619b2b9dd4b5b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-02-01-16-41-31.gh-issue-101410.Dt2aQE.rst @@ -0,0 +1,2 @@ +Make several math functions support custom error messages. :func:`math.sqrt` and :func:`math.log` +now provide more helpful error messages. diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index baf2dc439b8959..2a5c666c5e13c2 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -921,7 +921,7 @@ 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); @@ -930,8 +930,7 @@ math_1(PyObject *arg, double (*func) (double), int can_overflow) errno = 0; r = (*func)(x); if (isnan(r) && !isnan(x)) { - PyErr_SetString(PyExc_ValueError, - "math domain error"); /* invalid arg */ + PyErr_Format(PyExc_ValueError, err_msg, arg); /* invalid arg */ return NULL; } if (isinf(r) && isfinite(x)) { @@ -1030,9 +1029,9 @@ math_2(PyObject *const *args, Py_ssize_t nargs, return PyFloat_FromDouble(r); } -#define FUNC1(funcname, func, can_overflow, docstring) \ +#define FUNC1(funcname, func, can_overflow, docstring, err_msg) \ static PyObject * math_##funcname(PyObject *self, PyObject *args) { \ - return math_1(args, func, can_overflow); \ + return math_1(args, func, can_overflow, err_msg); \ }\ PyDoc_STRVAR(math_##funcname##_doc, docstring); @@ -1051,31 +1050,38 @@ math_2(PyObject *const *args, Py_ssize_t nargs, FUNC1(acos, acos, 0, "acos($module, x, /)\n--\n\n" "Return the arc cosine (measured in radians) of x.\n\n" - "The result is between 0 and pi.") + "The result is between 0 and pi.", + "math domain error") FUNC1(acosh, acosh, 0, "acosh($module, x, /)\n--\n\n" - "Return the inverse hyperbolic cosine of x.") + "Return the inverse hyperbolic cosine of x.", + "math domain error") FUNC1(asin, asin, 0, "asin($module, x, /)\n--\n\n" "Return the arc sine (measured in radians) of x.\n\n" - "The result is between -pi/2 and pi/2.") + "The result is between -pi/2 and pi/2.", + "math domain error") FUNC1(asinh, asinh, 0, "asinh($module, x, /)\n--\n\n" - "Return the inverse hyperbolic sine of x.") + "Return the inverse hyperbolic sine of x.", + "math domain error") FUNC1(atan, atan, 0, "atan($module, x, /)\n--\n\n" "Return the arc tangent (measured in radians) of x.\n\n" - "The result is between -pi/2 and pi/2.") + "The result is between -pi/2 and pi/2.", + "math domain error") 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, "atanh($module, x, /)\n--\n\n" - "Return the inverse hyperbolic tangent of x.") + "Return the inverse hyperbolic tangent of x.", + "math domain error") FUNC1(cbrt, cbrt, 0, "cbrt($module, x, /)\n--\n\n" - "Return the cube root of x.") + "Return the cube root of x.", + "math domain error") /*[clinic input] math.ceil @@ -1121,10 +1127,12 @@ FUNC2(copysign, copysign, "returns -1.0.\n") FUNC1(cos, cos, 0, "cos($module, x, /)\n--\n\n" - "Return the cosine of x (measured in radians).") + "Return the cosine of x (measured in radians).", + "math domain error") FUNC1(cosh, cosh, 1, "cosh($module, x, /)\n--\n\n" - "Return the hyperbolic cosine of x.") + "Return the hyperbolic cosine of x.", + "math domain error") FUNC1A(erf, erf, "erf($module, x, /)\n--\n\n" "Error function at x.") @@ -1133,18 +1141,22 @@ FUNC1A(erfc, erfc, "Complementary error function at x.") FUNC1(exp, exp, 1, "exp($module, x, /)\n--\n\n" - "Return e raised to the power of x.") + "Return e raised to the power of x.", + "math domain error") FUNC1(exp2, exp2, 1, "exp2($module, x, /)\n--\n\n" - "Return 2 raised to the power of x.") + "Return 2 raised to the power of x.", + "math domain error") FUNC1(expm1, expm1, 1, "expm1($module, x, /)\n--\n\n" "Return exp(x)-1.\n\n" "This function avoids the loss of precision involved in the direct " - "evaluation of exp(x)-1 for small x.") + "evaluation of exp(x)-1 for small x.", + "math domain error") FUNC1(fabs, fabs, 0, "fabs($module, x, /)\n--\n\n" - "Return the absolute value of the float x.") + "Return the absolute value of the float x.", + "math domain error") /*[clinic input] math.floor @@ -1192,7 +1204,8 @@ FUNC1A(lgamma, m_lgamma, FUNC1(log1p, m_log1p, 0, "log1p($module, x, /)\n--\n\n" "Return the natural logarithm of 1+x (base e).\n\n" - "The result is computed in a way which is accurate for x near zero.") + "The result is computed in a way which is accurate for x near zero.", + "math domain error") FUNC2(remainder, m_remainder, "remainder($module, x, y, /)\n--\n\n" "Difference between x and the closest integer multiple of y.\n\n" @@ -1201,19 +1214,25 @@ FUNC2(remainder, m_remainder, "y, the nearest even value of n is used. The result is always exact.") FUNC1(sin, sin, 0, "sin($module, x, /)\n--\n\n" - "Return the sine of x (measured in radians).") + "Return the sine of x (measured in radians).", + "math domain error") FUNC1(sinh, sinh, 1, "sinh($module, x, /)\n--\n\n" - "Return the hyperbolic sine of x.") + "Return the hyperbolic sine of x.", + "math domain error") FUNC1(sqrt, sqrt, 0, "sqrt($module, x, /)\n--\n\n" - "Return the square root of x.") + "Return the square root of x.", + "math.sqrt() expects a nonnegative input, got '%R'.\n" + "See cmath.sqrt() for a variant that supports complex numbers") FUNC1(tan, tan, 0, "tan($module, x, /)\n--\n\n" - "Return the tangent of x (measured in radians).") + "Return the tangent of x (measured in radians).", + "math domain error") FUNC1(tanh, tanh, 0, "tanh($module, x, /)\n--\n\n" - "Return the hyperbolic tangent of x.") + "Return the hyperbolic tangent of x.", + "math domain error") /* Precision summation function as msum() by Raymond Hettinger in , @@ -2180,7 +2199,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 +2208,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 +2232,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 +2247,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, "math.log() expects a positive input, got '%R'."); if (num == NULL || nargs == 1) return num; - den = loghelper(args[1], m_log); + den = loghelper(args[1], m_log, "math.log() expects a positive input, got '%R'."); if (den == NULL) { Py_DECREF(num); return NULL; @@ -2263,7 +2281,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, "math.log2() expects a positive input, got '%R'."); } @@ -2280,7 +2298,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, "math.log10() expects a positive input, got '%R'."); }