[pypy-svn] r5645 - pypy/trunk/src/pypy/appspace

mwh at codespeak.net mwh at codespeak.net
Sat Jul 24 17:43:41 CEST 2004


Author: mwh
Date: Sat Jul 24 17:43:41 2004
New Revision: 5645

Added:
   pypy/trunk/src/pypy/appspace/_float_formatting.py
Modified:
   pypy/trunk/src/pypy/appspace/_formatting.py
Log:
Climbing out of the tarpit: take string formatting from 90% to 99%.
See comments in _formatting for the remaining problems.


Added: pypy/trunk/src/pypy/appspace/_float_formatting.py
==============================================================================
--- (empty file)
+++ pypy/trunk/src/pypy/appspace/_float_formatting.py	Sat Jul 24 17:43:41 2004
@@ -0,0 +1,139 @@
+import math
+
+# from the excessive effort department, routines for printing floating
+# point numbers from
+
+# "Printing Floating-Point Numbers Quickly and Accurately" by Burger &
+# Dybvig, Proceedings of the SIGPLAN '96 Conference on Programming
+# Language Design and Implementation.
+
+# The paper contains scheme code which has been specialized for IEEE
+# doubles and converted into (still somewhat scheme-like) Python by
+# Michael Hudson.
+
+# XXX unfortunately, we need the fixed-format output routines, the source
+# for which is not included in the paper... for now, just put up with
+# occasionally incorrectly rounded final digits.  I'll get to it.
+
+# XXX should run this at interpreter level, really....
+
+
+## (define flonum->digits
+##   (lambda (v f e min-e p b B)
+##     (if (>= e 0)
+##         (if (not (= f (expt b (- p 1))))
+##             (let ([be (expt b e)])
+##               (scale (* f be 2) 2 be be 0 B v))
+##             (let* ([be (expt b e)] [be1 (* be b)])
+##                   (scale (* f be1 2) (* b 2) be1 be 0 B v)))
+##         (if (or (= e min-e) (not (= f (expt b (- p 1)))))
+##             (scale (* f 2) (* (expt b (- e)) 2) 1 1 0 B v)
+##             (scale (* f b 2) (* (expt b (- 1 e)) 2) b 1 0 B v)))))
+
+# I reject generality in the extreme: we're working with
+# ieee 754 64 bit doubles on any platform I care about.
+# This means b == 2, min-e = -1074, p = 53 above.  Also,
+# specialize for B = 10.
+
+# in:
+# v = f * 2**e
+# out:
+# [d0, d1, ..., dn], k
+# st 0.[d1][d2]...[dn] * 10**k is the "best" representation of v
+
+def flonum2digits(v, f, e):
+    if e >= 0:
+        if not f != 2**52:
+            be = 2**e
+            return scale(f*be*2, 2, be, be, 0, v)
+        else:
+            be = 2**e
+            be1 = 2*be
+            return scale(f*be1*2, 4, be1, be, 0, v)
+    else:
+        if e == -1075 or f != 2**52:
+            return scale(f*2, 2*2**(-e), 1, 1, 0, v)
+        else:
+            return scale(f*4, 2*2**(1-e), 2, 1, 0, v)
+
+
+## (define generate
+##   (lambda (r s m+ m- B low-ok? high-ok?)
+##     (let ([q-r (quotient-remainder (* r B) s)]
+##           [m+ (* m+ B)]
+##           [m- (* m- B)])
+##       (let ([d (car q-r)]
+##             [r (cdr q-r)])
+##         (let ([tc1 ((if low-ok? <= <) r m-)]
+##               [tc2 ((if high-ok? >= >) (+ r m+) s)])
+##           (if (not tc1)
+##               (if (not tc2)
+##                   (cons d (generate r s m+ m- B low-ok? high-ok?))
+##                   (list (+ d 1)))
+##               (if (not tc2)
+##                   (list d)
+##                   (if (< (* r 2) s)
+##                       (list d)
+##                       (list (+ d 1))))))))))
+
+# now the above is an example of a pointlessly recursive algorithm if
+# ever i saw one...
+
+def generate(r, s, m_plus, m_minus):
+    rr = []
+    while 1:
+        d, r = divmod(r*10, s)
+        m_plus *= 10
+        m_minus *= 10
+        tc1 = r < m_minus
+        tc2 = (r + m_plus) > s
+        if tc2:
+            rr.append(d+1)
+        else:
+            rr.append(d)
+        if tc1 or tc2:
+            break
+    return rr
+
+
+## (define scale
+##   (lambda (r s m+ m- k B low-ok? high-ok? v)
+##     (let ([est (inexact->exact (ceiling (- (logB B v) 1e-10)))])
+##       (if (>= est 0)
+##           (fixup r (* s (exptt B est)) m+ m- est B low-ok? high-ok? )
+##           (let ([scale (exptt B (- est))])
+##             (fixup (* r scale) s (* m+ scale) (* m- scale)
+##                    est B low-ok? high-ok? ))))))
+
+def scale(r, s, m_plus, m_minus, k, v):
+    est = long(math.ceil(math.log(v, 10) - 1e-10))
+    if est >= 0:
+        return fixup(r, s * 10 ** est, m_plus, m_minus, est)
+    else:
+        scale = 10 ** -est
+        return fixup(r*scale, s, m_plus*scale, m_minus*scale, est)
+
+
+## (define fixup
+##   (lambda (r s m+ m- k B low-ok? high-ok? )
+##     (if ((if high-ok? >= >) (+ r m+) s) ; too low?
+##         (cons (+ k 1) (generate r (* s B) m+ m- B low-ok? high-ok? ))
+##         (cons k (generate r s m+ m- B low-ok? high-ok? )))))
+
+def fixup(r, s, m_plus, m_minus, k):
+    if r + m_plus > s:
+        return generate(r, s*10, m_plus, m_minus), k + 1
+    else:
+        return generate(r, s, m_plus, m_minus), k
+
+
+def float_digits(f):
+    assert f >= 0
+    if f == 0.0:
+        return ['0'], 1
+    m, e = math.frexp(f)
+    m = long(m*2.0**53)
+    e -= 53
+    ds, k = flonum2digits(f, m, e)
+    ds = map(str, ds)
+    return ds, k

