https://github.com/python/cpython/commit/4359706ac8d5589fc37e2f1460a0d07a2319df15
commit: 4359706ac8d5589fc37e2f1460a0d07a2319df15
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2025-11-12T00:27:13+02:00
summary:
gh-120950: Fix overflow in math.log() with large int-like argument (GH-121011)
Handling of arbitrary large int-like argument is now consistent with
handling arbitrary large int arguments.
files:
A Misc/NEWS.d/next/Library/2024-06-26-16-16-43.gh-issue-121011.qW54eh.rst
M Lib/test/test_math.py
M Modules/mathmodule.c
diff --git a/Lib/test/test_math.py b/Lib/test/test_math.py
index ddeb8ad7cd62f3..68f41a2e62034d 100644
--- a/Lib/test/test_math.py
+++ b/Lib/test/test_math.py
@@ -189,6 +189,22 @@ def __init__(self, value):
def __index__(self):
return self.value
+class IndexableFloatLike:
+ def __init__(self, float_value, index_value):
+ self.float_value = float_value
+ self.index_value = index_value
+
+ def __float__(self):
+ if isinstance(self.float_value, BaseException):
+ raise self.float_value
+ return self.float_value
+
+ def __index__(self):
+ if isinstance(self.index_value, BaseException):
+ raise self.index_value
+ return self.index_value
+
+
class BadDescr:
def __get__(self, obj, objtype=None):
raise ValueError
@@ -1192,13 +1208,32 @@ 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)
self.assertTrue(math.isnan(math.log(NAN)))
+ self.assertEqual(math.log(IndexableFloatLike(math.e, 10**1000)), 1.0)
+ self.assertAlmostEqual(math.log(IndexableFloatLike(OverflowError(),
10**1000)),
+ 2302.5850929940457)
+
def testLog1p(self):
self.assertRaises(TypeError, math.log1p)
for n in [2, 2**90, 2**300]:
@@ -1214,16 +1249,28 @@ 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)))
+ self.assertEqual(math.log2(IndexableFloatLike(8.0, 2**2000)), 3.0)
+ self.assertEqual(math.log2(IndexableFloatLike(OverflowError(),
2**2000)), 2000.0)
+
@requires_IEEE_754
# log2() is not accurate enough on Mac OS X Tiger (10.4)
@support.requires_mac_ver(10, 5)
@@ -1239,12 +1286,24 @@ 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)))
+ self.assertEqual(math.log10(IndexableFloatLike(100.0, 10**1000)), 2.0)
+ self.assertEqual(math.log10(IndexableFloatLike(OverflowError(),
10**1000)), 1000.0)
+
@support.bigmemtest(2**32, memuse=0.2)
def test_log_huge_integer(self, size):
v = 1 << size
diff --git
a/Misc/NEWS.d/next/Library/2024-06-26-16-16-43.gh-issue-121011.qW54eh.rst
b/Misc/NEWS.d/next/Library/2024-06-26-16-16-43.gh-issue-121011.qW54eh.rst
new file mode 100644
index 00000000000000..aee7fe2bcb5c60
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-06-26-16-16-43.gh-issue-121011.qW54eh.rst
@@ -0,0 +1,2 @@
+:func:`math.log` now supports arbitrary large integer-like arguments in the
+same way as arbitrary large integer arguments.
diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c
index 82846843cfb0b2..de1886451eda8f 100644
--- a/Modules/mathmodule.c
+++ b/Modules/mathmodule.c
@@ -57,6 +57,7 @@ raised for division by zero and mod by zero.
#endif
#include "Python.h"
+#include "pycore_abstract.h" // _PyNumber_Index()
#include "pycore_bitutils.h" // _Py_bit_length()
#include "pycore_call.h" // _PyObject_CallNoArgs()
#include "pycore_import.h" // _PyImport_SetModuleString()
@@ -1578,43 +1579,62 @@ 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;
- int64_t e;
+ double x, result;
+ int64_t e;
- /* Negative or zero inputs give a ValueError. */
- if (!_PyLong_IsPositive((PyLongObject *)arg)) {
- /* The input can be an arbitrary large integer, so we
- don't include it's value in the error message. */
- PyErr_SetString(PyExc_ValueError,
- "expected a positive input");
- return NULL;
- }
+ /* Negative or zero inputs give a ValueError. */
+ if (!_PyLong_IsPositive((PyLongObject *)arg)) {
+ PyErr_SetString(PyExc_ValueError,
+ "expected a positive input");
+ 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);
- assert(e >= 0);
- assert(!PyErr_Occurred());
- /* Value is ~= x * 2**e, so the log ~= log(x) + log(2) * e. */
- result = fma(func(2.0), (double)e, func(x));
- }
- 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);
+ assert(!PyErr_Occurred());
+ /* Value is ~= x * 2**e, so the log ~= log(x) + log(2) * e. */
+ result = fma(func(2.0), (double)e, func(x));
}
+ 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, "expected a positive input, got %s");
+ PyObject *res = math_1(arg, func, 0, "expected a positive input, got %s");
+ 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;
}
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]