mirror of
https://github.com/python/cpython.git
synced 2025-12-08 06:10:17 +00:00
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.
This commit is contained in:
parent
9e7340cd3b
commit
4359706ac8
3 changed files with 111 additions and 30 deletions
|
|
@ -189,6 +189,22 @@ def __init__(self, value):
|
||||||
def __index__(self):
|
def __index__(self):
|
||||||
return self.value
|
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:
|
class BadDescr:
|
||||||
def __get__(self, obj, objtype=None):
|
def __get__(self, obj, objtype=None):
|
||||||
raise ValueError
|
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**40, 10**20)', math.log(10**40, 10**20), 2)
|
||||||
self.ftest('log(10**1000)', math.log(10**1000),
|
self.ftest('log(10**1000)', math.log(10**1000),
|
||||||
2302.5850929940457)
|
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.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, -10**1000)
|
||||||
|
self.assertRaises(ValueError, math.log, MyIndexable(-10**1000))
|
||||||
self.assertRaises(ValueError, math.log, 10, -10)
|
self.assertRaises(ValueError, math.log, 10, -10)
|
||||||
self.assertRaises(ValueError, math.log, NINF)
|
self.assertRaises(ValueError, math.log, NINF)
|
||||||
self.assertEqual(math.log(INF), INF)
|
self.assertEqual(math.log(INF), INF)
|
||||||
self.assertTrue(math.isnan(math.log(NAN)))
|
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):
|
def testLog1p(self):
|
||||||
self.assertRaises(TypeError, math.log1p)
|
self.assertRaises(TypeError, math.log1p)
|
||||||
for n in [2, 2**90, 2**300]:
|
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(1), 0.0)
|
||||||
self.assertEqual(math.log2(2), 1.0)
|
self.assertEqual(math.log2(2), 1.0)
|
||||||
self.assertEqual(math.log2(4), 2.0)
|
self.assertEqual(math.log2(4), 2.0)
|
||||||
|
self.assertEqual(math.log2(MyIndexable(4)), 2.0)
|
||||||
|
|
||||||
# Large integer values
|
# Large integer values
|
||||||
self.assertEqual(math.log2(2**1023), 1023.0)
|
self.assertEqual(math.log2(2**1023), 1023.0)
|
||||||
self.assertEqual(math.log2(2**1024), 1024.0)
|
self.assertEqual(math.log2(2**1024), 1024.0)
|
||||||
self.assertEqual(math.log2(2**2000), 2000.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.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.assertRaises(ValueError, math.log2, NINF)
|
||||||
self.assertTrue(math.isnan(math.log2(NAN)))
|
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
|
@requires_IEEE_754
|
||||||
# log2() is not accurate enough on Mac OS X Tiger (10.4)
|
# log2() is not accurate enough on Mac OS X Tiger (10.4)
|
||||||
@support.requires_mac_ver(10, 5)
|
@support.requires_mac_ver(10, 5)
|
||||||
|
|
@ -1239,12 +1286,24 @@ def testLog10(self):
|
||||||
self.ftest('log10(1)', math.log10(1), 0)
|
self.ftest('log10(1)', math.log10(1), 0)
|
||||||
self.ftest('log10(10)', math.log10(10), 1)
|
self.ftest('log10(10)', math.log10(10), 1)
|
||||||
self.ftest('log10(10**1000)', math.log10(10**1000), 1000.0)
|
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.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, -10**1000)
|
||||||
|
self.assertRaises(ValueError, math.log10, MyIndexable(-10**1000))
|
||||||
self.assertRaises(ValueError, math.log10, NINF)
|
self.assertRaises(ValueError, math.log10, NINF)
|
||||||
self.assertEqual(math.log(INF), INF)
|
self.assertEqual(math.log(INF), INF)
|
||||||
self.assertTrue(math.isnan(math.log10(NAN)))
|
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)
|
@support.bigmemtest(2**32, memuse=0.2)
|
||||||
def test_log_huge_integer(self, size):
|
def test_log_huge_integer(self, size):
|
||||||
v = 1 << size
|
v = 1 << size
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
:func:`math.log` now supports arbitrary large integer-like arguments in the
|
||||||
|
same way as arbitrary large integer arguments.
|
||||||
|
|
@ -57,6 +57,7 @@ raised for division by zero and mod by zero.
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "Python.h"
|
#include "Python.h"
|
||||||
|
#include "pycore_abstract.h" // _PyNumber_Index()
|
||||||
#include "pycore_bitutils.h" // _Py_bit_length()
|
#include "pycore_bitutils.h" // _Py_bit_length()
|
||||||
#include "pycore_call.h" // _PyObject_CallNoArgs()
|
#include "pycore_call.h" // _PyObject_CallNoArgs()
|
||||||
#include "pycore_import.h" // _PyImport_SetModuleString()
|
#include "pycore_import.h" // _PyImport_SetModuleString()
|
||||||
|
|
@ -1577,44 +1578,63 @@ math_modf_impl(PyObject *module, double x)
|
||||||
However, intermediate overflow is possible for an int if the number of bits
|
However, intermediate overflow is possible for an int if the number of bits
|
||||||
in that int is larger than PY_SSIZE_T_MAX. */
|
in that int is larger than PY_SSIZE_T_MAX. */
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
loghelper_int(PyObject* arg, double (*func)(double))
|
||||||
|
{
|
||||||
|
/* If it is int, do it ourselves. */
|
||||||
|
double x, result;
|
||||||
|
int64_t e;
|
||||||
|
|
||||||
|
/* 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(!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*
|
static PyObject*
|
||||||
loghelper(PyObject* arg, double (*func)(double))
|
loghelper(PyObject* arg, double (*func)(double))
|
||||||
{
|
{
|
||||||
/* If it is int, do it ourselves. */
|
/* If it is int, do it ourselves. */
|
||||||
if (PyLong_Check(arg)) {
|
if (PyLong_Check(arg)) {
|
||||||
double x, result;
|
return loghelper_int(arg, func);
|
||||||
int64_t e;
|
}
|
||||||
|
/* Else let libm handle it by itself. */
|
||||||
/* Negative or zero inputs give a ValueError. */
|
PyObject *res = math_1(arg, func, 0, "expected a positive input, got %s");
|
||||||
if (!_PyLong_IsPositive((PyLongObject *)arg)) {
|
if (res == NULL &&
|
||||||
/* The input can be an arbitrary large integer, so we
|
PyErr_ExceptionMatches(PyExc_OverflowError) &&
|
||||||
don't include it's value in the error message. */
|
PyIndex_Check(arg))
|
||||||
PyErr_SetString(PyExc_ValueError,
|
{
|
||||||
"expected a positive input");
|
/* 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;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
res = loghelper_int(arg, func);
|
||||||
x = PyLong_AsDouble(arg);
|
Py_DECREF(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);
|
|
||||||
}
|
}
|
||||||
|
return res;
|
||||||
/* Else let libm handle it by itself. */
|
|
||||||
return math_1(arg, func, 0, "expected a positive input, got %s");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue