[pypy-svn] pypy shorter-float-repr: Translate from CPython into RPython: format_float_short()

amauryfa commits-noreply at bitbucket.org
Fri Jan 21 01:12:52 CET 2011


Author: Amaury Forgeot d'Arc <amauryfa at gmail.com>
Branch: shorter-float-repr
Changeset: r41106:ab8bb12b6dc2
Date: 2011-01-21 01:12 +0100
http://bitbucket.org/pypy/pypy/changeset/ab8bb12b6dc2/

Log:	Translate from CPython into RPython: format_float_short()

diff --git a/pypy/rpython/module/ll_dtoa.py b/pypy/rpython/module/ll_dtoa.py
--- a/pypy/rpython/module/ll_dtoa.py
+++ b/pypy/rpython/module/ll_dtoa.py
@@ -47,8 +47,156 @@
                 raise ValueError("invalid input at position %d" % (offset,))
             return result
 
-def dtoa(value, mode=0, precision=0, flags=0):
+def format_nonfinite(digits, sign, flags):
+    "Format dtoa's output for nonfinite numbers"
+    if digits[0] == 'i' or digits[0] == 'I':
+        if sign == 1:
+            return '-inf'
+        elif flags & rarithmetic.DTSF_SIGN:
+            return '+inf'
+        else:
+            return 'inf'
+    elif digits[0] == 'n' or digits[0] == 'N':
+        return 'nan'
+    else:
+        # shouldn't get here
+        raise ValueError
+
+def format_number(digits, buflen, sign, decpt, code, precision, flags):
+    # We got digits back, format them.  We may need to pad 'digits'
+    # either on the left or right (or both) with extra zeros, so in
+    # general the resulting string has the form
+    #
+    # [<sign>]<zeros><digits><zeros>[<exponent>]
+    #
+    # where either of the <zeros> pieces could be empty, and there's a
+    # decimal point that could appear either in <digits> or in the
+    # leading or trailing <zeros>.
+    #
+    # Imagine an infinite 'virtual' string vdigits, consisting of the
+    # string 'digits' (starting at index 0) padded on both the left
+    # and right with infinite strings of zeros.  We want to output a
+    # slice
+    #
+    # vdigits[vdigits_start : vdigits_end]
+    #
+    # of this virtual string.  Thus if vdigits_start < 0 then we'll
+    # end up producing some leading zeros; if vdigits_end > digits_len
+    # there will be trailing zeros in the output.  The next section of
+    # code determines whether to use an exponent or not, figures out
+    # the position 'decpt' of the decimal point, and computes
+    # 'vdigits_start' and 'vdigits_end'.
     builder = StringBuilder(20)
+
+    use_exp = False
+    vdigits_end = buflen
+    if code == 'e':
+        use_exp = True
+        vdigits_end = precision
+    elif code == 'f':
+        vdigits_end = decpt + precision
+    elif code == 'g':
+        if decpt <= -4:
+            use_exp = True
+        elif decpt > precision:
+            use_exp = True
+        elif flags & rarithmetic.DTSF_ADD_DOT_0 and decpt == precision:
+            use_exp = True
+        if flags & rarithmetic.DTSF_ALT:
+            vdigits_end = precision
+    elif code == 'r':
+        #  convert to exponential format at 1e16.  We used to convert
+        #  at 1e17, but that gives odd-looking results for some values
+        #  when a 16-digit 'shortest' repr is padded with bogus zeros.
+        #  For example, repr(2e16+8) would give 20000000000000010.0;
+        #  the true value is 20000000000000008.0.
+        if decpt <= -4 or decpt > 16:
+            use_exp = True
+    else:
+        raise ValueError
+
+    # if using an exponent, reset decimal point position to 1 and
+    # adjust exponent accordingly.
+    if use_exp:
+        exp = decpt - 1
+        decpt = 1
+
+    # ensure vdigits_start < decpt <= vdigits_end, or vdigits_start <
+    # decpt < vdigits_end if add_dot_0_if_integer and no exponent
+    if decpt <= 0:
+        vdigits_start = decpt-1
+    else:
+        vdigits_start = 0
+    if vdigits_end <= decpt:
+        if not use_exp and flags & rarithmetic.DTSF_ADD_DOT_0:
+            vdigits_end = decpt + 1
+        else:
+            vdigits_end = decpt
+
+    # double check inequalities
+    assert vdigits_start <= 0
+    assert 0 <= buflen <= vdigits_end
+    # decimal point should be in (vdigits_start, vdigits_end]
+    assert vdigits_start < decpt <= vdigits_end
+
+    if sign == 1:
+        builder.append('-')
+    elif flags & rarithmetic.DTSF_SIGN:
+        builder.append('+')
+
+    # note that exactly one of the three 'if' conditions is true, so
+    # we include exactly one decimal point
+    # 1. Zero padding on left of digit string
+    if decpt <= 0:
+        builder.append_multiple_char('0', decpt - vdigits_start)
+        builder.append('.')
+        builder.append_multiple_char('0', 0 - decpt)
+    else:
+        builder.append_multiple_char('0', 0 - vdigits_start)
+
+    # 2. Digits, with included decimal point
+    if 0 < decpt <= buflen:
+        builder.append(rffi.charpsize2str(digits, decpt - 0))
+        builder.append('.')
+        ptr = rffi.ptradd(digits, decpt)
+        builder.append(rffi.charpsize2str(ptr, buflen - decpt))
+    else:
+        builder.append(rffi.charpsize2str(digits, buflen))
+
+    # 3. And zeros on the right
+    if buflen < decpt:
+        builder.append_multiple_char('0', decpt - buflen)
+        builder.append('.')
+        builder.append_multiple_char('0', vdigits_end - decpt)
+    else:
+        builder.append_multiple_char('0', vdigits_end - buflen)
+
+    s = builder.build()
+
+    # Delete a trailing decimal pt unless using alternative formatting.
+    if not flags & rarithmetic.DTSF_ALT:
+        last = len(s) - 1
+        if last >= 0 and s[last] == '.':
+            s = s[:last]
+
+    # Now that we've done zero padding, add an exponent if needed.
+    if use_exp:
+        if exp >= 0:
+            exp_str = str(exp)
+            if len(exp_str) < 2:
+                s += 'e+0' + exp_str
+            else:
+                s += 'e+' + exp_str
+        else:
+            exp_str = str(-exp)
+            if len(exp_str) < 2:
+                s += 'e-0' + exp_str
+            else:
+                s += 'e-' + exp_str
+
+    return s
+
+def dtoa(value, code='r', mode=0, precision=0, flags=0):
     with lltype.scoped_alloc(rffi.INTP.TO, 1) as decpt_ptr:
         with lltype.scoped_alloc(rffi.INTP.TO, 1) as sign_ptr:
             with lltype.scoped_alloc(rffi.CCHARPP.TO, 1) as end_ptr:
@@ -61,42 +209,15 @@
 
                     # Handle nan and inf
                     if buflen and not digits[0].isdigit():
-                        if digits[0] == 'i' or digits[0] == 'I':
-                            if sign == 1:
-                                builder.append('-')
-                            elif flags & rarithmetic.DTSF_SIGN:
-                                builder.append('+')
-                            builder.append('inf')
-                        elif digits[0] == 'n' or digits[0] == 'N':
-                            builder.append('nan')
-                        else:
-                            # shouldn't get here
-                            raise ValueError
-                        return builder.build()
+                        return format_nonfinite(digits, sign, flags)
 
-                    if sign == 1:
-                        builder.append('-')
-                    elif flags & rarithmetic.DTSF_SIGN:
-                        builder.append('+')
+                    decpt = rffi.cast(lltype.Signed, decpt_ptr[0])
 
-                    intpart = rffi.cast(lltype.Signed, decpt_ptr[0])
-                    if intpart <= buflen:
-                        builder.append(rffi.charpsize2str(digits, intpart))
-                    else:
-                        builder.append(rffi.charpsize2str(digits, buflen))
-                        while buflen < intpart:
-                            builder.append('0')
-                            intpart -= 1
-                    fracpart = buflen - intpart
-                    if fracpart > 0:
-                        builder.append('.')
-                        ptr = rffi.ptradd(digits, intpart)
-                        builder.append(rffi.charpsize2str(ptr, fracpart))
-                    elif flags & rarithmetic.DTSF_ADD_DOT_0:
-                        builder.append('.0')
+                    return format_number(digits, buflen, sign, decpt,
+                                         code, precision, flags)
+
                 finally:
                     dg_freedtoa(digits)
-    return builder.build()
 
 def llimpl_strtod(value, code, precision, flags):
     if code in 'EFG':
@@ -119,4 +240,4 @@
     else:
         raise ValueError('Invalid mode')
 
-    return dtoa(value, mode=mode, precision=precision, flags=flags)
+    return dtoa(value, code, mode=mode, precision=precision, flags=flags)

diff --git a/pypy/rpython/module/test/test_ll_dtoa.py b/pypy/rpython/module/test/test_ll_dtoa.py
--- a/pypy/rpython/module/test/test_ll_dtoa.py
+++ b/pypy/rpython/module/test/test_ll_dtoa.py
@@ -13,8 +13,11 @@
     assert dtoa(1.1, flags=rarithmetic.DTSF_SIGN) == "+1.1"
     assert dtoa(12.3577) == "12.3577"
     assert dtoa(10.0) == "10"
-    assert dtoa(1.0e100) == "1" + "0" * 100
+    assert dtoa(1.0e100) == "1e+100"
 
     assert dtoa(rarithmetic.INFINITY) == 'inf'
     assert dtoa(-rarithmetic.INFINITY) == '-inf'
     assert dtoa(rarithmetic.NAN) == 'nan'
+
+def test_dtoa_precision():
+    assert dtoa(1.1, code='f', precision=2) == "1.10"


More information about the Pypy-commit mailing list