[pypy-commit] pypy py3.3: Add rfloat.log2(), which tries to be exact for powers of two.

amauryfa noreply at buildbot.pypy.org
Thu Dec 18 22:38:48 CET 2014


Author: Amaury Forgeot d'Arc <amauryfa at gmail.com>
Branch: py3.3
Changeset: r75026:d3c7a156cc70
Date: 2014-12-18 22:35 +0100
http://bitbucket.org/pypy/pypy/changeset/d3c7a156cc70/

Log:	Add rfloat.log2(), which tries to be exact for powers of two. Use it
	in math.log2.

diff --git a/pypy/module/math/interp_math.py b/pypy/module/math/interp_math.py
--- a/pypy/module/math/interp_math.py
+++ b/pypy/module/math/interp_math.py
@@ -202,6 +202,8 @@
             x = _get_double(space, w_x)
             if base == 10.0:
                 result = math.log10(x)
+            elif base == 2.0:
+                result = rfloat.log2(x)
             else:
                 result = math.log(x)
                 if base != 0.0:
diff --git a/rpython/rlib/rfloat.py b/rpython/rlib/rfloat.py
--- a/rpython/rlib/rfloat.py
+++ b/rpython/rlib/rfloat.py
@@ -281,6 +281,35 @@
             return (u - 1.) * x / math.log(u)
         return math.exp(x) - 1.
 
+def log2(x):
+    # Uses an algorithm that should:
+    #   (a) produce exact results for powers of 2, and
+    #   (b) be monotonic, assuming that the system log is monotonic.
+    if not isfinite(x):
+        if isnan(x):
+            return x  # log2(nan) = nan
+        elif x > 0.0:
+            return x  # log2(+inf) = +inf
+        else:
+            # log2(-inf) = nan, invalid-operation
+            raise ValueError("math domain error")
+
+    if x > 0.0:
+        if 0:  # HAVE_LOG2
+            return math.log2(x)
+        m, e = math.frexp(x)
+        # We want log2(m * 2**e) == log(m) / log(2) + e.  Care is needed when
+        # x is just greater than 1.0: in that case e is 1, log(m) is negative,
+        # and we get significant cancellation error from the addition of
+        # log(m) / log(2) to e.  The slight rewrite of the expression below
+        # avoids this problem.
+        if x >= 1.0:
+            return math.log(2.0 * m) / math.log(2.0) + (e - 1)
+        else:
+            return math.log(m) / math.log(2.0) + e
+    else:
+        raise ValueError("math domain error")
+
 def round_away(x):
     # round() from libm, which is not available on all platforms!
     absx = abs(x)
diff --git a/rpython/rlib/test/test_rfloat.py b/rpython/rlib/test/test_rfloat.py
--- a/rpython/rlib/test/test_rfloat.py
+++ b/rpython/rlib/test/test_rfloat.py
@@ -265,3 +265,12 @@
                 if s.strip(): # empty s raises OperationError directly
                     py.test.raises(ParseStringError, string_to_float, s)
     py.test.raises(ParseStringError, string_to_float, "")
+
+def test_log2():
+    from rpython.rlib import rfloat
+    assert rfloat.log2(1.0) == 0.0
+    assert rfloat.log2(2.0) == 1.0
+    assert rfloat.log2(2.0**1023) == 1023.0
+    assert 1.584 < rfloat.log2(3.0) < 1.585
+    py.test.raises(ValueError, rfloat.log2, 0)
+    py.test.raises(ValueError, rfloat.log2, -1)


More information about the pypy-commit mailing list