Why does round(2.675, 2) return 2.67 in Python rather than 2.68?
answer
- The tie rule is a red herring here
- What got stored is not what you typed
- Inspect the exact stored value
- It sits just under the midpoint
- The fix is a type change, not a flag
basics
~10 sThe 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.
solid answer
~40 sThis is not the tie-breaking rule at work. A `float` is an IEEE 754 binary64 value, and the literal `2.675` is stored as `2.674999999999999822...`, which is genuinely closer to 2.67 than to 2.68. `round()` rounds the *stored* value correctly, so `2.67` is the right answer to the question actually asked. You can see it with `decimal.Decimal(2.675)`, which prints the exact stored value. Note that the direction is not always down: `round(2.665, 2)` gives `2.67` because that literal happens to be stored just *above* its halfway point. The fix is not a smarter float call — it is to stop rounding floats: parse the original text into a `decimal.Decimal` and use `quantize`, for example `Decimal('2.675').quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)`, which gives `2.68`.
code
python · 8 linesfrom decimal import Decimal, ROUND_HALF_UP
print(round(2.675, 2)) # 2.67
print(Decimal(2.675)) # the exact stored value, just under the midpoint
print(round(2.665, 2)) # 2.67 - this one was stored just above
exact = Decimal('2.675').quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
print(exact) # 2.68go deeper
Remember the headline: the literal you typed is not the number stored, so 2.675 is really a hair under the midpoint and 2.67 is the honest nearest value. Do not call it a bug.
Explain the mechanics end to end: binary64 storage, no tie so no tie rule, correct rounding of the stored value, and demonstrate it by printing Decimal(2.675). Know that neighbouring literals can round the other way.
Show the production judgment: decide where in a pipeline values stop being floats, build Decimal from strings at the input boundary, and name the rounding mode explicitly. Be able to say why pinning a few round(x, 2) results in tests proves nothing.
Own the policy call: which subsystems are allowed to hold money as a float at all, how the decimal boundary is enforced in review, and what it costs in performance and library compatibility to keep exact types end to end.
## The question behind the question Interviewers ask this to find out whether you reach for the tie rule (round-half-to-even) as an explanation for everything, or whether you know that the value being rounded was never the value you typed. ```python print(round(2.675, 2)) # 2.67 from decimal import Decimal print(Decimal(2.675)) # 2.67499999999999982236431605997495353221893310546875 ``` `Decimal(some_float)` converts exactly, with no rounding, so it is the cleanest way to see what a `float` really holds. The stored value sits *below* the halfway point between 2.67 and 2.68. There is therefore no tie, no tie-breaking rule is consulted, and 2.67 is simply the nearer of the two candidates. `round()` did exactly the right thing on the input it was handed. ## Why the stored value differs A `float` is a binary fraction: a sum of powers of two. Two-thirds of ordinary decimal fractions have no finite binary expansion, so the compiler stores the nearest representable double and the tiny difference is baked in before `round()` is ever called. The error is not introduced by rounding — it is introduced by writing a decimal literal. ## The direction is not always down A common wrong conclusion is 'floats round down at .5'. They do not; they round toward whichever side the stored value actually falls on, and that varies per literal: ```python print(round(2.675, 2)) # 2.67 - stored just below the midpoint print(round(2.665, 2)) # 2.67 - stored just above the midpoint print(round(0.005, 2)) # 0.01 - stored just above the midpoint print(round(1.005, 2)) # 1.0 - stored just below the midpoint ``` This unpredictability is the real problem. You cannot memorise which literals land where, and it means a rule like 'half rounds up' is untestable for two-decimal money in float arithmetic. It also means a unit test that pins `round(x, 2)` for a handful of values proves nothing about the next value. ## CPython rounds correctly, and that matters It is worth stating plainly, because candidates often assume a sloppy implementation: `float.__round__` performs *correctly rounded* decimal conversion of the binary value, using the same high-precision string-conversion machinery as `repr`. It is not doing `int(x * 100 + 0.5) / 100`, which would introduce a second layer of error. So the answer 2.67 is the mathematically correct rounding of the number CPython holds — the surprise is entirely in the gap between what you typed and what got stored. ## The fix For anything where the decimal digits are the truth — money, tax, invoice lines, published metrics — do not round floats. ```python from decimal import Decimal, ROUND_HALF_UP amount = Decimal('2.675') # from the string, not the float print(amount.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)) # 2.68 ``` Two details make or break this. First, build the `Decimal` from the original **string** or from integers: `Decimal(2.675)` takes the float's error along for the ride, and quantizing it half-up still gives `2.67`. Second, name the rounding mode explicitly rather than relying on the default, which is half-to-even. ## When floats are fine Not every rounding needs `decimal`. If the number is a measurement, a ratio, a score or a duration, a sub-picogram discrepancy at the hundredths place is irrelevant and rounding for display with an f-string is simpler and clearer. Reserve exact decimal arithmetic for values where a one-cent difference is a defect that someone will file. ## Answering it well A strong answer has three beats: the literal is not the value; therefore this is not a tie and half-to-even is not involved; therefore the fix is a type change at the boundary, not a cleverer rounding call. Weak answers stop at 'floating point is inaccurate', which does not explain why the result was 2.67 and not 2.68, or why the same call on a neighbouring literal rounds the other way.
- Does round(x, 2) always round a decimal-looking half downward, then?No — it depends on which side of the midpoint the stored double lands, and that varies per literal. `round(2.675, 2)` gives 2.67 but `round(0.005, 2)` gives 0.01, because that literal is stored fractionally above its midpoint. There is no memorable rule, which is exactly why float rounding is unsuitable when the decimal digits are the truth.
- Would round(Decimal('2.675'), 2) also give 2.67?No, it gives `2.68`. A `Decimal` built from the string holds exactly 2.675, so this really is a tie, and it breaks toward the even neighbour — 2.68, since 8 is even. Passing `ndigits` to `round()` on a `Decimal` returns a `Decimal`, and the tie rule comes from the decimal context's default.
- Why is Decimal(2.675) different from Decimal('2.675')?`Decimal(2.675)` converts the float exactly, so it inherits the float's error and holds 2.674999...; `Decimal('2.675')` parses the digits you wrote and holds exactly 2.675. Always build money values from the original string or from integer minor units, never by way of a float.
saying these in an interview costs you the question
- Blames banker's rounding for this result
- Says CPython rounds floats sloppily or incorrectly
- Claims floats always round halves down
- Suggests adding a small epsilon before rounding
- Builds the Decimal from the float instead of the string
- Stops at 'floats are inaccurate' with no mechanism