[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