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]

Reply via email to