skip to content

Rounding Rules and round()

round() breaks ties to the even number, and round(2.675, 2) gives 2.67 because the float was never exactly 2.675. Money code quantizes a Decimal instead, and interviewers check that you know why.

part ofPythonoverview, primer and where to startread it →
on this pageshow

questions

4

Why does Python's round(2.5) return 2 while round(3.5) returns 4?

level: juniorimportance: must knowfreq 65%

answer

  1. Only exact halves behave oddly
  2. Two neighbours are equally near
  3. The tie-break inspects the neighbour
  4. Designed so long sums do not drift
  5. IEEE 754's default tie rule

basics

~20 s

Python's built-in round() breaks an exact tie toward the nearest even value, so 2.5 goes down to 2 and 3.5 goes up to 4. This round-half-to-even rule keeps long columns of numbers from drifting upward.

solid answer

~40 s

`round()` returns the nearest value, and when two candidates are exactly equidistant it picks the **even** one — round-half-to-even, also called banker's rounding, and the same tie rule IEEE 754 uses by default. So `round(0.5)` is `0`, `round(1.5)` is `2`, `round(2.5)` is `2`, `round(3.5)` is `4`, and `round(-2.5)` is `-2`. The point is bias: always rounding halves away from zero pushes a long column of totals upward by half a unit per tie, while alternating up and down cancels out. Python 2 did round halves away from zero and returned a float; Python 3.0 changed both, and the behaviour is unchanged through 3.14. `round(x)` with no second argument returns an `int`; `round(x, ndigits)` returns the same type as `x`, so `round(2.5, 0)` is `2.0`.

code

python · 5 lines
python
for x in (0.5, 1.5, 2.5, 3.5, -2.5):
    print(x, "->", round(x))

print(type(round(2.5)), type(round(2.5, 0)))
print(round(12345, -2), round(12350, -2))

go deeper

for a junior

Recall the concrete outputs — round(0.5) is 0, round(2.5) is 2, round(3.5) is 4 — and be able to say the word 'tie'. Do not describe it as a bug; it is the documented rule.

for a middle

Explain the mechanics: ties go to the even neighbour, the one-argument form returns an int while the two-argument form preserves the type, and the builtin dispatches to __round__. Know that round() accepts no rounding mode.

for a senior

Show the production judgment: know that half-to-even is bias-free for statistics but wrong for most invoicing rules, and that the fix is quantizing a decimal.Decimal rather than post-processing a float. Be ready to say why the tie rule is not the cause of most round(x, 2) surprises.

for a principal

Own the policy: rounding mode is a business rule, not a coding preference, so it belongs in one documented money helper rather than scattered across call sites. Interviewers expect you to weigh auditability and cross-language consistency, since other stacks default to half-up.

