[pypy-commit] pypy default: Performance: copy long_to_decimal_string() from CPython.

arigo noreply at buildbot.pypy.org
Sat May 18 13:28:21 CEST 2013


Author: Armin Rigo <arigo at tunes.org>
Branch: 
Changeset: r64279:af160213d701
Date: 2013-05-18 11:17 +0200
http://bitbucket.org/pypy/pypy/changeset/af160213d701/

Log:	Performance: copy long_to_decimal_string() from CPython.

diff --git a/rpython/rlib/rbigint.py b/rpython/rlib/rbigint.py
--- a/rpython/rlib/rbigint.py
+++ b/rpython/rlib/rbigint.py
@@ -447,11 +447,11 @@
 
     @jit.elidable
     def repr(self):
-        return _format(self, BASE10, '', 'L')
+        return _format_decimal(self, addL=True)
 
     @jit.elidable
     def str(self):
-        return _format(self, BASE10)
+        return _format_decimal(self)
 
     @jit.elidable
     def eq(self, other):
@@ -2101,6 +2101,98 @@
     return ''.join(s[p:])
 
 
+DECIMAL_SHIFT = 0      # computed as max(E such that 10**E fits in a digit)
+while 10 ** (DECIMAL_SHIFT + 1) <= 2 ** SHIFT:
+    DECIMAL_SHIFT += 1
+DECIMAL_BASE = 10 ** DECIMAL_SHIFT
+
+def _format_decimal(a, addL=False):
+    """ Optimized version of _format(a, BASE10, '', addL*'L'). """
+    if a.sign == 0:
+        if addL:
+            return "0L"
+        else:
+            return "0"
+
+    size_a = a.numdigits()
+    negative = a.sign < 0
+
+    # quick and dirty upper bound for the number of digits
+    # required to express a in base DECIMAL_BASE:
+    #
+    #    #digits = 1 + floor(log2(a) / log2(DECIMAL_BASE))
+    #
+    # But log2(a) < size_a * PyLong_SHIFT, and
+    # log2(DECIMAL_BASE) = log2(10) * DECIMAL_SHIFT
+    #                    > 3 * DECIMAL_SHIFT
+
+    size = 1 + size_a * SHIFT // (3 * DECIMAL_SHIFT)
+    pout = [NULLDIGIT] * size
+
+    # convert array of base _PyLong_BASE digits in pin to an array of
+    # base _PyLong_DECIMAL_BASE digits in pout, following Knuth (TAOCP,
+    # Volume 2 (3rd edn), section 4.4, Method 1b).
+    size = 0
+    for i in range(size_a-1, -1, -1):
+        hi = a.digit(i)
+        for j in range(size):
+            z = (_widen_digit(pout[j]) << SHIFT) | hi
+            hi = _store_digit(z // DECIMAL_BASE)
+            pout[j] = _store_digit(z - _widen_digit(hi) * DECIMAL_BASE)
+        assert hi >= 0
+        while hi:
+            pout[size] = hi % DECIMAL_BASE
+            hi //= DECIMAL_BASE
+            size += 1
+    sizem1 = size - 1
+    assert sizem1 >= 0
+
+    # calculate exact length of output string, and allocate
+    strlen = (addL + negative +
+              1 + (sizem1) * DECIMAL_SHIFT)
+    tenpow = 10
+    rem = pout[sizem1]
+    while rem >= tenpow:
+        tenpow *= 10
+        strlen += 1
+
+    l = ['\x00'] * strlen
+
+    p = strlen
+    if addL:
+        p -= 1; assert p >= 0
+        l[p] = 'L'
+
+    # pout[0] through pout[size-2] contribute exactly
+    # DECIMAL_SHIFT digits each
+    for i in range(sizem1):
+        rem = pout[i]
+        assert rem >= 0
+        for j in range(DECIMAL_SHIFT):
+            p -= 1; assert p >= 0
+            l[p] = chr(ord('0') + rem % 10)
+            rem //= 10
+
+    # pout[size-1]: always produce at least one decimal digit
+    rem = pout[sizem1]
+    assert rem >= 0
+    while True:
+        p -= 1; assert p >= 0
+        l[p] = chr(ord('0') + rem % 10)
+        if rem < 10:
+            break
+        rem //= 10
+
+    # and sign
+    if negative:
+        p -= 1; assert p >= 0
+        l[p] = '-'
+
+    # check we've counted correctly
+    assert p == 0
+    return ''.join(l)
+
+
 def _bitwise(a, op, b): # '&', '|', '^'
     """ Bitwise and/or/xor operations """
 


More information about the pypy-commit mailing list