[Python-ideas] Yet another sum function (fractions.sum)
Oscar Benjamin
oscar.j.benjamin at gmail.com
Tue Aug 20 19:30:30 CEST 2013
On 19 August 2013 11:09, Peter Otten <__peter__ at web.de> wrote:
> Oscar Benjamin wrote:
>
> If that takes on, and the number of sum implementations grows, maybe there
> should be a __sum__() special (class) method, and the sum built-in be
> changed roughly to
>
> def sum(items, start=0):
> try:
> specialized_sum = start.__sum__
> except AttributeError:
> return ... # current behaviour
> return specialized_sum(items, start)
>
> sum(items, 0.0) would then automatically profit from the clever
> optimizations of math.fsum() etc.
fsum() is about controlling accumulated rounding errors rather than
optimisation (although it may be faster I've never needed to check).
I'd rather write
sum(items, Fraction)
than
sum(items, Fraction(0))
and either way it's so close to
Fraction.sum(items)
I understand what you mean about having a single function that does a
good job for all types and it goes into a much broader set of issues
around how Python handles different numeric types. It would be
possible in a backward compatible way provided Fraction.__sum__ falls
back on sum when it finds a non-Rational.
There are lots of other areas in the stdlib where a similar sort of
thinking could apply e.g.:
>>> import math
>>> from decimal import Decimal as D
>>> from fractions import Fraction as F
There's currently no fsum equivalent for Decimals even though a pure
Python implementation could have reasonable performance:
>>> math.fsum([0.1, 0.2, 0.3]) == 0.6
True
>>> math.fsum([D('0.1'), D('0.2'), D('0.3')]) == D('0.6')
False
>>> D(math.fsum([D('0.1'), D('0.2'), D('0.3')])) == D('0.6')
False
sum() would do better in the above but then it fails in other situations:
>>> sum([D('1e50'), D('1'), D('-1e50')]) == 1
False
>>> math.fsum([D('1e50'), D('1'), D('-1e50')]) == 1
True
The math module rounds everything to float losing precision even if
better routines are available:
>>> math.sqrt(D('0.02'))
0.1414213562373095
>>> D('0.02').sqrt()
Decimal('0.1414213562373095048801688724')
>>> math.exp(D(1))
2.718281828459045
>>> D(1).exp()
Decimal('2.718281828459045235360287471')
There's also no support for computing the other transcendental
functions with Decimals e.g. sin, cos etc. without rounding to float.
The math module also fails to find exact results when possible:
>>> a = 10 ** 20 + 1
>>> a
100000000000000000001
>>> math.sqrt(a**2) == a
False
>>> b = F(2, 3)
>>> math.sqrt(b ** 2) == b
False
These ones could just get fixed in the Fractions module:
>>> F(4, 9) ** F(1, 2)
0.6666666666666666
>>> F(2, 3) ** 2
Fraction(4, 9)
>>> a
100000000000000000001
>>> (a ** 2) ** F(1, 2) == a
False
Do you think that any of these things should also be changed so that
there can be e.g. one sqrt() function that does the right thing for
all types? One way to unify these things is with a load of new dunder
methods so that each type can implement its own response to the
standard function. Another is to have special modules that deal with
different things and e.g. a function for summing Rationals and another
for summing inexact types and so on.
Another possibility is that you have decimal functions in the decimal
module and fraction functions in the fractions module and so on and
don't use any duck-typing (except in coercion). That may seem strange
for Python but it's worth remembering that most of the best numerical
code that has ever been written was written in languages that
*require* you to write separate code for each numeric type and don't
give any syntactic support for working with non-native types.
Oscar
More information about the Python-ideas
mailing list