Skip to content

Commit e883928

Browse files
committed
gh-133895: Include computed result value in math and cmath error exceptions
1 parent 0eb448c commit e883928

File tree

3 files changed

+157
-43
lines changed

3 files changed

+157
-43
lines changed

Lib/test/test_math_errors.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import unittest
2+
import math
3+
import cmath
4+
5+
class TestMathErrors(unittest.TestCase):
6+
def test_math_value_error_with_value(self):
7+
# Test math.sqrt with negative number
8+
try:
9+
math.sqrt(-1)
10+
except ValueError as e:
11+
self.assertTrue(hasattr(e, 'value'))
12+
self.assertTrue(math.isnan(e.value))
13+
14+
# Test math.log with negative number
15+
try:
16+
math.log(-1)
17+
except ValueError as e:
18+
self.assertTrue(hasattr(e, 'value'))
19+
self.assertTrue(math.isnan(e.value))
20+
21+
# Test math.acos with value > 1
22+
try:
23+
math.acos(2)
24+
except ValueError as e:
25+
self.assertTrue(hasattr(e, 'value'))
26+
self.assertTrue(math.isnan(e.value))
27+
28+
def test_cmath_value_error_with_value(self):
29+
# Test cmath.sqrt with negative number
30+
try:
31+
cmath.sqrt(-1)
32+
except ValueError as e:
33+
self.assertTrue(hasattr(e, 'value'))
34+
self.assertTrue(isinstance(e.value, complex))
35+
self.assertTrue(cmath.isnan(e.value))
36+
37+
# Test cmath.log with negative number
38+
try:
39+
cmath.log(-1)
40+
except ValueError as e:
41+
self.assertTrue(hasattr(e, 'value'))
42+
self.assertTrue(isinstance(e.value, complex))
43+
self.assertTrue(cmath.isnan(e.value))
44+
45+
# Test cmath.acos with value > 1
46+
try:
47+
cmath.acos(2)
48+
except ValueError as e:
49+
self.assertTrue(hasattr(e, 'value'))
50+
self.assertTrue(isinstance(e.value, complex))
51+
self.assertTrue(cmath.isnan(e.value))
52+
53+
def test_math_overflow_error_with_value(self):
54+
# Test math.exp with very large number
55+
try:
56+
math.exp(1000)
57+
except OverflowError as e:
58+
self.assertTrue(hasattr(e, 'value'))
59+
self.assertTrue(math.isinf(e.value))
60+
61+
def test_cmath_overflow_error_with_value(self):
62+
# Test cmath.exp with very large number
63+
try:
64+
cmath.exp(1000)
65+
except OverflowError as e:
66+
self.assertTrue(hasattr(e, 'value'))
67+
self.assertTrue(isinstance(e.value, complex))
68+
self.assertTrue(cmath.isinf(e.value.real) or cmath.isinf(e.value.imag))
69+
70+
if __name__ == '__main__':
71+
unittest.main()

