diff options
author | Mark Dickinson <dickinsm@gmail.com> | 2010-09-29 19:06:36 (GMT) |
---|---|---|
committer | Mark Dickinson <dickinsm@gmail.com> | 2010-09-29 19:06:36 (GMT) |
commit | c60371748b400b8b891fb2e5d2fe25b007c85994 (patch) | |
tree | c35b85a1e9f1cfd904e0c655e739e116c530f866 | |
parent | 0c0714f954bd78fdeae30ae284abc381bd850393 (diff) | |
download | cpython-c60371748b400b8b891fb2e5d2fe25b007c85994.zip cpython-c60371748b400b8b891fb2e5d2fe25b007c85994.tar.gz cpython-c60371748b400b8b891fb2e5d2fe25b007c85994.tar.bz2 |
Issue #9599: Further accuracy tweaks to loghelper. For an integer n that's small enough to be converted to a float without overflow, log(n) is now computed as log(float(n)), and similarly for log10.
-rw-r--r-- | Lib/test/test_math.py | 11 | ||||
-rw-r--r-- | Modules/mathmodule.c | 36 |
2 files changed, 31 insertions, 16 deletions
diff --git a/Lib/test/test_math.py b/Lib/test/test_math.py index 90bd363..1499ff9 100644 --- a/Lib/test/test_math.py +++ b/Lib/test/test_math.py @@ -641,8 +641,12 @@ class MathTests(unittest.TestCase): self.ftest('log(32,2)', math.log(32,2), 5) self.ftest('log(10**40, 10)', math.log(10**40, 10), 40) self.ftest('log(10**40, 10**20)', math.log(10**40, 10**20), 2) - self.assertEquals(math.log(INF), INF) + self.ftest('log(10**1000)', math.log(10**1000), + 2302.5850929940457) + self.assertRaises(ValueError, math.log, -1.5) + self.assertRaises(ValueError, math.log, -10**1000) self.assertRaises(ValueError, math.log, NINF) + self.assertEquals(math.log(INF), INF) self.assertTrue(math.isnan(math.log(NAN))) def testLog1p(self): @@ -655,8 +659,11 @@ class MathTests(unittest.TestCase): self.ftest('log10(0.1)', math.log10(0.1), -1) self.ftest('log10(1)', math.log10(1), 0) self.ftest('log10(10)', math.log10(10), 1) - self.assertEquals(math.log(INF), INF) + self.ftest('log10(10**1000)', math.log10(10**1000), 1000.0) + self.assertRaises(ValueError, math.log10, -1.5) + self.assertRaises(ValueError, math.log10, -10**1000) self.assertRaises(ValueError, math.log10, NINF) + self.assertEquals(math.log(INF), INF) self.assertTrue(math.isnan(math.log10(NAN))) def testModf(self): diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index 3cfb5f7..29c32a3 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -1562,25 +1562,33 @@ loghelper(PyObject* arg, double (*func)(double), char *funcname) { /* If it is long, do it ourselves. */ if (PyLong_Check(arg)) { - double x; + double x, result; Py_ssize_t e; - x = _PyLong_Frexp((PyLongObject *)arg, &e); - if (x == -1.0 && PyErr_Occurred()) - return NULL; - if (x <= 0.0) { + + /* Negative or zero inputs give a ValueError. */ + if (Py_SIZE(arg) <= 0) { PyErr_SetString(PyExc_ValueError, "math domain error"); return NULL; } - /* Value is ~= x * 2**e, so the log ~= log(x) + log(2) * e. - - It's slightly better to compute the log as log(2 * x) + log(2) * (e - - 1): then when 'arg' is a power of 2, 2**k say, this gives us 0.0 + - log(2) * k instead of log(0.5) + log(2)*(k+1), and so marginally - increases the chances of log(arg, 2) returning the correct result. - */ - x = func(2.0 * x) + func(2.0) * (e - 1); - return PyFloat_FromDouble(x); + + 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); } /* Else let libm handle it by itself. */ |