]> granicus.if.org Git - python/commitdiff
Return reasonable results for math.log(long) and math.log10(long) (we were
authorTim Peters <tim.peters@gmail.com>
Wed, 5 Sep 2001 00:53:45 +0000 (00:53 +0000)
committerTim Peters <tim.peters@gmail.com>
Wed, 5 Sep 2001 00:53:45 +0000 (00:53 +0000)
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
Misc/NEWS
Modules/mathmodule.c

index 104b08658587b98ff7b7330e8355704627c01b24..573ef75f2201c7d28101e239156660a5a2d2aeba 100644 (file)
@@ -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()
index 7d44a5930f420d6ba15f870f334387489ecf6b27..5e763c0b36ae6c2489e15b5d90a10227faf3f5f2 100644 (file)
--- 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.
 
index f7154183318886169f8818f9a1a08e8cea8be1e8..eef8b78c3fab7699dd1532b0abf77ce7152a88ac 100644 (file)
@@ -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},