Python Forum

Full Version: Weird rounding
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
Hi everyone.

The post seems long but it won't take much of your time, I promise :)
This is the code:

def frange(start, stop, step):
    
    i = start
    while i <= stop:
        yield i
        i += step

for p in frange(5, 6.9, 0.01):
    print(str(p))
First few print outputs are:
Output:
5 5.01 5.02 5.029999999999999 5.039999999999999 5.049999999999999 5.059999999999999 5.0699999999999985 5.079999999999998 5.089999999999998 5.099999999999998
and all of the rest is the same.

1. Why is this happening?

2. How to work around/with it?

What I have done is

def frange(start, stop, step):
    
    i = start
    while i <= stop:
        i = float("{0:.2f}".format(i))
        yield i
        i += step
and then the print is fine. But that's not my main issue. My main issue happens here:

def gmaxf(d):
    
    if d == 13: return 32
    if d == 16: return 38
    if d == 20: return 40
    if d == 25: return 44
    if d == 32: return 50
    if d == 40: return 56
    return 0

def wminf(d):
    
    if d == 13: return 5
    if d == 16: return 6
    if d == 20: return 8
    if d == 25: return 10
    if d == 32: return 11.5
    if d == 40: return 14
    return 0

for d in diameters:
    pmin = wminf(d)
    pmax = d + 0.09
    gmin = d + 0.1
    gmax = gmaxf(d)
    for p in frange(wminf(d), pmax, 0.01):
        for w in frange(wminf(d), p, 0.01):
            rmin = rminf(p, w, gmin, gmax)
            rmax = rmaxf(p, w, gmin, gmax)
            if rmin > rmax:
                pmin = p + 0.01
    print('D = ' + str(d) + ', Pmin = ' + str(pmin))
And this is the output:

Output:
D = 13, Pmin = 12.2 D = 16, Pmin = 15.03 D = 20, Pmin = 18.540000000000003 D = 25, Pmin = 23.12 D = 32, Pmin = 30.060000000000002 D = 40, Pmin = 37.669999999999995
As you can see, I'm trying to found out minimum possible value for Pmin. rminf and rmaxf do not in any way change any of the variables' values, they simply calculate something and return it. I don't want to just round-up the value for Pmin because I have no idea what is going on. For example, I can assume that Pmin = 30.06 for D = 32. But what the heck is going on with Pmin for D = 40?

From both cases I can see that the problem occurs within p + 0.01. Why? What's going on?

Thank you! :)
That's just how floating point numbers work. https://floating-point-gui.de/

The simple answers are to either:
- ignore it, and format numbers before printing them, or
- use the Decimal module, which favors precision the way you're expecting: https://docs.python.org/3/library/decimal.html
(Oct-12-2018, 06:55 PM)nilamo Wrote: [ -> ]That's just how floating point numbers work. https://floating-point-gui.de/

The simple answers are to either:
- ignore it, and format numbers before printing them, or
- use the Decimal module, which favors precision the way you're expecting: https://docs.python.org/3/library/decimal.html

Will precision remain if I do this?

for d in diameters:
    pmin = wminf(d)
    pmax = d + 0.09
    gmin = d + 0.1
    gmax = gmaxf(d)
    for p in frange(wminf(d), pmax, 0.01):
        for w in frange(wminf(d), p, 0.01):
            rmin = rminf(p, w, gmin, gmax)
            rmax = rmaxf(p, w, gmin, gmax)
            if rmin > rmax:
                pmin = float("{0:.2f}".format(p + 0.01))
    print('D = ' + str(d) + ', Pmin = ' + str(pmin))
If I round it up after every 0.01 addition? It should, right?
No, you lose any semblance of precision if you add random numbers, then cast to a string and back to a float.
Thank you for your time so far.
Becuase P is always x.yz number (only 2 decimals, or 1, or none), would this be good enough?

pmin = (int(p*100) + 1 ) / 100
That way addition would be done with two integers and the result is simply integer/100.
As you can tell, I am trying to avoid Decimal module if possible :)
(Oct-12-2018, 07:39 PM)PythonZenon Wrote: [ -> ]As you can tell, I am trying to avoid Decimal module if possible :)
But why?
Any why isn't float good enough? Is it actually acting in a way you don't expect, or is it just displaying poorly when printed?
The data base at my old job stored all the weights as integers. They were actually calculated to four decimal places. You would do calculations on them and then divide by 10000 for the final result. We would do something similar when checking the register at the sports card store I worked at: You did all your calculations in pennies instead of dollars, so you didn't have to worry about an incorrect decimal.
(Oct-12-2018, 07:46 PM)nilamo Wrote: [ -> ]But why?
Any why isn't float good enough? Is it actually acting in a way you don't expect, or is it just displaying poorly when printed?

Honestly, because there is just SO MUCH TEXT I would have to read about Decimal module and I have spent so much time typing this code already. I just want to get it done. If i decide to implement Decimal module in my entire program, it's gonna take even more time because there are additional functions I have not shown here.

I dislike doing it with float because there might be many many additions while running algorithm and the error gets bigger and bigger and I lose precision. If I do it with doing *100 and converting to int, I have rounded-up number in each iteration of 'for' and error doesn't get bigger, right?

(Oct-12-2018, 07:48 PM)ichabod801 Wrote: [ -> ]The data base at my old job stored all the weights as integers. They were actually calculated to four decimal places. You would do calculations on them and then divide by 10000 for the final result. We would do something similar when checking the register at the sports card store I worked at: You did all your calculations in pennies instead of dollars, so you didn't have to worry about an incorrect decimal.

And this is what I shall do as well :) Thank you both!
Simple modification with round
Output:
In [32]: def frange(start, stop, step, precision=2): ...: while start <= stop: ...: yield start ...: start = round(start + step, precision) ...: list(frange(5, 5.1, .01)) ...: ...: Out[32]: [5, 5.01, 5.02, 5.03, 5.04, 5.05, 5.06, 5.07, 5.08, 5.09, 5.1]
Actually, you may even caclulate precision "on the fly"
Output:
In [36]: def frange(start, stop, step): ...: precision = len(str(step).partition('.')[-1]) ...: while start <= stop: ...: yield start ...: start = round(start + step, precision) ...: print(list(frange(5, 10, 1.1)), list(frange(5, 10, 1)), sep='\n') ...: ...: [5, 6.1, 7.2, 8.3, 9.4] [5, 6, 7, 8, 9, 10]

(Oct-12-2018, 06:37 PM)PythonZenon Wrote: [ -> ]
def gmaxf(d):
    
    if d == 13: return 32
    if d == 16: return 38
    if d == 20: return 40
    if d == 25: return 44
    if d == 32: return 50
    if d == 40: return 56
    return 0

There's a better way to implement that in Python, Pythonic way - by using dict instead of long chain of if's
def gmaxf(d):
    return {13: 32, 16: 38,....}.get(d, 0)