round(number, ndigits) rounds a number to the given decimal places: round(3.14159, 2) is 3.14. Two things surprise people. First, Python uses banker’s rounding - halves go to the nearest even digit, so round(0.5) is 0 and round(2.5) is 2, not 3. Second, round(2.675, 2) returns 2.67, which looks like a bug and is not - it is floating point. For money and anything where the rounding rule must be exact, you want Decimal, not round on a float.
Table of contents
- The basic call and its two arguments
- Banker’s rounding: why round(0.5) is 0
- The round(2.675, 2) mystery
- Use Decimal when the rule must be exact
- Formatting versus rounding
- How this fits the rest of the stack
- FAQ
The basic call and its two arguments
round(3.14159, 2) # 3.14
round(3.14159) # 3 - no ndigits means round to an int
round(1234, -2) # 1200 - negative ndigits rounds left of the point
With one argument, round returns an integer. With two, it returns a number of the same type as the input rounded to ndigits places. A negative ndigits rounds to tens, hundreds, and so on, which is occasionally handy for bucketing.
That is the whole surface of the function. Everything else that confuses people is about how rounding halves works and how floats store numbers - two things that are true of the language, not of this one function.
Banker’s rounding: why round(0.5) is 0
Most people learn round half up in school: 0.5 goes to 1, 2.5 goes to 3. Python rounds half to even, also called banker’s rounding:
round(0.5) # 0
round(1.5) # 2
round(2.5) # 2
round(3.5) # 4
Halfway values round to the nearest even number. This is not a quirk - it is deliberate. Round half up introduces a small upward bias when you sum many rounded values, because halves always go the same direction. Round half to even cancels that bias out over a large set, which is exactly what you want in accounting and statistics.
It surprises people because it disagrees with the schoolbook rule. But if you are summing thousands of rounded figures, banker’s rounding is the one that does not slowly drift. Knowing the rule is better than being startled by it in a report.
The round(2.675, 2) mystery
This is the one that generates bug reports:
round(2.675, 2) # 2.67, not 2.68
It is not banker’s rounding this time - it is floating point. The number 2.675 cannot be represented exactly in binary; the closest double is very slightly less than 2.675, something like 2.67499999999999982. So the value being rounded is genuinely below the halfway point, and rounding down to 2.67 is correct for the number that is actually stored.
from decimal import Decimal
Decimal(2.675) # 2.67499999999999982236431605997495353221893310546875
No amount of care with round fixes this, because the imprecision happened when the literal 2.675 became a float, before round ever saw it. The lesson is that round on a float is only ever as exact as the float, and floats are not exact for most decimal fractions.
Use Decimal when the rule must be exact
For money, invoices, tax, or anything where a fraction of a cent matters and an auditor might ask, do not round floats. Use decimal.Decimal, built from strings so no float imprecision sneaks in:
from decimal import Decimal, ROUND_HALF_UP
price = Decimal("2.675")
price.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP) # 2.68
Decimal gives you exact decimal arithmetic and lets you choose the rounding mode explicitly - ROUND_HALF_UP for the schoolbook rule, ROUND_HALF_EVEN for banker’s rounding, and several others. Building the Decimal from a string ("2.675", not 2.675) is the crucial step; build it from a float and you inherit the float’s imprecision.
The rule of thumb: round for display and rough numbers, Decimal for money and anything where the rounding behaviour is part of the spec.
Formatting versus rounding
If all you want is a number that reads nicely in output, you may not need round at all - you need string formatting:
value = 3.14159
f"{value:.2f}" # '3.14' - a string, formatted to 2 places
round(value, 2) # 3.14 - a float, still a float
f"{value:.2f}" never changes your underlying number; it produces a display string. That is usually what you want in a log line or a report, because it avoids carrying a rounded float back into further calculations where the rounding compounds.
Keep the distinction clear: round when you need a rounded number to compute with, format when you need a rounded string to show. Reaching for round to fix how something prints is a common and avoidable mix-up.
How this fits the rest of the stack
Numeric correctness - the difference between banker’s rounding and a float artifact and an exact Decimal - is the kind of detail that decides whether a billing calculation matches the invoice. When that calculation runs as a service that other systems trust, the arithmetic and the runtime it executes in both have to be things you can reason about. The RunxBuild hosting calculator lays out the service, database, storage, and bandwidth as separate line items, and the RunxBuild dashboard is where the team watches deploys, logs, and restarts as they happen.
Useful related references:
- Change Python Version: The Three Tools That Make It Stop Hurting
- Private Methods in Python: There Are None, and That Is Fine
- Python Module Not Found: A Diagnostic Sequence That Actually Fixes It
- Python services on RunxBuild
FAQ
Why does round(2.675, 2) give 2.67 in Python?
Because 2.675 cannot be stored exactly as a float; the nearest double is slightly less than 2.675, so the value being rounded is truly below the halfway point and rounds down to 2.67. It is floating-point representation, not a bug in round. Use Decimal built from the string "2.675" for exact behaviour.
What is banker’s rounding in Python?
Python’s round rounds halves to the nearest even digit, so round(0.5) is 0 and round(2.5) is 2. This round-half-to-even rule removes the upward bias you get from always rounding halves up, which matters when you sum many rounded values.
How do I round to 2 decimal places in Python?
round(number, 2) returns a float rounded to two places. If you only need it for display, f"{number:.2f}" gives a formatted string without changing the underlying value. For money, use Decimal("...").quantize(Decimal("0.01")) for exact results.
When should I use Decimal instead of round?
Use Decimal whenever the rounding rule is part of the specification - money, tax, invoices - or when float imprecision would be visible. Build Decimal from strings, choose the rounding mode explicitly, and you get exact decimal arithmetic that round on a float cannot guarantee.
What does a negative ndigits do in round?
It rounds to the left of the decimal point. round(1234, -2) returns 1200, rounding to the nearest hundred. It is a quick way to bucket numbers into tens, hundreds, or thousands.