Round decimal amounts with an explicit policy

A price calculation needs a rounding rule as well as a number of decimal places. Two systems can both display cents and still disagree on a halfway value if one uses half-even and the other uses half-up. Choose the rule from the application's contract, write it down, and test the halfway cases before comparing totals.
Python's decimal documentation describes decimal values, contexts, signals and rounding modes. The following example accepts a decimal string and uses quantize to set the exponent to that of 0.01. It explicitly chooses half-even for this example; financial rules in a real jurisdiction or contract may require something else.
Decimal input is quantized to cents; insufficient context precision can raise InvalidOperation.
Apply the selected rule
from decimal import Decimal, ROUND_HALF_EVEN
def round_cents(text):
return Decimal(text).quantize(Decimal('0.01'), rounding=ROUND_HALF_EVEN)
print(round_cents('2.345')) # 2.34
print(round_cents('2.355')) # 2.36
At an exact halfway point, half-even selects the result whose final retained digit is even. Both examples start with strings, so the decimal inputs are exactly the values shown. Constructing a Decimal from a binary float instead captures that float's exact value, which may not be the decimal value the user typed. Preserve the original decimal text when that is the input contract.
quantize returns a new value. It does not modify the original decimal, determine a currency, validate an invoice, or decide when a business process should round. Rounding every line item and summing can differ from rounding the final sum. Pick the stage required by the contract and test both typical values and the cases where those approaches diverge.
Bound the context and the input
Arithmetic uses a context with precision and signal handling. If the quantized coefficient would exceed the context precision, quantize signals InvalidOperation. With the usual trap enabled, that raises an exception. For example, under localcontext() with ctx.prec = 3, quantizing 1234.56 to cents raises InvalidOperation. Test that failure with a temporary localcontext, alongside assertions for the two halfway results. Restore the caller's context when the test exits.
The example intentionally leaves the caller's context in force. Production code should choose precision and traps for the expected range, and reject values outside the supported domain. Decimal also supports infinities and NaNs; if an amount must be finite, check is_finite() before calculations. Invalid text needs an input error response. Catch the relevant decimal exceptions at the boundary rather than substituting zero or silently changing the requested amount.
Finally, store or transmit the amount and its currency together. A two-place exponent is not a universal currency rule, and this example makes no claim about a particular currency's legal rounding or display requirements.
Make the boundary observable
Include the chosen rounding mode and rounding stage in the interface contract shared with systems that exchange amounts. A consumer that rounds each item may receive a different total from one that rounds only the aggregate, even with the same mode. Include examples with positive and negative values in acceptance tests and verify the expected behavior against the actual business rule. Save decimal values in a representation that preserves the required precision. Formatting a result with two displayed digits is not a substitute for deciding how the underlying amount is calculated.