Floating point arithmetics: Possible unsafe reliance on specific comparison?
Hire the world's top talent on demand or became one of them at Toptal: https://topt.al/25cXVn
and get $2,000 discount on your first invoice
--------------------------------------------------
Music by Eric Matyas
https://www.soundimage.org
Track title: Puzzle Island
--
Chapters
00:00 Floating Point Arithmetics: Possible Unsafe Reliance On Specific Comparison?
01:11 Answer 1 Score 3
02:19 Accepted Answer Score 4
04:10 Answer 3 Score 1
04:28 Thank you
--
Full question
https://stackoverflow.com/questions/8870...
--
Content licensed under CC BY-SA
https://meta.stackexchange.com/help/lice...
--
Tags
#python #languageagnostic #floatingpoint
#avk47
ACCEPTED ANSWER
Score 4
You can use the decimal module to get an idea of what "hides" between a floating point number such as 0.3:
>>> from decimal import Decimal
>>> Decimal(0.3)
Decimal('0.299999999999999988897769753748434595763683319091796875')
Note that Python 2.7 changed how floating point numbers are written (how repr(f) works) so that it now shows the shortest string that will give the same floating point number if you do float(s). This means that repr(0.3) == '0.3' in Python 2.7, but repr(0.3) == '0.29999999999999999' in earlier versions. I'm mentioning this since it can confuse things further when you really want to see what's behind the numbers.
Using the decimal module, we can see the error in a computation with floats:
>>> (Decimal(2.0) - Decimal(1.1)) / Decimal(0.3) - Decimal(3)
Decimal('-1.85037170771E-16')
Here we might expect (2.0 - 1.1) / 0.3 == 3.0, but there is a small non-zero difference. However, if you do the computation with normal floating point numbers, then you do get zero:
>>> (2 - 1.1) / 0.3 - 3
0.0
>>> bool((2 - 1.1) / 0.3 - 3)
False
The result is rounded somewhere along the way since 1.85e-16 is non-zero:
>>> bool(-1.85037170771E-16)
True
I'm unsure exactly where this rounding takes place.
As for the loop termination in general, then there's one clue I can offer: for floats less than 253, IEEE 754 can represent all integers:
>>> 2.0**53
9007199254740992.0
>>> 2.0**53 + 1
9007199254740992.0
>>> 2.0**53 + 2
9007199254740994.0
The space between representable numbers is 2 from 253 to 254, as shown above. But if your i is an integer less than 253, then i - 1 will also be a representable integer and you will eventually hit 0.0, which is considered false in Python.
ANSWER 2
Score 3
I will give you a language-agnostic answer (I don't really know Python).
There are multiple potential problems in your code. Firstly, this:
(a - b - c)
If a is (for example) 109, and b and c are both 1, then the answer will be 109, not 109-2 (I'm assuming single-precision float here).
Then there's this:
i = (a - b - c) / d
If numerator and denominator are numbers that can't be exactly represented in floating-point (e.g. 0.3 and 0.1), then the result might not be an exact integer (it might be 3.0000001 instead of 3). Therefore, your loop will never terminate.
Then there's this:
i -= 1
Similarly to above, if i is currently 109, then the result of this operation will still be 109, so your loop will never terminate.
Therefore, you should strongly consider performing all the calculations in integer arithmetic.
ANSWER 3
Score 1
You're right that there could be a non-convergence on zero (at least for more iterations than you intend). Why not have your test be: while i >= 1. In that case, as with integers, if your i value dips below 1, the loop will end.