Python round() Behavior for Half-Way Cases
Python's built-in round() function handles half-way
values using a strategy known as "round half to even" or "banker's
rounding." Unlike the common schoolbook method of always rounding
.5 upward, Python rounds midway values to the nearest even
number to minimize cumulative statistical bias. This article explains
how this mechanism works, why it is implemented, how floating-point
representation affects the results, and how to implement traditional
rounding when necessary.
The "Round Half to Even" Rule
In Python 3, when an exact tie occurs—meaning a number is precisely
halfway between two possible rounded values—round() chooses
the nearest even integer.
# Rounding to the nearest integer
print(round(0.5)) # Output: 0 (0 is even)
print(round(1.5)) # Output: 2 (2 is even)
print(round(2.5)) # Output: 2 (2 is even)
print(round(3.5)) # Output: 4 (4 is even)
print(round(4.5)) # Output: 4 (4 is even)The same behavior applies to negative numbers:
print(round(-0.5)) # Output: 0
print(round(-1.5)) # Output: -2
print(round(-2.5)) # Output: -2This behavior also extends to fractional positions when passing an
optional ndigits argument:
print(round(1.25, 1)) # Output: 1.2 (2 is even)
print(round(1.35, 1)) # Output: 1.4 (4 is even)Why Python Uses Banker's Rounding
Traditional arithmetic rounding ("round half up") always rounds ties toward positive infinity or away from zero. When performing calculations over large datasets, consistently rounding up introduces a positive upward bias in sums and averages.
Banker's rounding conforms to the IEEE 754 standard for floating-point arithmetic. By rounding half-way cases to the nearest even digit, roughly half of the ties are rounded down and half are rounded up, neutralizing the drift and maintaining statistical accuracy over multiple operations.
The Impact of Floating-Point Precision
Sometimes round() produces results that seem
inconsistent with the "round half to even" rule. For example:
print(round(2.675, 2)) # Output: 2.67, expected 2.68This occurs not because the rounding rule failed, but because most
decimal fractions cannot be represented exactly in binary floating-point
format. When Python stores 2.675, the underlying binary
representation is slightly less than the decimal value:
from decimal import Decimal
print(Decimal(2.675))
# Output: 2.67499999999999982236431605997495353221893310546875Because the stored number is actually closer to 2.67
than to 2.68, Python rounds down. It is not an exact
halfway tie from the machine's perspective.
How to Implement Standard "Round Half Up"
If your use case requires standard arithmetic rounding (such as
financial calculations requiring traditional rounding up at
.5), use the decimal module:
from decimal import Decimal, ROUND_HALF_UP
def round_half_up(val, places=0):
d = Decimal(str(val))
pattern = '1' if places == 0 else f"1.{'0' * places}"
return float(d.quantize(Decimal(pattern), rounding=ROUND_HALF_UP))
print(round_half_up(2.5)) # Output: 3.0
print(round_half_up(3.5)) # Output: 4.0
print(round_half_up(2.675, 2)) # Output: 2.68Using Decimal avoids binary representation errors by
preserving the exact base-10 value and explicitly applying the
ROUND_HALF_UP strategy.