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: -2

This 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.68

This 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.67499999999999982236431605997495353221893310546875

Because 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.68

Using Decimal avoids binary representation errors by preserving the exact base-10 value and explicitly applying the ROUND_HALF_UP strategy.