views:

689

answers:

6

If you put a decimal in a format where has to be rounded to the nearest 10th, and it is: 1.55, it'll round to 1.5. 1.56 will then round to 1.6. In school I recall learning that you round up when you reach five, and down if it's 4 or below. Why is it different in Python, et al.

Here's a code example for Python 2.6x (whatever the latest version is)

'{0:01.2f}'.format(5.555)  # This will return '5.55'

After trying some of the examples provided, I realized something even more confusing:

'{0:01.1f}'.format(5.55)  # This will return '5.5'
# But then
'{0:01.1f}'.format(1.55)  # This will return '1.6'

Why the difference when using 1.55 vs 5.55. Both are typed as literals (so floats)

+6  A: 

There are many ways to round numbers. You can read more about rounding on Wikipedia. The rounding method used in Python is Round half away from zero and the rounding method you are describing is more or less the same (at least for positive numbers).

Martin Liversage
Isn't the question saying that "Python, et. al." are **not** doing Round half up? I think the point of the question is Round half up is the most common method and the asker is not seeing that behaviour. Although in my experience Python and other languages **do** use Round half up which is why I'm puzzled.
Dave Webb
+2  A: 

Can you give some example code, because that's not the behaviour I see in Python:

>>> "%.1f" % 1.54
'1.5'
>>> "%.1f" % 1.55
'1.6'
>>> "%.1f" % 1.56
'1.6'
Dave Webb
+2  A: 

This doesn't appear to be the case. You're using the "float" string formatter, right?

>>> "%0.2f" % 1.55
'1.55'
>>> "%0.1f" % 1.55
'1.6'
>>> "%0.0f" % 1.55
'2'
Travis Bradshaw
+2  A: 

Rounding and truncation is different for every programming language, so your question is probably directly related to Python.

However, rounding as a practice depends on your methodology.

You also should know that CONVERTING a decimal to a whole number in many programming languages yields different results from actually rounding the number.

Edit: Per some of the other posters, it seems that Python does not exhibit the rounding behavior you've described:

>>> "%0.2f" % 1.55 
'1.55' 
>>> "%0.1f" % 1.55 
'1.6' 
>>> "%0.0f" % 1.55 
'2' 
Ed Altorfer
Weird, it does do what I was saying if you have %0.2f and you go 1.555, you'll get 1.55, but then with %0.1f and 1.55, you get 1.6. So inconsistent.
orokusaki
That's because "1.555", rounded to binary floating point, is just barely on the *other* side of the decimal 1.555 vs. what happens with 1.55.
Stephen Canon
+50  A: 

First off, in most languages an undecorated constant like "1.55" is treated as a double precision value. However, 1.55 is not exactly representable as double precision value, because it doesn't have a terminating representation in binary. This causes many curious behaviors, but one effect is that when you type 1.55, you don't actually get the value that's exactly halfway between 1.5 and 1.6.

In binary, the decimal number 1.55 is:

1.10001100110011001100110011001100110011001100110011001100110011001100...

When you type "1.55", this value actually gets rounded to the nearest representable double-precision value (on many systems... but there are exceptions, which I'll get to). This value is:

1.1000110011001100110011001100110011001100110011001101

which is slightly larger than 1.55; in decimal, it's exactly:

1.5500000000000000444089209850062616169452667236328125

So, when asked to round this value to a single digit after the decimal place, it will round up to 1.6. This is why most of the commenters have said that they can't duplicate the behavior that you're seeing.

But wait, on your system, "1.55" rounded down, not up. What's going on?

It could be a few different things, but the most likely is that you're on a platform (probably Windows), that defaults to doing floating-point arithmetic using x87 instructions, which use a different (80-bit) internal format. In the 80-bit format, 1.55 has the value:

1.100011001100110011001100110011001100110011001100110011001100110

which is slightly smaller than 1.55; in decimal, this number is:

1.54999999999999999995663191310057982263970188796520233154296875

Because it is just smaller than 1.55, it rounds down when it is rounded to one digit after the decimal point, giving the result "1.5" that you're observing.

FWIW: in most programming languages, the default rounding mode is actually "round to nearest, ties to even". It's just that when you specify fractional values in decimal, you'll almost never hit an exact halfway case, so it can be hard for a layperson to observe this. You can see it, though, if you look at how "1.5" is rounded to zero digits:

>>> "%.0f" % 0.5
'0'
>>> "%.0f" % 1.5
'2'

Note that both values round to even numbers; neither rounds to "1".

Edit: in your revised question, you seem to have switched to a different python interpreter, on which floating-point is done in the IEEE754 double type, not the x87 80bit type. Thus, "1.55" rounds up, as in my first example, but "5.55" converts to the following binary floating-point value:

101.10001100110011001100110011001100110011001100110011

which is exactly:

5.54999999999999982236431605997495353221893310546875

in decimal; since this is smaller than 5.55, it rounds down.

Stephen Canon
`"%.0f" % 0.5` returns `'1'` for me on Python 2.5.
Dave Webb
Dave: Interesting -- on Python 2.5.1 here it returns '0' for me, too. What platform are you on?
Ken
I believe that the python "%.0f" just invokes your system's C library version of `printf`. The C standard recommends, but doesn't require, that printf implement IEEE754 correct rounding for `printf` floating-point formatters. You may be on a system where the library provider has chosen not to do so.
Stephen Canon
My verbiage added some confusion as well though. I said "decimal formatting", but the actual type I'm working with is float. This, because I'm just typing literals into the interpreter rather than Decimal('1.55'), so yes it was being represented as a float.
orokusaki
+1 Excellent answer. If only all SO answers were like this.
Robert Harvey
@Ken - `"%0.f" % 0.5` returns '1' in both ActivePython 2.5.4 on Windows XP and ActivePython 2.6.4 on Windows 7.
Dave Webb
Epic answer. Respects :-)
e-satis
+1  A: 

I can't see a reason for the exact behaviour that you are describing. If your numbers are just examples, a similar scenario can be explained by bankers rounding being used:

1.5 rounds to 2
2.5 rounds to 2
3.5 rounds to 4
4.5 rounds to 4

I.e. a .5 value will be rounded to the nearest even whole number. The reason for this is that rounding a lot of numbers would even out in the long run. If a bank for example is to pay interrest to a million customers, and 10% of them ends up with a .5 cent value to be rounded, the bank would pay out $500 more if the values were rounded up instead.

Another reason for unexpected rounding is the precision of floating point numbers. Most numbers can't be represented exactly, so they are represented by the closest possible approximation. When you think that you have a number that is 1.55, you may actually ending up with a number like 1.54999. Rounding that number to one decimal would of course result in 1.5 rather than 1.6.

Guffa
You said that 3.5 rounds to 2 and 5.5 rounds to 4. This is a typo right?
orokusaki
Yes, it's a typo. I will correct it.
Guffa