from decimal import getcontext, Decimal, Inexact # # The following sum methods obtained from python cookbook article 393090 # by Raymond Hettinger. # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/393090 # def msum(iterable): "Full precision summation using multiple floats for intermediate values" # Rounded x+y stored in hi with the round-off stored in lo. Together # hi+lo are exactly equal to x+y. The inner loop applies hi/lo summation # to each partial so that the list of partial sums remains exact. # Depends on IEEE-754 arithmetic guarantees. See proof of correctness at: # www-2.cs.cmu.edu/afs/cs/project/quake/public/papers/robust-arithmetic.ps partials = [] # sorted, non-overlapping partial sums for x in iterable: i = 0 for y in partials: if abs(x) < abs(y): x, y = y, x hi = x + y lo = y - (hi - x) if lo: partials[i] = lo i += 1 x = hi partials[i:] = [x] return sum(partials, 0.0) from math import frexp def lsum(iterable): "Full precision summation using long integers for intermediate values" # Transform (exactly) a float to m * 2 ** e where m and e are integers. # Adjust (tmant,texp) and (mant,exp) to make texp the common exponent. # Given a common exponent, the mantissas can be summed directly. tmant, texp = 0L, 0 for x in iterable: mant, exp = frexp(x) mant, exp = long(mant * 2.0 ** 53), exp-53 if texp > exp: tmant <<= texp - exp texp = exp else: mant <<= exp - texp tmant += mant return float(str(tmant)) * 2.0 ** texp def dsum(iterable): "Full precision summation using Decimal objects for intermediate values" # Transform (exactly) a float to m * 2 ** e where m and e are integers. # Convert (mant, exp) to a Decimal and add to the cumulative sum. # If the precision is too small for exact conversion and addition, # then retry with a larger precision. getcontext().traps[Inexact] = True total = Decimal(0) for x in iterable: mant, exp = frexp(x) mant, exp = int(mant * 2.0 ** 53), exp-53 while 1: try: total += mant * Decimal(2) ** exp break except Inexact: getcontext().prec += 1 return float(total) def time_rounding(n): from random import random, gauss, shuffle for i in range(n): vals = [7, 1e100, -7, -1e100, -9e-20, 8e-20] * 10 s = 0 for i in range(200): v = gauss(0, random()) ** 7 - s s += v vals.append(v) shuffle(vals) assert msum(vals) == lsum(vals) == dsum(vals)