0

I have some issues with floating point numbers and rounding them.

I have a function that takes a dictionary as an input. It finds the average of all the values and then calculates the difference between each value and the average. The answers should be rounded to two decimal places.

I can't figure out why it's not returning floating point values that are properly rounded. The funny thing is that when I print out x[i] inside the for loop, it looks exactly how I want it. Also, I want to keep the values a number and not a formatted string.

def avgstuff(x):
    ppv = sum(x.values())/float(len(x))
    for i in x:
        x[i] = round(x[i] - ppv,2)
    return x

print avgstuff({'A': 40, 'C': 10, 'B': 25, 'E': 58, 'D': 153})

current output:
{'A': -17.199999999999999, 'C': -47.200000000000003, 'B': -32.200000000000003, 'E': 0.80000000000000004, 'D': 95.799999999999997}

Output I want:
{'A': -17.20, 'C': -47.20, 'B': -32.20, 'E': 0.80, 'D': 95.80}

Output I don't want:
{'A': '-17.20', 'C': '-47.20', 'B': '-32.20', 'E': '0.80', 'D': '95.80'}

timgeb
  • 64,821
  • 18
  • 95
  • 124
  • 4
    You seem to be getting the odd float effect mentioned here: http://stackoverflow.com/questions/56820/round-in-python-doesnt-seem-to-be-rounding-properly I'll have an answer up shortly. – NuclearPeon Dec 21 '15 at 23:08
  • 2
    I tried your code and my output with your given input is {'A': -17.2, 'C': -47.2, 'B': -32.2, 'E': 0.8, 'D': 95.8} – Rockybilly Dec 21 '15 at 23:27
  • 2
    They have been rounded to two decimal places, actually. If doesn't suit you, perhaps you ought to look at the decimal module. – saulspatz Dec 21 '15 at 23:28
  • That's a surprising output from Python 2.7, but it's what I'd expect from Python 2.6. Are you sure that you're using 2.7 and not 2.6? (Another possible explanation for that output is that you've got `numpy.float64` instances rather than Python `float`s. Are you using NumPy?) – Mark Dickinson Dec 22 '15 at 09:06
  • And a second question: why do you care about the difference? If you're doing further computation with the results, then the tiny errors you're seeing here are unlikely to have a significant effect (depending on what exactly you're doing, of course). If you're *not* doing further computation and this is just about how things look on the screen, then you want to be using string formatting. What's your application, and how do things go wrong if you simply ignore these tiny errors? – Mark Dickinson Dec 22 '15 at 09:19

2 Answers2

1

The problem here is that certain real numbers (0.2 for example) cannot exactly be converted to binary. You would need an infinite amount of bits.

0.2 * 2 = 0 remainder 0.4
0.4 * 2 = 0 remainder 0.8
0.8 * 2 = 1 remainder 0.6
0.6 * 2 = 1 remainder 0.2

and from here it would just start again...

=> (0.2)_dec = (0.001100110011...)_bin

timgeb
  • 64,821
  • 18
  • 95
  • 124
0

Use the Decimal library:

from decimal import *
def avgstuff(x):
    ppv = sum(x.values())/len(x)
    for i in x:
        x[i] = Decimal(x[i]-ppv).quantize(Decimal('0.01'), rounding=ROUND_HALF_DOWN)

    return x

See https://docs.python.org/2/library/decimal.html for more info

If you want floats to be returned and not Decimal objects, use the line in the loop:

x[i] = float(Decimal(x[i]-ppv).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP))
NuclearPeon
  • 4,789
  • 3
  • 40
  • 47
  • Your suggested conversion back to float does nothing more than give a long-winded way to do a binary floating-point round (except that you've got unusual behaviour for halfway cases here). – Mark Dickinson Dec 22 '15 at 09:10
  • Hmmm, would you expound your comment a bit further? What is the unusual behaviour for halfway cases? – NuclearPeon Dec 22 '15 at 18:10
  • I just meant the choice of `ROUND_HALF_DOWN`, which is both (a) a little unusual, and (b) different from the rounding mode used for regular `float`s (which is either `ROUND_HALF_UP` or `ROUND_HALF_EVEN`, depending on whether you're looking at Python 2 or Python 3, respectively). – Mark Dickinson Dec 22 '15 at 18:45
  • @MarkDickinson oh sorry, it's been a while since I've done much with math; one of my previous answers was about rounding down so that was still on my brain. You are correct, it should be `ROUND_HALF_UP`. – NuclearPeon Dec 22 '15 at 22:01