0

I try to do a simple loop in Python. The loop is supposed to stop if d <= 4. Unfortunately, due to the Floating Point Arithmetic problem, the last calculus gives 4.000000000000001 (instead of 4) and then stop the loop. Is there a simple solution for this kind of problem ?

Input

import matplotlib.pyplot as plt
import numpy as np

A = 4
B = 2
C = 0.1

d = 0
while d <= A: 
    d += B*C
    print(d)

Output

0.2
0.4
0.6000000000000001
0.8
1.0
1.2
1.4
1.5999999999999999
1.7999999999999998
1.9999999999999998
2.1999999999999997
2.4
2.6
2.8000000000000003
3.0000000000000004
3.2000000000000006
3.400000000000001
3.600000000000001
3.800000000000001
4.000000000000001
Peter Estiven
  • 414
  • 1
  • 7
  • 16

3 Answers3

2

The printing has nothing to do with floating point math.

Your loop

  1. tests if d <= 4
    1. calculates new d
    2. prints d - with whatever value it has
d = 0
while d <= A:   # here d <= 4
    d += B*C    # here is is recomputed and now bigger 4
    print(d)    # here it gets printed

You need to test before printing with the current value - or print first, then recalculate :

A = 4
B = 2
C = 0.1

d = 0
while True: 
    d += B*C
    if d > A:
        break
    print(d)

To avoid rounding errors accumulating, change your calculation to something non-incremental:

A = 4
B = 2
C = 0.1

d=0
rounds = 0
while d < A:
    rounds += 1
    d = B*C*rounds  # do not accumulate errors: kinda hackish though
    print(d)

Output:

0.2
0.4
0.6000000000000001
0.8
1.0
1.2000000000000002
1.4000000000000001
1.6
1.8
2.0
2.2
2.4000000000000004
2.6
2.8000000000000003
3.0
3.2
3.4000000000000004
3.6
3.8000000000000003
4.0
Patrick Artner
  • 50,409
  • 9
  • 43
  • 69
  • The position of the print is not the problem. The result of the last calculus is "suposed" to be 4 and then pass the condition. But it's already more than 4. – Peter Estiven Jan 02 '19 at 13:54
  • @PatrickArtner If I'm not wrong, this code gives the same result. I can put the `print` before or after `d +=B*C`, it will be the same. I need to stop exactly at 4. – Peter Estiven Jan 02 '19 at 14:02
  • @PeterEstiven is your question a dupe of https://stackoverflow.com/questions/5595425/what-is-the-best-way-to-compare-floats-for-almost-equality-in-python ? ... `while not math.isclose(A, d, rel_tol=1e-5): ` – Patrick Artner Jan 02 '19 at 14:07
  • @PatrickArtner Yes, similar subject. But it's frustrating : there is nothing nicer or more intuitive than that ? I don't have this kind of problem with PHP for example. – Peter Estiven Jan 02 '19 at 14:11
  • @PeterEstiven you can avoid accumulation errors - see edit. – Patrick Artner Jan 02 '19 at 14:15
  • @PatrickArtner Thanks, it's better, but the problem will be the same if the condition is now 3.8. I will miss the value 3.8 like I was missing the value 4 in the original code. – Peter Estiven Jan 02 '19 at 14:18
  • My best solution so far is `isclose()` but still no completely satisfying. – Peter Estiven Jan 02 '19 at 14:31
  • @PatrickArtner, hack per hack, this removes any problem: `d = 10*B*C*rounds/10`. Check: `print(d == 4) #=> True`. If `C=0.01`, use `100` instead. – iGian Jan 02 '19 at 15:00
  • @iGian the delta added is about 0.2 - your hack gets rid of the decimal uncertainty :) maybe post it as own answer – Patrick Artner Jan 02 '19 at 15:03
  • @PeterEstiven PHP will have exactly the same problem, it might just be "helpfully" masked in some situations. floating-point arithmetic will **always** have this "problem", that's why `numpy` methods like `linspace` and `arange` exist — allowing you to create these upto machine-precision – Sam Mason Jan 02 '19 at 17:36
  • `d = B*C*rounds` may produce a value higher or lower than desired due to rounding, so this solution is not correct. – Eric Postpischil Jan 02 '19 at 20:49
1

Since d is larger than 4 (slightly) at the last iteration, d doesn't enter the loop at the next iteration and consequently doesn't print the 4.200 (I guess as you expect). To turn around this problem (these kinds of problems) you can use this structure while making comparisons. Just choose epsilon according to precision you need.

import matplotlib.pyplot as plt
import numpy as np

A = 4
B = 2
C = 0.1
epsilon = 0.001
d = 0
while d <= A + epsilon: 
    d += B*C
    print(d)

At the following link, one can find more elaborate, generic, and elegant version to solve this problem. https://www.learncpp.com/cpp-tutorial/35-relational-operators-comparisons/

akurmustafa
  • 122
  • 10
1

Note that this has nothing to do with Python per se. You'll have this problem with any language (e.g., C) that represents floating point values using IEEE 754 encoding or any similar binary encoding. That's because decimal values like 0.2 cannot be represented exactly in a binary format like IEEE 754. If you google "compare floating point values" you'll find quite a few papers describing this problem and how to deal with it. For example, this one.

Depending on your situation you may be better off using something like a scaled integer representation. For example, if your values always have a precision of 1/10th then simply scale by ten: 11 is 1.1, 38 is 3.8, etc. There is also the decimal module.

Kurtis Rader
  • 6,734
  • 13
  • 20