Old Man Yells At Cloud

Steve D'Aprano steve+python at pearwood.info
Mon Sep 18 21:44:34 EDT 2017


On Tue, 19 Sep 2017 12:31 am, Ben Bacarisse wrote:

> Was the result of 1/2 determined
> by a poll to find out what most people expected?

No. It was determined by the fact that a decade or more of experience with the
language demonstrated that the Python 2 behaviour was a terrible mistake and a
continual source of bugs in code because the / operator would silently change
its function and return garbage results depending on the precise type of the
numeric arguments.

I specify *numeric* arguments because I don't want people derailing this in a
general rant about operator overloading. I'm not talking about "if you pass a
Widget instead of a float, you get an unexpected different Widget".

Rather, I am talking about "if you pass 2 instead of 2.0, you get an answer of
34.72 instead of the correct answer of 35.47" kinds of errors. Silent failures
that return garbage results.

A simple (if perhaps artificial) example: harmonic mean.

>>> 1/sum(1/x for x in [1.0, 2.0, 5.5, 3.2, 4.0])  # Correct.
0.4455696202531646

>>> 1/sum(1/x for x in [1.0, 2, 5.5, 3.2, 4.0])  # Oops.
0.57328990228013033


> But there is a stronger claim (which I think you also made) that a
> floating point result is the correct one.  However, some people with
> little experience of floating point arithmetic (I certainly can't say
> most but it must be quite few) will expect
> 
>   1/10
> 
> to return a tenth.  For /them/, the floating point result is silently
> wrong and a source of bugs.

It does return a tenth, or rather the closest possible result to a tenth
representable.

It is true that binary floats have some unexpected properties. They aren't the
real numbers that we learn in maths. But most people who have been to school
have years of experience with calculators training them to expect computer
calculations are sometimes "off". They do a sum and get 2.999999999 instead of
3, or perhaps 3.000000001, and that's just the way calculators work.

And then a small but vocal minority get a bit more experience with computers and
forget what they had already learned and make the novice mistake of expecting
floats to be infinitely precise real numbers, then complain that "Python is
inaccurate" on the bug tracker. Of course they don't bother to do even a
cursory search before raising an issue, or read the documentation, or the FAQs,
or do the tutorial.



> If I were aiming a language at beginners, 
> I'd make 1/10 be a rational or a type error.

You would make it a type error? Why, do you hate beginners and want to punish
them for typing to learn your language?

Fortunately Python is not a language that is purely aimed at beginners. Python
doesn't go out of its way to be hostile to anyone, let alone beginners, but it
doesn't compromise or stint on functionality just so that novices to
programming have a microscopically easier first week.
 

> However, I don't think that is Python's main demographic, so 1/10 giving
> something not quite one tenth may well be the correct design choice.

Indeed. There are three obvious choices for regular division of integers. All
have disadvantages:


- Return a binary float. This has the advantage that it is the most common, and
the fastest, and that computationally binary floats have the smallest "wobble"
of any numeric base. If you are serious about floating point accuracy and
stability, you probably want binary floats. But like all floating point
formats, it has the disadvantages that many calculations can suffer from
rounding errors, and that certain "ordinary looking" decimal values, like 0.1,
cannot be represented exactly.


- Return a decimal float. This has the advantage that "ordinary looking" values
like 0.1 can be represented exactly, but otherwise is subject to precisely the
same rounding errors as binary floats, plus the additional problem that the
errors can be significantly larger, leading to even more numeric instability.
And just like binary, there are still ordinary fractions which cannot be
expressed exactly, like 1/3.


- Return an exact rational number, a fraction. While these are exact, this has
the disadvantage that fractions are not as practical as decimals for humans.
Rational arithmetic is also much slower than floating point, and that many
perfectly innocent-looking calculations can explode into ridiculously huge
numbers of digits, leading to excessive memory use and even more performance
degradation.


Guido strongly dislikes the rational option because of his experience with ABC,
where simple-looking calculations would often grind to a halt as they
calculated fractions with millions or billions of digits in the numerators and
denominators, for numbers which were well within the range of a float.


-- 
Steve
“Cheer up,” they said, “things could be worse.” So I cheered up, and sure
enough, things got worse.




More information about the Python-list mailing list