12

If I tell Python v. 3.4.3, round(2.5), then it outputs 2. If I tell it round(1.5) then it outputs 2 as well, though. Similarly, round(3.5) gives 4, while round(4.5) gives 4 as well. I need Python to round with consistency, though. Specifically, it needs to round anytime I input a number halfway between two integers. So round(1.5) = 1 and round(2.5) = 2, while round(1.6) = 2 and such, as usual.

How can I resolve this?

EDIT: I've already read the documentation for the round function and understand that this is its intended behavior. My question is, how can I alter this behavior, because for my purposes I need 1.5 round down.

James Pirlman
  • 229
  • 2
  • 5
  • 4
    possible duplicate of [Python 3.x rounding behavior](http://stackoverflow.com/questions/10825926/python-3-x-rounding-behavior) – tzaman Jul 07 '15 at 01:55
  • Note that this is not entirely a duplicate: the OP is mostly confused by the way Python 3 rounds, not by how it has changed from Python 2. –  Jul 07 '15 at 02:01
  • This is an important observation for most Python newcomers. Though surprising at first, Python 3 rounds much [more consistent than you might think](http://stackoverflow.com/a/40376443/2932052). – Wolf Nov 02 '16 at 09:31

7 Answers7

5

Python 3 uses a different rounding behaviour compared to Python 2: it now uses so-called "banker's rounding" (Wikipedia): when the integer part is odd, the number is rounded away from zero; when the integer part is even, is it rounded towards zero.

The reason for this is to avoid a bias, when all values at .5 are rounded away from zero (and then e.g. summed).

This is the behaviour you are seeing, and it is in fact consistent. It's perhaps just different than what you are used to.

  • It's not really a matter of getting used to it. For my applications, I actually *need* it to always round numbers like 1.5, 2.5, 3.5, ... down. Is there a way to modify the `round` function to do this? – James Pirlman Jul 07 '15 at 02:05
  • @JamesPirlman If you need specific behaviors when dealing with non-integer numbers, you really need to use the [Decimal module](https://docs.python.org/3/library/decimal.html), like I and others have mentioned. This is not about `round` in specific, but how computers handle floating point numbers in general. [See my answer](http://stackoverflow.com/a/31258564/2194039). – justinpawela Jul 07 '15 at 02:11
  • *`so-called "bankes's rounding"`* should better be replaced by something that is more precise for this context, it's defined in IEEE-754, Wikipedia says *`This is the default rounding mode used in IEEE 754 computing functions and operators (see also Nearest integer function).`* – Wolf Nov 02 '16 at 09:03
4

The round docs do address the peculiaries of rounding floating point numbers.

You can use the decimal library to achieve what you want.

from decimal import Decimal, ROUND_HALF_UP, ROUND_HALF_DOWN

round(2.675, 2)
# output: 2.67

Decimal('2.675').quantize(Decimal('1.11'), rounding=ROUND_HALF_UP)
# output: 2.68

Decimal('2.5').quantize(Decimal('1.'), rounding=ROUND_HALF_DOWN)
# output: 2
Brendan Abel
  • 35,343
  • 14
  • 88
  • 118
3

Your want "round down", and you are getting "round to even". Just do it manually by doing

ceil(x - 0.5)
Abednego
  • 452
  • 3
  • 4
  • Good answer to the update question, I'd like to upvote it, but I find a word about the original topic missing for that. – Wolf Nov 02 '16 at 08:59
1

This is documented pretty well. According to the Python docs for round:

Note The behavior of round() for floats can be surprising: for example, round(2.675, 2) gives 2.67 instead of the expected 2.68. This is not a bug: it’s a result of the fact that most decimal fractions can’t be represented exactly as a float. See Floating Point Arithmetic: Issues and Limitations for more information

In specific, this is a side-effect of how computers handle floating-point numbers in general.

If you need more precision, including different rounding, I suggest you check out the Python Decimal module. Specifically of interest, they have the ability to control rounding modes. Looks like you might want decimal.ROUND_HALF_DOWN.

justinpawela
  • 1,968
  • 1
  • 14
  • 18
1

Python 3 provides rounding methods defined in the IEEE Standard for Floating-Point Arithmetic (IEEE 754), the default rounding[1] is directed to the nearest number and minimizing cumulative errors.

In IEEE 754, there are 5 methods defined, two for rounding to nearest (Python provides the first one by round) and three methods that are explicitly directed (Python has trunc, ceil, and floor in its Math module).

You obviously need a directed rounding and there is a way to tell this Python, you have just to choose.


[1] Since the representation of floating point numbers in computers is limited, rounding is not as trivial as you might think, you'll be surprised! I recommend a careful read of 15. Floating Point Arithmetic: Issues and Limitations in the python 3 documentation.

Wolf
  • 9,679
  • 7
  • 62
  • 108
0

I believe I have the answer to all the rounding errors people have been encountering. I have wrote my own method, which functions same as the "round" but actually looks at the last digit and rounds from there case by case. There is no converting a decimal to binary. It can handle any amount of numbers behind the decimal and it also takes scientific notation (as outputted by floats). It also doesn't require any imports! Let me know if you catch any cases that don't work!

def Round(Number,digits = 0):
Number_Str = str(Number)
if "e" in Number_Str:
    Number_Str = "%.10f" % float(Number_Str)
if "." in Number_Str:   #If not given an integer
    try:
        Number_List = list(Number_Str)  #All the characters in Number in a list
        Number_To_Check = Number_List[Number_List.index(".") + 1 + digits]  #Gets value to be looked at for rounding.      
        if int(Number_To_Check) >= 5:
            Index_Number_To_Round = Number_List.index(".") + digits
            if Number_List[Index_Number_To_Round] == ".":
                Index_Number_To_Round -= 1
            if int(Number_List[Index_Number_To_Round]) == 9:
                Number_List_Spliced = Number_List[:Number_List.index(".")+digits]
                for index in range(-1,-len(Number_List_Spliced) - 1,-1):
                    if Number_List_Spliced[index] == ".":
                        continue
                    elif int(Number_List_Spliced[index]) == 9:
                        Number_List_Spliced[index] = "0"
                        try:
                            Number_List_Spliced[index-1]
                            continue
                        except IndexError:
                            Number_List_Spliced.insert(0,"1")
                    else:
                        Number_List_Spliced[index] = str(int(Number_List_Spliced[index])+1)
                        break
                FinalNumber = "".join(Number_List_Spliced)
            else:
                Number_List[Index_Number_To_Round] = str(int(Number_List[Index_Number_To_Round])+1)
                FinalNumber = "".join(Number_List[:Index_Number_To_Round + 1])
            return float(FinalNumber)
        else:
            FinalNumber = "".join(Number_List[:Number_List.index(".") + 1 + digits])
            return float(FinalNumber)
    except IndexError:
        return float(Number)
else:   #If given an integer
    return float(Number)
yarz-tech
  • 284
  • 2
  • 6
  • 18
0

Here is a function that will let your round consistently, change round_down to True if you want to round down.

def common_round(float_number, digit_to_round_to=0, round_down=False):

float_splits = str(float_number).split(".")

# Checks if number not really a float, returns the int:
if len(float_splits) == 1:
    y = float_splits
    return y

else:
    # Looks for the rounding digit:
    float_part = float_splits[-1]
    float_part_sliced = float_part[:(digit_to_round_to + 1)]
    rounding_digit = int(float_part_sliced[-1])

    if rounding_digit != 5:
        # If rounding_digit is not 5, just do round():
        y = round(float_number, digit_to_round_to)
        return y

    else:
        # If rounding_digit is 5, add or substract 1 to rounding digit:
        if round_down == False:
            rounding_digit += 1
        else:
            rounding_digit -= 1

        # Paste number back together and do round():
        reconstructed_str = float_splits[0] + '.' + float_part_sliced[:-1] + str(rounding_digit)
        reconstructed_float = float(reconstructed_str)
        y = round(reconstructed_float, digit_to_round_to)
        return y