From 785261684e0e660dcdce48daf683cec541f4a8f2 Mon Sep 17 00:00:00 2001 From: Tim Peters Date: Wed, 5 Sep 2001 00:53:45 +0000 Subject: Return reasonable results for math.log(long) and math.log10(long) (we were getting Infs, NaNs, or nonsense in 2.1 and before; in yesterday's CVS we were getting OverflowError; but these functions always make good sense for positive arguments, no matter how large). --- Lib/test/test_long.py | 40 +++++++++++++++++++++++++++--- Misc/NEWS | 3 +++ Modules/mathmodule.c | 68 ++++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 104 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_long.py b/Lib/test/test_long.py index 104b086..573ef75 100644 --- a/Lib/test/test_long.py +++ b/Lib/test/test_long.py @@ -1,4 +1,4 @@ -from test_support import verify, verbose, TestFailed +from test_support import verify, verbose, TestFailed, fcmp from string import join from random import random, randint @@ -353,9 +353,7 @@ def test_float_overflow(): "1. / huge", "huge / 1.", "1. / mhuge", "mhuge / 1.", "1. ** huge", "huge ** 1.", "1. ** mhuge", "mhuge ** 1.", "math.sin(huge)", "math.sin(mhuge)", - "math.log(huge)", "math.log(mhuge)", # should do better "math.sqrt(huge)", "math.sqrt(mhuge)", # should do better - "math.log10(huge)", "math.log10(mhuge)", # should do better "math.floor(huge)", "math.floor(mhuge)"]: try: @@ -364,6 +362,41 @@ def test_float_overflow(): pass else: raise TestFailed("expected OverflowError from %s" % test) + +# ---------------------------------------------- test huge log and log10 + +def test_logs(): + import math + + if verbose: + print "log and log10" + + LOG10E = math.log10(math.e) + + for exp in range(10) + [100, 1000, 10000]: + value = 10 ** exp + log10 = math.log10(value) + verify(fcmp(log10, exp) == 0) + + # log10(value) == exp, so log(value) == log10(value)/log10(e) == + # exp/LOG10E + expected = exp / LOG10E + log = math.log(value) + verify(fcmp(log, expected) == 0) + + for bad in -(1L << 10000), -2L, 0L: + try: + math.log(bad) + raise TestFailed("expected ValueError from log(<= 0)") + except ValueError: + pass + + try: + math.log10(bad) + raise TestFailed("expected ValueError from log10(<= 0)") + except ValueError: + pass + # ---------------------------------------------------------------- do it test_division() @@ -372,3 +405,4 @@ test_format() test_misc() test_auto_overflow() test_float_overflow() +test_logs() diff --git a/Misc/NEWS b/Misc/NEWS index 7d44a59..5e763c0 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -81,6 +81,9 @@ Core Library +- math.log and math.log10 now return sensible results for even huge + long arguments. For example, math.log10(10 ** 10000) ~= 10000.0. + - A new function, imp.lock_held(), returns 1 when the import lock is currently held. See the docs for the imp module. diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index f715418..eef8b78 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -1,6 +1,7 @@ /* Math module -- standard C math library functions, pi and e */ #include "Python.h" +#include "longintrepr.h" #ifndef _MSC_VER #ifndef __STDC__ @@ -136,10 +137,6 @@ FUNC2(fmod, fmod, " x % y may differ.") FUNC2(hypot, hypot, "hypot(x,y)\n\nReturn the Euclidean distance, sqrt(x*x + y*y).") -FUNC1(log, log, - "log(x)\n\nReturn the natural logarithm of x.") -FUNC1(log10, log10, - "log10(x)\n\nReturn the base-10 logarithm of x.") #ifdef MPW_3_1 /* This hack is needed for MPW 3.1 but not for 3.2 ... */ FUNC2(pow, power, "pow(x,y)\n\nReturn x**y (x to the power of y).") @@ -231,6 +228,69 @@ static char math_modf_doc [] = "Return the fractional and integer parts of x. Both results carry the sign\n" "of x. The integer part is returned as a real."; +/* A decent logarithm is easy to compute even for huge longs, but libm can't + do that by itself -- loghelper can. func is log or log10, and name is + "log" or "log10". Note that overflow isn't possible: a long can contain + no more than INT_MAX * SHIFT bits, so has value certainly less than + 2**(2**64 * 2**16) == 2**2**80, and log2 of that is 2**80, which is + small enough to fit in an IEEE single. log and log10 are even smaller. +*/ + +static PyObject* +loghelper(PyObject* args, double (*func)(double), char *name) +{ + PyObject *arg; + char format[16]; + + /* See whether this is a long. */ + format[0] = 'O'; + format[1] = ':'; + strcpy(format + 2, name); + if (! PyArg_ParseTuple(args, format, &arg)) + return NULL; + + /* If it is long, do it ourselves. */ + if (PyLong_Check(arg)) { + double x; + int e; + x = _PyLong_AsScaledDouble(arg, &e); + if (x <= 0.0) { + PyErr_SetString(PyExc_ValueError, + "math domain error"); + return NULL; + } + /* Value is ~= x * 2**(e*SHIFT), so the log ~= + log(x) + log(2) * e * SHIFT. + CAUTION: e*SHIFT may overflow using int arithmetic, + so force use of double. */ + x = func(x) + func(2.0) * (double)e * (double)SHIFT; + return PyFloat_FromDouble(x); + } + + /* Else let libm handle it by itself. */ + format[0] = 'd'; + return math_1(args, func, format); +} + +static PyObject * +math_log(PyObject *self, PyObject *args) +{ + return loghelper(args, log, "log"); +} + +static char math_log_doc[] = +"log(x) -> the natural logarithm (base e) of x."; + +static PyObject * +math_log10(PyObject *self, PyObject *args) +{ + return loghelper(args, log10, "log10"); +} + +static char math_log10_doc[] = +"log10(x) -> the base 10 logarithm of x."; + + static PyMethodDef math_methods[] = { {"acos", math_acos, METH_VARARGS, math_acos_doc}, {"asin", math_asin, METH_VARARGS, math_asin_doc}, -- cgit v0.12