Round numbers correctly¶
The question. You need to round a number — to a whole number, to two decimal places, to a few significant figures, or to the nearest penny — and you want the result your users expect, not a surprise from round.
The answer depends on which rounding you mean. The built-in round uses banker's rounding (half to even), which is right for statistics but wrong for the "always round .5 up" most people expect. Here's each case and the tool that fits it.
The default: round rounds half to even¶
round sends a value exactly halfway to the nearest even digit. Over many values this avoids the upward bias of always-round-up, but it means round(0.5) is 0 and round(2.5) is 2.
print(round(0.5), round(1.5), round(2.5), round(3.5)) # 0 2 2 4
print(round(2.675, 2)) # 2.67 — and 2.675 is really 2.67499... as a float
"Round half up": use Decimal.quantize¶
For the schoolbook rule — halves always go up — round a Decimal with ROUND_HALF_UP. Build the Decimal from a string so the value is exact before you round it.
from decimal import Decimal, ROUND_HALF_UP
def round_half_up(value, places=0):
q = Decimal(1).scaleb(-places) # e.g. places=2 -> Decimal('0.01')
return Decimal(str(value)).quantize(q, rounding=ROUND_HALF_UP)
print(round_half_up(0.5)) # 1
print(round_half_up(2.5)) # 3
print(round_half_up(2.675, 2)) # 2.68
Round to decimal places for display¶
If you only need a rounded number shown to the user, don't round the value at all — format it. An f-string with .2f gives exactly two decimal places as text, sidesteps representation surprises, and keeps your underlying number intact.
x = 3.14159
print(f'{x:.2f}') # '3.14'
print(f'{1/3:.4f}') # '0.3333'
print(f'{2.5:.0f}') # '2' (note: format also rounds half to even)
Rounding for display (format) versus rounding the value (so later maths uses the rounded number) are different jobs — pick format when it's purely for output.
Round to significant figures¶
Neither round nor .Nf does significant figures directly. For a quick numeric result, this helper works; for display, the g format type rounds to significant figures and trims trailing zeros.
from math import log10, floor
def round_sig(x, sig):
if x == 0:
return 0.0
return round(x, -int(floor(log10(abs(x)))) + (sig - 1))
print(round_sig(12345, 2)) # 12000 (int in -> int out)
print(round_sig(0.0034567, 2)) # 0.0035
# for display, the 'g' type does significant figures:
print(f'{12345:.2g}') # '1.2e+04'
print(f'{0.0034567:.2g}') # '0.0035'
Round money to the penny¶
Money is the case where rounding and exactness both matter, so do it in Decimal. Keep everything decimal, then quantize to two places with ROUND_HALF_UP at the point you need a final figure.
from decimal import Decimal, ROUND_HALF_UP
pennies = Decimal('0.01')
subtotal = Decimal('19.99') * 3 # 59.97 exactly
with_tax = subtotal * Decimal('1.20') # 20% VAT -> 71.964
total = with_tax.quantize(pennies, rounding=ROUND_HALF_UP)
print(total) # 71.96
The full money workflow is in handle money with Decimal.
Which to use¶
| You want… | Use |
|---|---|
| statistically unbiased rounding | built-in round (half to even) |
| "halves go up" | Decimal.quantize(..., ROUND_HALF_UP) |
| a rounded number just for display | an f-string: f'{x:.2f}' |
| significant figures for display | the g type: f'{x:.3g}' |
| money | Decimal throughout, quantize at the end |