Namsang LABS
Deep Dive · #settlement #rounding #python #kotlin

Accounting Through Code: Rounding Is Policy

· Sangkyoon Nam

First thing in the morning, a Slack notification popped up. It was K from the finance team.

“The settlement amount for Company A this month is off by a penny from the invoice — did anything change?”

Nothing changed. Not a single thing. Yet there it was — a one-cent discrepancy.

I spent the entire afternoon digging into it. The cause was anticlimactic. Decimals were being silently truncated during integer division. The fix was a one-liner. But when I looked more closely at the data, the real problem was waiting.

#round(0.5) Does Not Always Return 1

The rounding we all learned in school — round up if 5 or above, round down if 4 or below — says 0.5 should become 1. Up through Python 2, that was the case.

# Python 2
round(0.5)  # → 1.0
round(1.5)  # → 2.0
round(2.5)  # → 3.0

But run the same code in Python 3, and the results change.

# Python 3
round(0.5)  # → 0
round(1.5)  # → 2
round(2.5)  # → 2

0.5 becomes 0, and 2.5 becomes 2. What happened in Python 3?

#Banker’s Rounding

This behavior has a name: Banker’s Rounding, also known as Round Half to Even. When a value falls exactly at the midpoint — like 0.5 — instead of always rounding up, it rounds to the nearest even number.

  • 0.5 → 0 (nearest even is 0)
  • 1.5 → 2 (nearest even is 2)
  • 2.5 → 2 (nearest even is 2)
  • 3.5 → 4 (nearest even is 4)

Traditional rounding (Round Half Up) looks perfectly fine on any single transaction. Rounding 0.5 up to 1 seems harmless. But apply it across millions of transactions and the story changes. Consistently rounding 0.5 up introduces a slight upward bias that accumulates over time. The more transactions and the larger the amounts, the bigger the error becomes — too significant to ignore.

Banker’s Rounding was designed to address exactly this problem. By alternating between rounding up and rounding down at the midpoint, the bias cancels out over the long run. When the IEEE 754 floating-point standard was established in 1985, this method was adopted as the default rounding mode. Since then, most hardware and programming languages have followed suit.

One interesting aside: the origin of the name “Banker’s Rounding” is actually unclear. There is no evidence that this was ever an official standard in the banking industry. In fact, historical records suggest that banks never had a unified rounding standard at all. The name says “Banker’s,” but the practice may not have come from finance.

In Korean, Banker’s Rounding is sometimes called 오사오입 (五捨六入) — a naming convention that hints at the direction: “discard five, round up six.”

Python 3 switched for the same reason. Python 2’s rounding went “away from zero” — traditional rounding. Python 3 changed to “round to nearest even.” It is statistically fairer and aligns with the IEEE 754 standard.

Java and Kotlin also use traditional rounding in Math.round(). The difference shows up with BigDecimal, which is designed for precise decimal arithmetic. BigDecimal requires you to explicitly specify a rounding mode — omit it, and it throws an exception.

BigDecimal("2.5").setScale(0)
// → ArithmeticException: Rounding necessary

This is not a bug — it is by design. BigDecimal forces the developer to explicitly declare the rounding mode.

import java.math.BigDecimal
import java.math.RoundingMode

BigDecimal("2.5").setScale(0, RoundingMode.HALF_UP)   // 3 — traditional rounding
BigDecimal("2.5").setScale(0, RoundingMode.HALF_EVEN) // 2 — Banker's Rounding
BigDecimal("2.5").setScale(0, RoundingMode.DOWN)      // 2 — always truncate

Same value of 2.5, but the result changes depending on which RoundingMode you choose.

Language / MethodDefault Behavior
Python 3 round()Banker’s Rounding (HALF_EVEN)
Java / Kotlin Math.round()HALF_UP
Java / Kotlin BigDecimalMust specify — throws exception otherwise
JavaScript Math.round()HALF_UP

JavaScript’s Math.round() is not strictly HALF_UP. It behaves the same for positive numbers, but differs for negative ones. Math.round(-0.5) returns 0, not -1.

Let’s look at a concrete scenario using dollars, where decimals come up frequently. Company N pays an ad agency a 15% commission on monthly ad spend. This month’s ad spend was $18,336.30.

Commission = $18,336.30 × 0.15 = $2,750.445

PolicyBilled Amount
HALF_UP$2,750.45
HALF_EVEN$2,750.44
DOWN$2,750.44

HALF_UP rounds up. HALF_EVEN rounds down because the preceding digit (4) is even. DOWN always truncates. Same amount, different results depending on the policy.

In a settlement system, rounding is not about a language’s default behavior — it is a policy that must be discussed. Which method to use is determined by contract terms, tax invoice standards, and industry conventions.

// 2024-03-15 Agreed with finance team. Per contract clause 4.2.
val ROUNDING_MODE = RoundingMode.HALF_UP

Fail to make this explicit, and you might get another Slack message. And next time, it might not be just a penny.

As of this writing in 2026, rounding has become a hot issue beyond code as well. In November 2025, the last penny (1-cent coin) was minted in the United States. As the supply of pennies dwindles, cash transaction rounding policies are turning into business and legal disputes.

Share this post