Skip to content

Commit 4359706

Browse files
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.
1 parent 9e7340c commit 4359706

File tree

3 files changed

+111
-30
lines changed

3 files changed

+111
-30
lines changed

Lib/test/test_math.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,22 @@ def __init__(self, value):
189189
def __index__(self):
190190
return self.value
191191

192+
class IndexableFloatLike:
193+
def __init__(self, float_value, index_value):
194+
self.float_value = float_value
195+
self.index_value = index_value
196+
197+
def __float__(self):
198+
if isinstance(self.float_value, BaseException):
199+
raise self.float_value
200+
return self.float_value
201+
202+
def __index__(self):
203+
if isinstance(self.index_value, BaseException):
204+
raise self.index_value
205+
return self.index_value
206+
207+
192208
class BadDescr:
193209
def __get__(self, obj, objtype=None):
194210
raise ValueError
@@ -1192,13 +1208,32 @@ def testLog(self):
11921208
self.ftest('log(10**40, 10**20)', math.log(10**40, 10**20), 2)
11931209
self.ftest('log(10**1000)', math.log(10**1000),
11941210
2302.5850929940457)
1211+
self.ftest('log(10**2000, 10**1000)', math.log(10**2000, 10**1000), 2)
1212+
self.ftest('log(MyIndexable(32), MyIndexable(2))',
1213+
math.log(MyIndexable(32), MyIndexable(2)), 5)
1214+
self.ftest('log(MyIndexable(10**1000))',
1215+
math.log(MyIndexable(10**1000)),
1216+
2302.5850929940457)
1217+
self.ftest('log(MyIndexable(10**2000), MyIndexable(10**1000))',
1218+
math.log(MyIndexable(10**2000), MyIndexable(10**1000)),
1219+
2)
1220+
self.assertRaises(ValueError, math.log, 0.0)
1221+
self.assertRaises(ValueError, math.log, 0)
1222+
self.assertRaises(ValueError, math.log, MyIndexable(0))
11951223
self.assertRaises(ValueError, math.log, -1.5)
1224+
self.assertRaises(ValueError, math.log, -1)
1225+
self.assertRaises(ValueError, math.log, MyIndexable(-1))
11961226
self.assertRaises(ValueError, math.log, -10**1000)
1227+
self.assertRaises(ValueError, math.log, MyIndexable(-10**1000))
11971228
self.assertRaises(ValueError, math.log, 10, -10)
11981229
self.assertRaises(ValueError, math.log, NINF)
11991230
self.assertEqual(math.log(INF), INF)
12001231
self.assertTrue(math.isnan(math.log(NAN)))
12011232

1233+
self.assertEqual(math.log(IndexableFloatLike(math.e, 10**1000)), 1.0)
1234+
self.assertAlmostEqual(math.log(IndexableFloatLike(OverflowError(), 10**1000)),
1235+
2302.5850929940457)
1236+
12021237
def testLog1p(self):
12031238
self.assertRaises(TypeError, math.log1p)
12041239
for n in [2, 2**90, 2**300]:
@@ -1214,16 +1249,28 @@ def testLog2(self):
12141249
self.assertEqual(math.log2(1), 0.0)
12151250
self.assertEqual(math.log2(2), 1.0)
12161251
self.assertEqual(math.log2(4), 2.0)
1252+
self.assertEqual(math.log2(MyIndexable(4)), 2.0)
12171253

12181254
# Large integer values
12191255
self.assertEqual(math.log2(2**1023), 1023.0)
12201256
self.assertEqual(math.log2(2**1024), 1024.0)
12211257
self.assertEqual(math.log2(2**2000), 2000.0)
1258+
self.assertEqual(math.log2(MyIndexable(2**2000)), 2000.0)
12221259

1260+
self.assertRaises(ValueError, math.log2, 0.0)
1261+
self.assertRaises(ValueError, math.log2, 0)
1262+
self.assertRaises(ValueError, math.log2, MyIndexable(0))
12231263
self.assertRaises(ValueError, math.log2, -1.5)
1264+
self.assertRaises(ValueError, math.log2, -1)
1265+
self.assertRaises(ValueError, math.log2, MyIndexable(-1))
1266+
self.assertRaises(ValueError, math.log2, -2**2000)
1267+
self.assertRaises(ValueError, math.log2, MyIndexable(-2**2000))
12241268
self.assertRaises(ValueError, math.log2, NINF)
12251269
self.assertTrue(math.isnan(math.log2(NAN)))
12261270

1271+
self.assertEqual(math.log2(IndexableFloatLike(8.0, 2**2000)), 3.0)
1272+
self.assertEqual(math.log2(IndexableFloatLike(OverflowError(), 2**2000)), 2000.0)
1273+
12271274
@requires_IEEE_754
12281275
# log2() is not accurate enough on Mac OS X Tiger (10.4)
12291276
@support.requires_mac_ver(10, 5)
@@ -1239,12 +1286,24 @@ def testLog10(self):
12391286
self.ftest('log10(1)', math.log10(1), 0)
12401287
self.ftest('log10(10)', math.log10(10), 1)
12411288
self.ftest('log10(10**1000)', math.log10(10**1000), 1000.0)
1289+
self.ftest('log10(MyIndexable(10))', math.log10(MyIndexable(10)), 1)
1290+
self.ftest('log10(MyIndexable(10**1000))',
1291+
math.log10(MyIndexable(10**1000)), 1000.0)
1292+
self.assertRaises(ValueError, math.log10, 0.0)
1293+
self.assertRaises(ValueError, math.log10, 0)
1294+
self.assertRaises(ValueError, math.log10, MyIndexable(0))
12421295
self.assertRaises(ValueError, math.log10, -1.5)
1296+
self.assertRaises(ValueError, math.log10, -1)
1297+
self.assertRaises(ValueError, math.log10, MyIndexable(-1))
12431298
self.assertRaises(ValueError, math.log10, -10**1000)
1299+
self.assertRaises(ValueError, math.log10, MyIndexable(-10**1000))
12441300
self.assertRaises(ValueError, math.log10, NINF)
12451301
self.assertEqual(math.log(INF), INF)
12461302
self.assertTrue(math.isnan(math.log10(NAN)))
12471303

