Skip to content

gh-120950: Fix overflow in math.log() with large int-like argument #121011

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
33 changes: 33 additions & 0 deletions Lib/test/test_math.py
Original file line number Diff line number Diff line change
Expand Up @@ -1208,8 +1208,23 @@ def testLog(self):
self.ftest('log(10**40, 10**20)', math.log(10**40, 10**20), 2)
self.ftest('log(10**1000)', math.log(10**1000),
2302.5850929940457)
self.ftest('log(10**2000, 10**1000)', math.log(10**2000, 10**1000), 2)
self.ftest('log(MyIndexable(32), MyIndexable(2))',
math.log(MyIndexable(32), MyIndexable(2)), 5)
self.ftest('log(MyIndexable(10**1000))',
math.log(MyIndexable(10**1000)),
2302.5850929940457)
self.ftest('log(MyIndexable(10**2000), MyIndexable(10**1000))',
math.log(MyIndexable(10**2000), MyIndexable(10**1000)),
2)
self.assertRaises(ValueError, math.log, 0.0)
self.assertRaises(ValueError, math.log, 0)
self.assertRaises(ValueError, math.log, MyIndexable(0))
self.assertRaises(ValueError, math.log, -1.5)
self.assertRaises(ValueError, math.log, -1)
self.assertRaises(ValueError, math.log, MyIndexable(-1))
self.assertRaises(ValueError, math.log, -10**1000)
self.assertRaises(ValueError, math.log, MyIndexable(-10**1000))
self.assertRaises(ValueError, math.log, 10, -10)
self.assertRaises(ValueError, math.log, NINF)
self.assertEqual(math.log(INF), INF)
Expand All @@ -1230,13 +1245,22 @@ def testLog2(self):
self.assertEqual(math.log2(1), 0.0)
self.assertEqual(math.log2(2), 1.0)
self.assertEqual(math.log2(4), 2.0)
self.assertEqual(math.log2(MyIndexable(4)), 2.0)

# Large integer values
self.assertEqual(math.log2(2**1023), 1023.0)
self.assertEqual(math.log2(2**1024), 1024.0)
self.assertEqual(math.log2(2**2000), 2000.0)
self.assertEqual(math.log2(MyIndexable(2**2000)), 2000.0)

self.assertRaises(ValueError, math.log2, 0.0)
self.assertRaises(ValueError, math.log2, 0)
self.assertRaises(ValueError, math.log2, MyIndexable(0))
self.assertRaises(ValueError, math.log2, -1.5)
self.assertRaises(ValueError, math.log2, -1)
self.assertRaises(ValueError, math.log2, MyIndexable(-1))
self.assertRaises(ValueError, math.log2, -2**2000)
self.assertRaises(ValueError, math.log2, MyIndexable(-2**2000))
self.assertRaises(ValueError, math.log2, NINF)
self.assertTrue(math.isnan(math.log2(NAN)))

Expand All @@ -1255,8 +1279,17 @@ def testLog10(self):
self.ftest('log10(1)', math.log10(1), 0)
self.ftest('log10(10)', math.log10(10), 1)
self.ftest('log10(10**1000)', math.log10(10**1000), 1000.0)
self.ftest('log10(MyIndexable(10))', math.log10(MyIndexable(10)), 1)
self.ftest('log10(MyIndexable(10**1000))',
math.log10(MyIndexable(10**1000)), 1000.0)
self.assertRaises(ValueError, math.log10, 0.0)
self.assertRaises(ValueError, math.log10, 0)
self.assertRaises(ValueError, math.log10, MyIndexable(0))
self.assertRaises(ValueError, math.log10, -1.5)
self.assertRaises(ValueError, math.log10, -1)
self.assertRaises(ValueError, math.log10, MyIndexable(-1))
self.assertRaises(ValueError, math.log10, -10**1000)
self.assertRaises(ValueError, math.log10, MyIndexable(-10**1000))
self.assertRaises(ValueError, math.log10, NINF)
self.assertEqual(math.log(INF), INF)
self.assertTrue(math.isnan(math.log10(NAN)))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
:func:`math.log` now supports arbitrary large integer-like arguments in the
same way as arbitrary large integer arguments.
78 changes: 50 additions & 28 deletions Modules/mathmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -2217,41 +2217,63 @@ 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_int(PyObject* arg, double (*func)(double))
{
/* If it is int, do it ourselves. */
if (PyLong_Check(arg)) {
double x, result;
Py_ssize_t e;
double x, result;
Py_ssize_t e;

/* Negative or zero inputs give a ValueError. */
if (!_PyLong_IsPositive((PyLongObject *)arg)) {
PyErr_SetString(PyExc_ValueError,
"math domain error");
return NULL;
}
/* Negative or zero inputs give a ValueError. */
if (!_PyLong_IsPositive((PyLongObject *)arg)) {
PyErr_SetString(PyExc_ValueError,
"math domain error");
return NULL;
}

x = PyLong_AsDouble(arg);
if (x == -1.0 && PyErr_Occurred()) {
if (!PyErr_ExceptionMatches(PyExc_OverflowError))
return NULL;
/* Here the conversion to double overflowed, but it's possible
to compute the log anyway. Clear the exception and continue. */
PyErr_Clear();
x = _PyLong_Frexp((PyLongObject *)arg, &e);
if (x == -1.0 && PyErr_Occurred())
return NULL;
/* Value is ~= x * 2**e, so the log ~= log(x) + log(2) * e. */
result = func(x) + func(2.0) * e;
}
else
/* Successfully converted x to a double. */
result = func(x);
return PyFloat_FromDouble(result);
x = PyLong_AsDouble(arg);
if (x == -1.0 && PyErr_Occurred()) {
if (!PyErr_ExceptionMatches(PyExc_OverflowError))
return NULL;
/* Here the conversion to double overflowed, but it's possible
to compute the log anyway. Clear the exception and continue. */
PyErr_Clear();
x = _PyLong_Frexp((PyLongObject *)arg, &e);
if (x == -1.0 && PyErr_Occurred())
return NULL;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (x == -1.0 && PyErr_Occurred())
return NULL;

Now _PyLong_Frexp() not raises.

/* Value is ~= x * 2**e, so the log ~= log(x) + log(2) * e. */
result = func(x) + func(2.0) * e;
}
else
/* Successfully converted x to a double. */
result = func(x);
return PyFloat_FromDouble(result);
}

static PyObject*
loghelper(PyObject* arg, double (*func)(double))
{
/* If it is int, do it ourselves. */
if (PyLong_Check(arg)) {
return loghelper_int(arg, func);
}
/* Else let libm handle it by itself. */
return math_1(arg, func, 0);
PyObject *res = math_1(arg, func, 0);
if (res == NULL &&
PyErr_ExceptionMatches(PyExc_OverflowError) &&
PyIndex_Check(arg))
{
/* Here the conversion to double overflowed, but it's possible
to compute the log anyway. Clear the exception, convert to
integer and continue. */
PyErr_Clear();
arg = _PyNumber_Index(arg);
if (arg == NULL) {
return NULL;
}
res = loghelper_int(arg, func);
Py_DECREF(arg);
}
return res;
}


Expand Down
Loading