Why does Python's round(2.5) return 2 while round(3.5) returns 4?
answer
- Only exact halves behave oddly
- Two neighbours are equally near
- The tie-break inspects the neighbour
- Designed so long sums do not drift
- IEEE 754's default tie rule
basics
~20 sPython'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 linesfor 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
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.
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.
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.
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