-2

For rounding to the nearest 0.05, I am just dividing it by 0.05 and then multiply by 0.05. As described in other similar questions.

In [119]: (127.651//0.05)*0.05
Out[119]: 127.65

In [120]: (127.6501//0.05)*0.05
Out[120]: 127.65

In [121]: (127.65000001//0.05)*0.05
Out[121]: 127.65

In [122]: (127.65000000001//0.05)*0.05
Out[122]: 127.65

In [123]: (127.6500000000001//0.05)*0.05
Out[123]: 127.65

Till here its doing as expected. However for this special case:

In [124]: (127.650000000000000001//0.05)*0.05
Out[124]: 127.60000000000001

I would have expected 127.65 here.

Tried rounding off before dividing, but again.. strange. Not only i am getting unexpected result(expecting 127.65), but also it is giving results that is beyond 2 decimal places, which will result into failure in my further processing functions.

In [125]: (round(127.650000000000000001,5)//0.05)*0.05
Out[125]: 127.60000000000001

If i execute just the inner round, o/p is 127.65...

In [126]: round(127.650000000000000001,5)
Out[126]: 127.65

But on adding the divide and multiply logic.. the result becomes unexpected.

In [125]: (round(127.650000000000000001,5)//0.05)*0.05
Out[125]: 127.60000000000001

Should there be any datatype issue? or internal precision limitations of python?

how do i overcome it in an elegant way?

PS:

1) I am using python 2.7

2) I am eventually using it in a function. Which is giving unexpected results for this special case. I cannot control the input data quality and its data precision, as it is coming from reading a csv file to dataframe.

def RP(price):# Round down to the nearest price quotable ...i.e. multiple of 0.05
        return(round(price//0.05*0.05,2))
Wacao
  • 51
  • 9
  • 1
    More classic [Is floating point math broken?](https://stackoverflow.com/q/588004/3001761). If you want exact representation, consider using [`Decimal`](https://docs.python.org/3/library/decimal.html). – jonrsharpe Aug 16 '17 at 22:37
  • I'm very new to py, but I guess for floating points there will be issues. importing decimal will make it much precise I guess. Just `import decimal` – ihpar Aug 16 '17 at 22:37
  • Hint: what does `127.6500000000000056 == 127.65` give you? Does the answer surprise you? – juanpa.arrivillaga Aug 16 '17 at 22:44
  • Instead of surprising. It confused me further. In [159]: 127.6500000000000056 == 127.65 Out[159]: True After reading the duplicate answer link. I was expecting it to return False. – Wacao Aug 16 '17 at 22:56
  • @ihpar just adding import decimal isnt resolving. – Wacao Aug 16 '17 at 23:03
  • @Wacao You have to use it too. Sorry my initial comment was confusing. Here is a good tutorial on it please check that https://www.programiz.com/python-programming/numbers – ihpar Aug 16 '17 at 23:04
  • @ihpar Still doesnt work In [187]: from decimal import Decimal as D In [188]: D(127.65)//D(0.05)*D(0.05) Out[188]: Decimal('127.6000000000000070832228971') I am in a big trouble now. Not sure how many places in my code, this would have been creating issues and changing my data. Explicit datatype defination in other programming language is much better i believe after this case. – Wacao Aug 16 '17 at 23:19
  • @Wacao you shoud put numbers in quotes such as `from decimal import Decimal as D print(D('127.65')//D('0.05')*D('0.05')) ` will give output of `127.65` – ihpar Aug 16 '17 at 23:27
  • Why are you using `//` if you want to round to the _nearest_ 0.05? That'll round _down_ rather than to nearest. – Mark Dickinson Aug 17 '17 at 06:37
  • if you create a decimal from a floating point number, you've *already lost the precision*. Create it from a string (*"numbers in quotes"*, as @ihpar would have it) instead. This is discussed in the documentation I linked above. – jonrsharpe Aug 18 '17 at 06:47

1 Answers1

0

Finally the scaling up logic mentioned in is-floating-point-math-broken worked for me.

In [200]: (int(127.65*100)//5*5)/100.0
Out[200]: 127.65

In [201]:

In [201]: (int(127.65000000000000001*100)//5*5)/100.0
Out[201]: 127.65

or :

In [286]: int(127.65*20)/20.0
Out[286]: 127.65

In [287]: int(127.6500000000000000000000001*20)/20.0
Out[287]: 127.65

UPDATE:

For my intended purpose, (rounding to the nearest 0.05 to get the price quote), so far below is the most closest solution that i can find after so many days of search and checking results on multiple values and iterations.

def RP(price):# Round to the nearest price quotable ...i.e. multiple of 0.05
        return(round(price*20.0,0)/20.0)

def RP_down(price):# Round_Down to the price quotable ...i.e. multiple of 0.05
        return(int(price*20.0)/20.0)

def RP_up(price):# Round_UP to the price quotable ...i.e. multiple of 0.05
        return(0.05 + int(price*20.0)/20.0)

Why it works? The below explanation follows:

Multiply by 20.0 followed by round() or int(), changes the domain from floating point to integers.

And so, eliminates the precision problem, that led to the unexpected results.

Note: That in the last step (divide by 20.0), we again move from integer to floating point, but with the correct expected value(no surprises so far after running in production from past one week). And you know it for sure that it has only 2 decimal places theoretically (an integer divide by 20), so if need be, you can handle it in the rest of your program by chopping off the rest, while printing.

Wacao
  • 51
  • 9
  • 1
    This is not a good solution. It may "work" for you for those couple of cases that you tested, but you'll eventually find other examples where things round the "wrong" way. – Mark Dickinson Aug 17 '17 at 06:43
  • @Mark Updated the answer. Unfortunately other ways like using 'Decimal' cant work for me because the data input is already in float and the precision is already lost. Surprisingly, there is a lot of documentation on this problem, but majority of it is about explaining the problem. Very few for solutions. So the scaling-up logic was the best option(i may be wrong) that i could have used to handle this case. Should you have any other solution, please convey. – Wacao Aug 25 '17 at 23:06