Modified: pypy/trunk/src/pypy/appspace/_formatting.py
==============================================================================
--- pypy/trunk/src/pypy/appspace/_formatting.py	(original)
+++ pypy/trunk/src/pypy/appspace/_formatting.py	Sat Jul 24 17:43:41 2004
@@ -2,6 +2,11 @@
 
 # There's some insane stuff in here.  Blame CPython.  Please.
 
+# Known problems:
+# (1) rounding isn't always right (see comments in _float_formatting).
+# (2) something goes wrong in the f_alt case of %g handling.
+# (3) it's really, really slow.
+
 class _Flags(object):
     def __repr__(self):
         return "<%s>"%(', '.join([f for f in self.__dict__ 
@@ -12,12 +17,13 @@
     f_alt = 0
     f_zero = 0
 
+
 def value_next(valueiter):
     try:
         return valueiter.next()
     except StopIteration:
-        raise TypeError('not enough arguments for format string')
-    
+        raise TypeError('not enough arguments for format string')    
+
 
 def peel_num(c, fmtiter, valueiter):
     if c == '*':
@@ -34,6 +40,7 @@
     else:
         return c, 0
 
+
 def peel_flags(c, fmtiter):
     flags = _Flags()
     while 1:
@@ -52,6 +59,7 @@
         c = fmtiter.next()
     return c, flags
 
+
 def parse_fmt(fmtiter, valueiter, valuedict):
     """return (char, flags, width, prec, value)
     partially consumes fmtiter & valueiter"""
@@ -93,6 +101,7 @@
             value = value_next(valueiter)
     return (c, flags, width, prec, value)
 
+
 class Formatter(object):
     def __init__(self, char, flags, width, prec, value):
         self.char = char
@@ -105,9 +114,10 @@
         # negative zeroes?
         # * mwh giggles, falls over
         # still, if we can recognize them, here's the place to do it.
-        if v < 0:
+        import math
+        if v < 0 or v == 0 and math.atan2(0, v) != 0:
             sign = '-'
-            v = -v
+            v = -v            
         else:
             if self.flags.f_sign:
                 sign = '+'
@@ -119,11 +129,9 @@
 
     def numeric_postprocess(self, r, sign):
         assert self.char in 'iduoxXeEfFgG'
-        
         padchar = ' '
         if self.flags.f_zero:
             padchar = '0'
-        
         if self.width is not None:
             p = self.width - len(r) - len(sign)
             if self.flags.f_ljust:
@@ -136,16 +144,12 @@
         else:
             r = sign + r
         return r
-        
 
     def format(self):
         raise NotImplementedError
 
     def std_wp(self, r):
-        padchar = ' '
-        if self.flags.f_zero and self.char in 'iduoxXeEfFgG':
-            padchar = '0'
-        
+        assert self.char not in 'iduoxXeEfFgG'
         if self.prec is not None:
             r = r[:self.prec]
         if self.width is not None:
@@ -153,9 +157,10 @@
             if self.flags.f_ljust:
                 r = r + ' '*p
             else:
-                r = padchar*p + r
+                r = ' '*p + r
         return r
 
+
 def funcFormatter(*funcs):
     class _F(Formatter):
         def format(self):
@@ -165,6 +170,7 @@
             return self.std_wp(r)
     return _F
 
+
 def maybe_int(value):
     try:
         inter = value.__int__
@@ -172,166 +178,26 @@
         raise TypeError, "an integer argument is required"
     return inter()
 
+
 def maybe_float(value):
     try:
         floater = value.__float__
     except AttributeError:
-        raise TypeError, "float argument is required"
+        raise TypeError, "a float argument is required"
     return floater()
 
-import math
-
-# from the excessive effort department, routines for printing floating
-# point numbers from
-
-# "Printing Floating-Point Numbers Quickly and Accurately" by Burger &
-# Dybvig, Proceedings of the SIGPLAN '96 Conference on Programming
-# Language Design and Implementation.
-
-# The paper contains scheme code which has been specialized for IEEE
-# doubles and converted into (still somewhat scheme-like) Python by
-# Michael Hudson.
-
-# XXX unfortunately, we need the fixed-format output routines, the source
-# for which is not included in the paper... for now, just put up with
-# occasionally incorrectly rounded final digits.  I'll get to it.
-
-# XXX should run this at interpreter level, really....
-
-## (define flonum->digits
-##   (lambda (v f e min-e p b B)
-##     (if (>= e 0)
-##         (if (not (= f (expt b (- p 1))))
-##             (let ([be (expt b e)])
-##               (scale (* f be 2) 2 be be 0 B v))
-##             (let* ([be (expt b e)] [be1 (* be b)])
-##                   (scale (* f be1 2) (* b 2) be1 be 0 B v)))
-##         (if (or (= e min-e) (not (= f (expt b (- p 1)))))
-##             (scale (* f 2) (* (expt b (- e)) 2) 1 1 0 B v)
-##             (scale (* f b 2) (* (expt b (- 1 e)) 2) b 1 0 B v)))))
-
-def flonum2digits(v, f, e, B):
-    
-    # sod generality in the extreme: we're working with ieee 754 64
-    # bit doubles on any platform I care about.
-    # this means b == 2, min-e = -1075 (?), p = 53 above
-
-    # in:
-    # v = f * 2**e
-    # B is output base
-
-    # out:
-    # [d0, d1, ..., dn], k
-    # st 0.[d1][d2]...[dn] * B**k is the "best" representation of v
-
-    if e >= 0:
-        if not f != 2**52:
-            be = 2**e
-            return scale(f*be*2, 2, be, be, 0, B, v)
-        else:
-            be = 2**e
-            be1 = 2*be
-            return scale(f*be1*2, 4, be1, be, 0, B, v)
-    else:
-        if e == -1075 or f != 2**52:
-            return scale(f*2, 2*2**(-e), 1, 1, 0, B, v)
-        else:
-            return scale(f*4, 2*2**(1-e), 2, 1, 0, B, v)
-
-## (define generate
-##   (lambda (r s m+ m- B low-ok? high-ok?)
-##     (let ([q-r (quotient-remainder (* r B) s)]
-##           [m+ (* m+ B)]
-##           [m- (* m- B)])
-##       (let ([d (car q-r)]
-##             [r (cdr q-r)])
-##         (let ([tc1 ((if low-ok? <= <) r m-)]
-##               [tc2 ((if high-ok? >= >) (+ r m+) s)])
-##           (if (not tc1)
-##               (if (not tc2)
-##                   (cons d (generate r s m+ m- B low-ok? high-ok?))
-##                   (list (+ d 1)))
-##               (if (not tc2)
-##                   (list d)
-##                   (if (< (* r 2) s)
-##                       (list d)
-##                       (list (+ d 1))))))))))
 
-# now the above is an example of a pointlessly recursive algorithm if
-# ever i saw one...
+from _float_formatting import float_digits
 
-def generate(r, s, m_plus, m_minus, B):
-    rr = []
-    while 1:
-        d, r = divmod(r*B, s)
-        m_plus *= B
-        m_minus *= B
-        tc1 = r < m_minus
-        tc2 = (r + m_plus) > s
-        if tc2:
-            rr.append(d+1)
-        else:
-            rr.append(d)
-        if tc1 or tc2:
-            break
-    return rr
-
-## (define scale
-##   (lambda (r s m+ m- k B low-ok? high-ok? v)
-##     (let ([est (inexact->exact (ceiling (- (logB B v) 1e-10)))])
-##       (if (>= est 0)
-##           (fixup r (* s (exptt B est)) m+ m- est B low-ok? high-ok? )
-##           (let ([scale (exptt B (- est))])
-##             (fixup (* r scale) s (* m+ scale) (* m- scale) est B low-ok? high-ok? ))))))
-
-def scale(r, s, m_plus, m_minus, k, B, v):
-    est = long(math.ceil(math.log(v, B) - 1e-10))
-    if est >= 0:
-        return fixup(r, s * B ** est, m_plus, m_minus, est, B)
-    else:
-        scale = B ** -est
-        return fixup(r*scale, s, m_plus*scale, m_minus*scale, est, B)
-
-## (define fixup
-##   (lambda (r s m+ m- k B low-ok? high-ok? )
-##     (if ((if high-ok? >= >) (+ r m+) s) ; too low?
-##         (cons (+ k 1) (generate r (* s B) m+ m- B low-ok? high-ok? ))
-##         (cons k (generate r s m+ m- B low-ok? high-ok? )))))
-
-def fixup(r, s, m_plus, m_minus, k, B):
-    if r + m_plus > s:
-        return generate(r, s*B, m_plus, m_minus, B), k + 1
-    else:
-        return generate(r, s, m_plus, m_minus, B), k
-    
-
-def float_digits(f):
-    assert f >= 0
-    if f == 0.0:
-        return [], 1
-    m, e = math.frexp(f)
-    m = long(m*2.0**53)
-    e -= 53
-    ds, k = flonum2digits(f, m, e, 10)
-    ds = map(str, ds)
-    return ds, k
-
-class floatFFormatter(Formatter):
-    def format(self):
-        v = maybe_float(self.value)
-        if abs(v)/1e25 > 1e25:
-            return floatGFormatter('g', self.flags, self.width,
-                                   self.prec, self.value).format()
-        v, sign = self.numeric_preprocess(v)
-
-        if self.prec is None:
-            self.prec = 6
-
-        # we want self.prec digits after the radix point.
+class FloatFormatter(Formatter):
+    def eDigits(self, ds):
+        ds = ds[:self.prec + 1] + ['0'] * (self.prec + 1 - len(ds))
+        if self.prec > 0 or self.flags.f_alt:
+            ds[1:1] = ['.']
+        return ''.join(ds)
 
-        # this is probably more complex than it needs to be:
+    def fDigits(self, ds, k):
         p = max(self.prec, 0)
-        ds, k = float_digits(v)
         if 0 < k < len(ds):
             if len(ds) - k < p:
                 ds.extend(['0'] * (p - (len(ds) - k)))
@@ -345,94 +211,140 @@
             ds[0:0]= ['0', '.']
         elif k >= len(ds):
             ds.extend((k-len(ds))*['0'] + ['.'] + ['0']*p)
+        return ''.join(ds)
 
-        if self.prec <= 0:
-            del ds[-1]
-        
-        return self.numeric_postprocess(''.join(ds), sign)
-
-class floatEFormatter(Formatter):
     def format(self):
         v = maybe_float(self.value)
-
         v, sign = self.numeric_preprocess(v)
-
         if self.prec is None:
             self.prec = 6
+        r = self._format(v)
+        return self.numeric_postprocess(r, sign)
+        
 
+class FloatFFormatter(FloatFormatter):
+    def _format(self, v):
+        if v/1e25 > 1e25:
+            return floatGFormatter('g', self.flags, self.width,
+                                   self.prec, self.value).format()
         ds, k = float_digits(v)
-        ds = ds[:self.prec + 1] + ['0'] * (self.prec + 1 - len(ds))
-        ds[1:1] = ['.']
-
-        r = ''.join(ds) + self.char + "%+03d"%(k-1,)
-
-        return self.numeric_postprocess(r, sign)
+        digits = self.fDigits(ds, k)
+        if  not self.flags.f_alt:
+            digits = digits.rstrip('.')
+        return digits
 
-class floatGFormatter(Formatter):
-    # the description of %g in the Python documentation lies.
-    def format(self):
-        v = maybe_float(self.value)
 
-        v, sign = self.numeric_preprocess(v)
+class FloatEFormatter(FloatFormatter):
+    def _format(self, v):
+        ds, k = float_digits(v)
+        digits = self.eDigits(ds)
+        return "%s%c%+03d"%(digits, self.char, k-1)
 
-        if self.prec is None:
-            self.prec = 6
 
+class FloatGFormatter(FloatFormatter):
+    # The description of %g in the Python documentation lies
+    # in a variety of minor ways.
+    # Gah, this still isn't quite right in the f_alt case.
+    # (One has to wonder who might care).
+    def _format(self, v):
         ds, k = float_digits(v)
-
         ds = ds[:self.prec] # XXX rounding!
-
-        if -4 < k < self.prec:
-            if 0 < k < len(ds):
-                ds[k:k] = ['.']
-            if k <= 0:
-                ds[0:0] = ['0', '.'] + ['0']*(-k)
-            elif k >= len(ds):
-                ds.extend((k-len(ds))*['0'])
-            r = ''.join(ds)
+        if -4 < k <= self.prec:
+            digits = self.fDigits(ds, k)
+            if not self.flags.f_alt:
+                digits = digits.rstrip('0').rstrip('.')
+            r = digits
         else:
-            ds[1:1] = ['.']
-            r = ''.join(ds) + self.char + "%+03d"%(k-1,)
-            
-        return self.numeric_postprocess(r, sign)
+            digits = self.eDigits(ds)
+            if not self.flags.f_alt:
+                digits = digits.rstrip('0').rstrip('.')
+            r = "%se%+03d"%(digits, k-1)
+        return r
+
 
 class HexFormatter(Formatter):
+    # NB: this has 2.4 semantics wrt. negative values
     def format(self):
-        i = maybe_int(self.value)
-        r = hex(i)
-        if not self.flags.f_alt:
-            r = r[2:]
+        v, sign = self.numeric_preprocess(maybe_int(self.value))
+        r = hex(v)[2:]
+        if self.prec is not None and len(r) < self.prec:
+            r = '0'*(self.prec - len(r)) + r
+        if self.flags.f_alt:
+            r = '0x' + r
         if self.char == 'X':
             r = r.upper()
-        return self.std_wp(r)
+        return self.numeric_postprocess(r, sign)
+
 
 class OctFormatter(Formatter):
+    # NB: this has 2.4 semantics wrt. negative values
     def format(self):
-        i = maybe_int(self.value)
-        r = oct(i)
+        v, sign = self.numeric_preprocess(maybe_int(self.value))
+        r = oct(v)
         if not self.flags.f_alt:
             r = r[1:]
-        return self.std_wp(r)
+        if self.prec is not None and len(r) < self.prec:
+            r = '0'*(self.prec - len(r)) + r
+        return self.numeric_postprocess(r, sign)
+
+
+class IntFormatter(Formatter):
+    # NB: this has 2.4 semantics wrt. negative values (for %u)
+    def format(self):
+        v, sign = self.numeric_preprocess(maybe_int(self.value))
+        r = str(v)
+        if self.prec is not None and len(r) < self.prec:
+            r = '0'*(self.prec - len(r)) + r
+        return self.numeric_postprocess(r, sign)
+
+
+class CharFormatter(Formatter):
+    def format(self):
+        if isinstance(self.value, str):
+            v = self.value
+            if len(v) != 1:
+                raise TypeError, "%c requires int or char"
+        else:
+            i = maybe_int(self.value)
+            if not 0 <= i <= 255:
+                raise OverflowError("OverflowError: unsigned byte "
+                                    "integer is greater than maximum")
+            v = chr(i)
+        self.prec = None
+        return self.std_wp(v)
+
 
 format_registry = {
-    's':funcFormatter(str),
-    'r':funcFormatter(repr),
+    'd':IntFormatter,
+    'i':IntFormatter,
+    'o':OctFormatter,
+    'u':IntFormatter,
     'x':HexFormatter,
     'X':HexFormatter,
-    'o':OctFormatter,
-    'd':funcFormatter(maybe_int, str),
-    'e':floatEFormatter,
-    'E':floatEFormatter,
-    'f':floatFFormatter,
-    'g':floatGFormatter,
+    'e':FloatEFormatter,
+    'E':FloatEFormatter,
+    'f':FloatFFormatter,
+    'F':FloatFFormatter,
+    'g':FloatGFormatter,
+    'G':FloatGFormatter,
+    'c':CharFormatter,
+    's':funcFormatter(str),
+    'r':funcFormatter(repr),
+    # this *can* get accessed, by e.g. '%()4%'%{'':1}.
+    # The usual %% case has to be handled specially as it
+    # doesn't consume a value.
+    '%':funcFormatter(lambda x:'%'),
     }
+
     
 class FmtIter(object):
     def __init__(self, fmt):
         self.fmt = fmt
         self.i = 0
+
     def __iter__(self):
         return self
+
     def next(self):
         try:
             c = self.fmt[self.i]
@@ -440,6 +352,7 @@
             raise StopIteration
         self.i += 1
         return c
+
     def skip_to_fmt(self):
         i = self.i
         j = self.fmt.find('%', i)
@@ -450,6 +363,7 @@
             self.i = j
             return self.fmt[i:j]
 
+
 def format(fmt, values, valuedict=None):
     fmtiter = FmtIter(fmt)
     valueiter = iter(values)



More information about the Pypy-commit mailing list