for / while else doesn't make sense

Steven D'Aprano steve at pearwood.info
Mon May 23 22:15:25 EDT 2016


On Tue, 24 May 2016 03:09 am, Jon Ribbens wrote:

> On 2016-05-23, Steven D'Aprano <steve at pearwood.info> wrote:
>> But one thing is certain: very few people, Jon Ribbens being one of them,
>> expects 1/3 to return 0. And that is why Python changed the meaning of
>> the / operator: because using it for integer division was deeply
>> unpopular and a bug magnet.
> 
> Making it return floats is also a bug magnet, just for more subtle
> bugs that are harder to diagnose.


Floating point arithmetic does contain traps for the unwary, sometimes very
subtle ones. But they aren't *bugs* -- they're inherent in the nature of
floating point arithmetic.

Using / for integer division is a bug magnet. See my response to
the "Interfacing a dynamic shared library gives me different results in 2.7
versus 3.5" thread:

https://mail.python.org/pipermail/python-list/2016-May/709330.html

The old way of using / was a bug magnet, because people would write code
like this:

def harmonic_mean(values):
    n = len(values)
    total = sum(1/x for x in values)
    return n/total


and test it with floats:

py> harmonic_mean([1.0, 2.0, 4.0])
1.7142857142857142

which matches the exact result of 12/7. And then some day somebody sneaks in
an integer by mistake, and the result goes completely to hell:

py> harmonic_mean([1.0, 2, 4.0])
2.4


Changing / to perform true division slays this particular bug magnet,
without introducing any new ones.


Floats themselves are an abstraction for real mathematical arithmetic.
(Perhaps a better term is "reification".) And for most purposes, they work
well-enough, especially with IEE-754 arithmetic. You can write a lot of
naive maths code treating floats as if they were exact Real numbers, and
with a few judicious tweaks to your printing routines, nobody will know the
difference.

But all abstractions (and reifications) leak:

http://www.joelonsoftware.com/articles/LeakyAbstractions.html

and floats are no exception. But what are you going to do? Use integer
maths? You still have to deal with rounding error.

In Australia, we have an 11% consumption tax, the GST. I cannot tell you how
many times I've needed to add a 1 cent "Rounding" amount on invoices to get
the results to work out correctly. E.g. if an item costs $17 including tax,
then you have a choice in rounding the tax-free cost down to $15.31 or up
to $15.32, depending on the rounding mode, which in turn gives you the
tax-inclusive cost of either $16.99 or $17.01. Either way you need a one
cent adjustment.

If you use integer maths, it always rounds down:

py> 1700*100//111  # $17.00/1.11 calculated in cents
1531
py> 1531*111//100
1699

(Reminds me of using Forth without a floating point stack.) The right
solution is actually to use a Decimal format, or a correctly rounded
IEEE-754 integer format. But even that will still leak somewhere.

Using integers in this case is not only *harder* than using floats, but it's
more likely to go wrong. Because integer division always rounds down,
errors accumulate faster than with floats, where calculations will be
correctly rounded to minimize the error.



-- 
Steven




More information about the Python-list mailing list