## What round() actually promises `round(x)` returns the value nearest to `x`. That is unambiguous everywhere except at an exact halfway point, where two candidates are equally near and the language must choose. Python 3 chooses the **even** candidate. The rule has several names — round-half-to-even, banker's rounding, and in the IEEE 754 standard roundTiesToEven, which is the default rounding mode of the floating-point hardware your CPU already uses for arithmetic. The consequences look surprising the first time: ```python print(round(0.5), round(1.5), round(2.5), round(3.5), round(4.5)) # 0 2 2 4 4 print(round(-2.5), round(-1.5)) # -2 -2 ``` Half of the ties go down and half go up, chosen by the parity of the neighbour, not by the sign or by a fixed direction. ## Why even rather than up The alternative most people expect — round halves away from zero — has a systematic bias. If you round a large batch of values and the halfway case is at all common (it is very common with prices, half-hours, half-cents, and anything measured to a half unit), every tie adds half a unit to the sum in the same direction. Sum a thousand such values and the total is reliably too big. Round-half-to-even spreads ties in both directions, so over many values the errors cancel rather than accumulate. That is why the rule is standard for statistics and for the floating-point unit itself, and why accountants once adopted it by hand. It is *not*, however, the rule most tax and invoicing regulations require. Those usually mandate half-up. Python's built-in `round()` has no rounding-mode parameter, so you do not talk it into half-up — you switch to `decimal.Decimal` and call its `quantize` method with `decimal.ROUND_HALF_UP`. ## The two shapes of the call Omitting the second argument and passing it are genuinely different operations: * `round(x)` returns an `int`. `round(2.5)` is the integer `2`. * `round(x, ndigits)` returns the **same type as `x`**. `round(2.5, 0)` is the float `2.0`, and `round(Decimal('2.5'), 0)` is a `Decimal`. * `ndigits` may be negative, which rounds to the left of the decimal point: `round(12345, -2)` is `12300`, and `round(12350, -2)` is `12400` because 123.5 hundreds ties toward the even 124. That type difference bites in code that compares a rounded value against an integer or feeds it to something expecting an index. ## It is a protocol, not a float special case `round()` is a builtin that delegates: it calls `type(x).__round__(x)`, or `type(x).__round__(x, ndigits)` when a second argument is given. `float.__round__` is what implements the tie rule for floats; `int.__round__` and `Decimal.__round__` implement it for their own types (a `Decimal` rounds under its own context, whose default rounding is also half-to-even). A class of your own becomes usable with the builtin the moment it defines `__round__`; without it, `round()` raises `TypeError`. ## Exact ties are rarer than the examples suggest A float only lands exactly on a halfway point when its binary value really is one — halves of integers do, and so do values like `0.25` when rounding to one decimal place. Most decimal-looking literals do not: at two decimal places, a literal such as `2.675` is stored as a value slightly *below* the halfway point, so no tie-breaking rule is consulted at all and the result is `2.67`. That is why the half-to-even rule is most visible when rounding to whole numbers, and why blaming it for every surprising `round(x, 2)` result is wrong. ## Version history Python 2's `round()` returned a float and rounded halves away from zero, so `round(2.5)` there was `3.0`. Python 3.0 changed the tie rule to half-to-even and made the one-argument form return an `int`. Nothing about this has changed since, up to and including 3.14 — if an interviewer frames it as a recent change or a bug, it is neither. ## What to do about it For display, half-to-even is fine and formatting with an f-string is usually clearer than rounding first. For money and for anything a regulator will audit, do not round binary floats at all: parse to `decimal.Decimal` from the original string and quantize with the rounding mode the rule demands. For statistics, half-to-even is the behaviour you want and you should leave it alone.

  • What type does round() return, and does the second argument change that?
    `round(x)` with the second argument omitted returns an `int`. `round(x, ndigits)` returns the same type as `x`, so `round(2.5, 0)` is the float `2.0` rather than the integer `2`. That distinction matters when the result is compared against an integer or used as an index.
  • How does round() handle a negative ndigits?
    It rounds to the left of the decimal point and keeps the argument's type. `round(12345, -2)` is `12300`, and `round(12350, -2)` is `12400` because 123.5 hundreds is an exact tie and 124 is the even neighbour. It is a handy way to bucket integers to tens, hundreds or thousands.
  • How would you get half-up rounding instead?
    The built-in `round()` has no rounding-mode parameter, so you change types: build a `decimal.Decimal` from the original string and call its `quantize` method with `rounding=decimal.ROUND_HALF_UP`. `Decimal('2.5').quantize(Decimal('1'), rounding=ROUND_HALF_UP)` is `3`, where the built-in would give `2`.

Think of a referee who must break a dead-even tie: instead of always awarding it to the same side, they alternate by a fixed impartial rule, so over a whole season neither side gains.

saying these in an interview costs you the question

  • Says round() always rounds .5 upward
  • Calls the behaviour a CPython bug
  • Thinks round(2.5) returns a float
  • Claims round() takes a rounding-mode argument
  • Believes the tie rule explains every surprising round(x, 2) result
  • Says half-to-even changes results for non-tie values too

context

open as a page

Why does round(2.675, 2) return 2.67 in Python rather than 2.68?

level: middleimportance: must knowfreq 55%

basics

~10 s

The literal 2.675 is stored as a binary double slightly below 2.675, so it is not a halfway case at all — the nearest two-decimal value really is 2.67, and round() returns it correctly.

open as a page

How do math.floor, math.ceil and math.trunc differ on a value like -2.5?

level: middleimportance: should knowfreq 45%

basics

~10 s

math.floor goes toward negative infinity, math.ceil toward positive infinity, and math.trunc toward zero. On -2.5 that gives -3, -2 and -2; on 2.5 floor and trunc agree at 2 while ceil gives 3.

open as a page

How do you round money in Python so a payment reconciliation job matches the ledger to the cent?

level: seniorimportance: should knowfreq 40%

basics

~10 s

Stop rounding floats. Parse each amount into a decimal.Decimal from its original string, then quantize to Decimal('0.01') with an explicitly named rounding mode such as decimal.ROUND_HALF_UP, and round once at a defined boundary.

open as a page