1304+
self.assertEqual(math.log10(IndexableFloatLike(100.0, 10**1000)), 2.0)
1305+
self.assertEqual(math.log10(IndexableFloatLike(OverflowError(), 10**1000)), 1000.0)
1306+
12481307
@support.bigmemtest(2**32, memuse=0.2)
12491308
def test_log_huge_integer(self, size):
12501309
v = 1 << size
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:func:`math.log` now supports arbitrary large integer-like arguments in the
2+
same way as arbitrary large integer arguments.

Modules/mathmodule.c

Lines changed: 50 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ raised for division by zero and mod by zero.
5757
#endif
5858

5959
#include "Python.h"
60+
#include "pycore_abstract.h" // _PyNumber_Index()
6061
#include "pycore_bitutils.h" // _Py_bit_length()
6162
#include "pycore_call.h" // _PyObject_CallNoArgs()
6263
#include "pycore_import.h" // _PyImport_SetModuleString()
@@ -1578,43 +1579,62 @@ math_modf_impl(PyObject *module, double x)
15781579
in that int is larger than PY_SSIZE_T_MAX. */
15791580

15801581
static PyObject*
1581-
loghelper(PyObject* arg, double (*func)(double))
1582+
loghelper_int(PyObject* arg, double (*func)(double))
15821583
{
15831584
/* If it is int, do it ourselves. */
1584-
if (PyLong_Check(arg)) {
1585-
double x, result;
1586-
int64_t e;
1585+
double x, result;
1586+
int64_t e;
15871587

1588-
/* Negative or zero inputs give a ValueError. */
1589-
if (!_PyLong_IsPositive((PyLongObject *)arg)) {
1590-
/* The input can be an arbitrary large integer, so we
1591-
don't include it's value in the error message. */
1592-
PyErr_SetString(PyExc_ValueError,
1593-
"expected a positive input");
1594-
return NULL;
1595-
}
1588+
/* Negative or zero inputs give a ValueError. */
1589+
if (!_PyLong_IsPositive((PyLongObject *)arg)) {
1590+
PyErr_SetString(PyExc_ValueError,
1591+
"expected a positive input");
1592+
return NULL;
1593+
}
15961594

1597-
x = PyLong_AsDouble(arg);
1598-
if (x == -1.0 && PyErr_Occurred()) {
1599-
if (!PyErr_ExceptionMatches(PyExc_OverflowError))
1600-
return NULL;
1601-
/* Here the conversion to double overflowed, but it's possible
1602-
to compute the log anyway. Clear the exception and continue. */
1603-
PyErr_Clear();
1604-
x = _PyLong_Frexp((PyLongObject *)arg, &e);
1605-
assert(e >= 0);
1606-
assert(!PyErr_Occurred());
1607-
/* Value is ~= x * 2**e, so the log ~= log(x) + log(2) * e. */
1608-
result = fma(func(2.0), (double)e, func(x));
1609-
}
1610-
else
1611-
/* Successfully converted x to a double. */
1612-
result = func(x);
1613-
return PyFloat_FromDouble(result);
1595+
x = PyLong_AsDouble(arg);
1596+
if (x == -1.0 && PyErr_Occurred()) {
1597+
if (!PyErr_ExceptionMatches(PyExc_OverflowError))
1598+
return NULL;
1599+
/* Here the conversion to double overflowed, but it's possible
1600+
to compute the log anyway. Clear the exception and continue. */
1601+
PyErr_Clear();
1602+
x = _PyLong_Frexp((PyLongObject *)arg, &e);
1603+
assert(!PyErr_Occurred());
1604+
/* Value is ~= x * 2**e, so the log ~= log(x) + log(2) * e. */
1605+
result = fma(func(2.0), (double)e, func(x));
16141606
}
1607+
else
1608+
/* Successfully converted x to a double. */
1609+
result = func(x);
1610+
return PyFloat_FromDouble(result);
1611+
}
16151612

1613+
static PyObject*
1614+
loghelper(PyObject* arg, double (*func)(double))
1615+
{
1616+
/* If it is int, do it ourselves. */
1617+
if (PyLong_Check(arg)) {
1618+
return loghelper_int(arg, func);
1619+
}
16161620
/* Else let libm handle it by itself. */
1617-
return math_1(arg, func, 0, "expected a positive input, got %s");
1621+
PyObject *res = math_1(arg, func, 0, "expected a positive input, got %s");
1622+
if (res == NULL &&
1623+
PyErr_ExceptionMatches(PyExc_OverflowError) &&
1624+
PyIndex_Check(arg))
1625+
{
1626+
/* Here the conversion to double overflowed, but it's possible
1627+
to compute the log anyway. Clear the exception, convert to
1628+
integer and continue. */
1629+
PyErr_Clear();
1630+
arg = _PyNumber_Index(arg);
1631+
if (arg == NULL) {
1632+
return NULL;
1633+
}
1634+
res = loghelper_int(arg, func);
1635+
Py_DECREF(arg);
1636+
}
1637+
return res;
16181638
}
16191639

16201640

0 commit comments

Comments
 (0)