When you deal with money, bonuses, or ratios in Python, you’ve probably used something like this:
from decimal import Decimal, ROUND_HALF_UP
value = Decimal("1.005")
rounded = value.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
print(rounded) # 1.01
Seems simple enough, until you realize that Python, by default, doesn’t round this way.
Let’s dig into why.
Understanding the Rounding Problem
Rounding isn’t as innocent as it looks. Take a simple case:
Value Rounded (2 decimals) Expected 1.004 1.00 1.005 1.00 or 1.01?
If you try this in Python without specifying a rounding mode, you’ll get:
from decimal import Decimal
print(Decimal("1.005").quantize(Decimal("0.01")))
# Output: 1.00
Wait, what? Why did 1.005 become 1.00 instead of 1.01?
That’s because Python follows IEEE 754 standards for floating-point arithmetic, and its default rounding mode is ROUND_HALF_EVEN, also called banker’s rounding.
What is ROUND_HALF_EVEN?
ROUND_HALF_EVEN means:
If a number is exactly halfway between two rounding choices, round to the nearest even number.
For example:
Original Rounded (1 decimal) Rule 1.5 2 even 2.5 2 even 3.5 4 even 4.5 4 even
Notice the pattern? Half the time, it rounds up. Half the time, down.
This keeps the total rounding error unbiased when you process large datasets, which is exactly why it’s the default in most languages and hardware implementations.
Why It Matters
Let’s compare two rounding modes across a range of numbers.
The test:
Round every number from 0.5 to 99.5, inclusive.
from decimal import Decimal, ROUND_HALF_UP, ROUND_HALF_EVEN
numbers = [Decimal(i) + Decimal("0.5") for i in range(100)]
def rounded_sum(mode):
return sum(n.quantize(Decimal("1"), rounding=mode) for n in numbers)
print("ROUND_HALF_UP:", rounded_sum(ROUND_HALF_UP))
print("ROUND_HALF_EVEN:", rounded_sum(ROUND_HALF_EVEN))
The result:
Mode Total Sum After Rounding Bias
ROUND_HALF_UP 5050 Slight upward bias
ROUND_HALF_EVEN 5000 Balanced / unbiased
So, when you always round 0.5 up, your numbers gradually inflate. That’s fine if you’re showing prices or bonuses to users, not fine if you’re summing a million transactions in a bank’s backend.
When to Use Each Use Case Recommended Mode Reason Displaying money, bonuses, or salary ROUND_HALF_UP Matches human expectation (“.005 → .01”) Statistical / financial backend calculations ROUND_HALF_EVEN Avoids cumulative rounding bias Scientific or analytical datasets ROUND_HALF_EVEN Follows IEEE 754 standard User-facing invoices, reports ROUND_HALF_UP Feels natural to end users Practical Example
Suppose you’re calculating bonuses based on a ratio:
from decimal import Decimal, ROUND_HALF_UP
def calculate_ratio_amount(base_amount, base_bonus, current_amount):
base_amount = Decimal(base_amount)
base_bonus = Decimal(base_bonus)
current_amount = Decimal(current_amount)
ratio = base_bonus / base_amount
result = current_amount * ratio
return result.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
If your result is 18.445, this function will output:
Decimal('18.45')
Without ROUND_HALF_UP, the default rounding (ROUND_HALF_EVEN) might give you:
Decimal('18.44')
That’s a visible difference in financial reports.
The Key Takeaway
Python’s ROUND_HALF_EVEN isn’t wrong, it’s scientifically fair. But in human-facing systems, fairness and intuition aren’t the same thing.
So, here’s the rule of thumb:
Use ROUND_HALF_UP when humans will read it. Use ROUND_HALF_EVEN when machines will sum it.