Modules/cmathmodule.c

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,23 @@ class Py_complex_protected_return_converter(CReturnConverter):
3535
self.declare(data)
3636
data.return_conversion.append("""
3737
if (errno == EDOM) {
38-
PyErr_SetString(PyExc_ValueError, "math domain error");
38+
PyObject *exc = PyErr_GetRaisedException();
39+
PyObject *value = PyComplex_FromCComplex(_return_value);
40+
if (value) {
41+
PyObject_SetAttrString(exc, "value", value);
42+
}
43+
Py_DECREF(value);
44+
PyErr_SetRaisedException(exc);
3945
goto exit;
4046
}
4147
else if (errno == ERANGE) {
42-
PyErr_SetString(PyExc_OverflowError, "math range error");
48+
PyObject *exc = PyErr_GetRaisedException();
49+
PyObject *value = PyComplex_FromCComplex(_return_value);
50+
if (value) {
51+
PyObject_SetAttrString(exc, "value", value);
52+
}
53+
Py_DECREF(value);
54+
PyErr_SetRaisedException(exc);
4355
goto exit;
4456
}
4557
else {
@@ -408,8 +420,8 @@ cmath_cosh_impl(PyObject *module, Py_complex z)
408420

409421
/* special treatment for cosh(+/-inf + iy) if y is not a NaN */
410422
if (!isfinite(z.real) || !isfinite(z.imag)) {
411-
if (isinf(z.real) && isfinite(z.imag) &&
412-
(z.imag != 0.)) {
423+
if (isinf(z.real) && isfinite(z.imag)
424+
&& (z.imag != 0.)) {
413425
if (z.real > 0) {
414426
r.real = copysign(INF, cos(z.imag));
415427
r.imag = copysign(INF, sin(z.imag));
@@ -899,10 +911,24 @@ cmath_log_impl(PyObject *module, Py_complex x, PyObject *y_obj)
899911
static PyObject *
900912
math_error(void)
901913
{
902-
if (errno == EDOM)
903-
PyErr_SetString(PyExc_ValueError, "math domain error");
904-
else if (errno == ERANGE)
905-
PyErr_SetString(PyExc_OverflowError, "math range error");
914+
if (errno == EDOM) {
915+
PyObject *exc = PyErr_GetRaisedException();
916+
PyObject *value = PyComplex_FromCComplex(_return_value);
917+
if (value) {
918+
PyObject_SetAttrString(exc, "value", value);
919+
}
920+
Py_DECREF(value);
921+
PyErr_SetRaisedException(exc);
922+
}
923+
else if (errno == ERANGE) {
924+
PyObject *exc = PyErr_GetRaisedException();
925+
PyObject *value = PyComplex_FromCComplex(_return_value);
926+
if (value) {
927+
PyObject_SetAttrString(exc, "value", value);
928+
}
929+
Py_DECREF(value);
930+
PyErr_SetRaisedException(exc);
931+
}
906932
else /* Unexpected math error */
907933
PyErr_SetFromErrno(PyExc_ValueError);
908934
return NULL;
@@ -1338,3 +1364,33 @@ PyInit_cmath(void)
13381364
{
13391365
return PyModuleDef_Init(&cmathmodule);
13401366
}
1367+
1368+
static int
1369+
Py_complex_protected_return_converter(PyObject *obj, void *ptr)
1370+
{
1371+
Py_complex *p = (Py_complex *)ptr;
1372+
if (PyComplex_Check(obj)) {
1373+
*p = PyComplex_AsCComplex(obj);
1374+
return 1;
1375+
}
1376+
if (PyFloat_Check(obj)) {
1377+
p->real = PyFloat_AsDouble(obj);
1378+
p->imag = 0.0;
1379+
return 1;
1380+
}
1381+
if (PyLong_Check(obj)) {
1382+
p->real = PyLong_AsDouble(obj);
1383+
p->imag = 0.0;
1384+
return 1;
1385+
}
1386+
if (PyErr_Occurred()) {
1387+
PyObject *exc = PyErr_GetRaisedException();
1388+
PyObject *value = PyComplex_FromCComplex(*p);
1389+
if (value) {
1390+
PyObject_SetAttrString(exc, "value", value);
1391+
}
1392+
Py_DECREF(value);
1393+
PyErr_SetRaisedException(exc);
1394+
}
1395+
return 0;
1396+
}

Modules/mathmodule.c

Lines changed: 22 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ module math
8383
Double and triple length extended precision algorithms from:
8484
8585
Accurate Sum and Dot Product
86-
by Takeshi Ogita, Siegfried M. Rump, and ShinIchi Oishi
86+
by Takeshi Ogita, Siegfried M. Rump, and Shin'Ichi Oishi
8787
https://doi.org/10.1137/030601818
8888
https://www.tuhh.de/ti3/paper/rump/OgRuOi05.pdf
8989
@@ -847,44 +847,31 @@ math_lcm_impl(PyObject *module, PyObject * const *args,
847847
static int
848848
is_error(double x, int raise_edom)
849849
{
850-
int result = 1; /* presumption of guilt */
851-
assert(errno); /* non-zero errno is a precondition for calling */
852-
if (errno == EDOM) {
850+
if (Py_IS_NAN(x)) {
853851
if (raise_edom) {
854-
PyErr_SetString(PyExc_ValueError, "math domain error");
852+
PyObject *exc = PyErr_GetRaisedException();
853+
PyObject *value = PyFloat_FromDouble(x);
854+
if (value) {
855+
PyObject_SetAttrString(exc, "value", value);
856+
}
857+
Py_DECREF(value);
858+
PyErr_SetRaisedException(exc);
855859
}
860+
return 1;
856861
}
857-
858-
else if (errno == ERANGE) {
859-
/* ANSI C generally requires libm functions to set ERANGE
860-
* on overflow, but also generally *allows* them to set
861-
* ERANGE on underflow too. There's no consistency about
862-
* the latter across platforms.
863-
* Alas, C99 never requires that errno be set.
864-
* Here we suppress the underflow errors (libm functions
865-
* should return a zero on underflow, and +- HUGE_VAL on
866-
* overflow, so testing the result for zero suffices to
867-
* distinguish the cases).
868-
*
869-
* On some platforms (Ubuntu/ia64) it seems that errno can be
870-
* set to ERANGE for subnormal results that do *not* underflow
871-
* to zero. So to be safe, we'll ignore ERANGE whenever the
872-
* function result is less than 1.5 in absolute value.
873-
*
874-
* bpo-46018: Changed to 1.5 to ensure underflows in expm1()
875-
* are correctly detected, since the function may underflow
876-
* toward -1.0 rather than 0.0.
877-
*/
878-
if (fabs(x) < 1.5)
879-
result = 0;
880-
else
881-
PyErr_SetString(PyExc_OverflowError,
882-
"math range error");
862+
if (Py_IS_INFINITY(x)) {
863+
if (raise_edom) {
864+
PyObject *exc = PyErr_GetRaisedException();
865+
PyObject *value = PyFloat_FromDouble(x);
866+
if (value) {
867+
PyObject_SetAttrString(exc, "value", value);
868+
}
869+
Py_DECREF(value);
870+
PyErr_SetRaisedException(exc);
871+
}
872+
return 1;
883873
}
884-
else
885-
/* Unexpected math error */
886-
PyErr_SetFromErrno(PyExc_ValueError);
887-
return result;
874+
return 0;
888875
}
889876

890877
/*

0 commit comments

Comments
 (0)