[pypy-svn] r5630 - pypy/trunk/src/pypy/appspace
mwh at codespeak.net
mwh at codespeak.net
Fri Jul 23 12:46:25 CEST 2004
Author: mwh
Date: Fri Jul 23 12:46:24 2004
New Revision: 5630
Modified:
pypy/trunk/src/pypy/appspace/_formatting.py
Log:
a completely over-the-top implementation of converting floating
point numbers to strings. unfortunately, still not perfect (see
XXXes), but much better than what went before..
Modified: pypy/trunk/src/pypy/appspace/_formatting.py
==============================================================================
--- pypy/trunk/src/pypy/appspace/_formatting.py (original)
+++ pypy/trunk/src/pypy/appspace/_formatting.py Fri Jul 23 12:46:24 2004
@@ -1,3 +1,7 @@
+# Application level implementation of string formatting.
+
+# There's some insane stuff in here. Blame CPython. Please.
+
class _Flags(object):
def __repr__(self):
return "<%s>"%(', '.join([f for f in self.__dict__
@@ -96,6 +100,43 @@
self.width = width
self.prec = prec
self.value = value
+
+ def numeric_preprocess(self, v):
+ # negative zeroes?
+ # * mwh giggles, falls over
+ # still, if we can recognize them, here's the place to do it.
+ if v < 0:
+ sign = '-'
+ v = -v
+ else:
+ if self.flags.f_sign:
+ sign = '+'
+ elif self.flags.f_blank:
+ sign = ' '
+ else:
+ sign = ''
+ return v, sign
+
+ 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:
+ r = sign + r + ' '*p
+ else:
+ if self.flags.f_zero:
+ r = sign+padchar*p + r
+ else:
+ r = padchar*p + sign + r
+ else:
+ r = sign + r
+ return r
+
def format(self):
raise NotImplementedError
@@ -138,68 +179,222 @@
raise TypeError, "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...
+
+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
- r = str(int(v))
- # XXX this is a bit horrid
- if self.prec > 0:
- frac_part = str(v%1)[1:2+self.prec]
- if len(frac_part) < self.prec + 1:
- frac_part += (1 + self.prec - len(frac_part)) * '0'
- r += frac_part
- if v >= 0.0 and self.flags.f_sign:
- r = '+' + r
- self.prec = None
- return self.std_wp(r)
-import math
+ # we want self.prec digits after the radix point.
+
+ # this is probably more complex than it needs to be:
+ 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)))
+ else:
+ ds = ds[:p + k]
+ ds[k:k] = ['.']
+ elif k <= 0:
+ ds[0:0] = ['0']*(-k)
+ ds = ds[:p]
+ ds.extend(['0'] * (p - len(ds)))
+ ds[0:0]= ['0', '.']
+ elif k >= len(ds):
+ ds.extend((k-len(ds))*['0'] + ['.'] + ['0']*p)
+
+ 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
+
+ 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)
class floatGFormatter(Formatter):
+ # the description of %g in the Python documentation lies.
def format(self):
- # this needs to be much, much more complicated :-( (perhaps
- # should just punt to host Python -- which in turn punts to
- # libc)
v = maybe_float(self.value)
+
+ v, sign = self.numeric_preprocess(v)
+
if self.prec is None:
self.prec = 6
- i = 0
- if v == 0.0:
- if self.flags.f_sign:
- return '+0'
- else:
- return '0'
- elif v < 0.0:
- sign = '-'
- v = -v
- else:
- sign = '+'
- p = math.floor(math.log(v, 10))
- if p < 0:
- r = ['0', '.'] + ['0'] * (-int(p))
- r = []
- vv = v
- tol = abs(v*10.0**-self.prec)
- while 1:
- d = long(vv/10.0**p)
- vv -= d*10.0**p
- r.append(str(d))
- if abs(vv) <= tol:
- break
- p -= 1
- if p == -1:
- r.append('.')
- while p > 0:
- r.append('0')
- p -= 1
- r = ''.join(r)
- if sign == '-' or self.flags.f_sign:
- r = sign + r
- self.prec = None
- return self.std_wp(r)
+ 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)
+ else:
+ ds[1:1] = ['.']
+ r = ''.join(ds) + self.char + "%+03d"%(k-1,)
+
+ return self.numeric_postprocess(r, sign)
class HexFormatter(Formatter):
def format(self):
@@ -211,12 +406,23 @@
r = r.upper()
return self.std_wp(r)
+class OctFormatter(Formatter):
+ def format(self):
+ i = maybe_int(self.value)
+ r = oct(i)
+ if not self.flags.f_alt:
+ r = r[1:]
+ return self.std_wp(r)
+
format_registry = {
's':funcFormatter(str),
'r':funcFormatter(repr),
'x':HexFormatter,
'X':HexFormatter,
+ 'o':OctFormatter,
'd':funcFormatter(maybe_int, str),
+ 'e':floatEFormatter,
+ 'E':floatEFormatter,
'f':floatFFormatter,
'g':floatGFormatter,
}
More information about the Pypy-commit
mailing list