Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
59 changes: 59 additions & 0 deletions Lib/test/test_math.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,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
Expand Down Expand Up @@ -1208,13 +1224,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]:
Expand All @@ -1230,16 +1265,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)
Expand All @@ -1255,12 +1302,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)

def testSumProd(self):
sumprod = math.sumprod
Decimal = decimal.Decimal
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;
/* 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