Back to Blog

Agent workflows

Decimal or Float? Give Your Data Agent a Rounding Contract

Prevent unexplained total differences by defining numeric representation, rounding mode and rounding stage. Includes a small executed decimal fixture, not a financial-system benchmark.

OpenAgentSkillPublished:

Two correct calculations can produce different totals

A reporting agent imports line amounts, adds them and formats the result with two decimal places. A second system rounds each line first and then adds the displayed values. Their totals differ.

Changing every number to a decimal type may remove one source of approximation, but it does not resolve this disagreement. The two systems are applying different rules about when rounding happens.

For developers reviewing agent-generated reports, the missing artifact is a rounding contract: what the values mean, how they enter the calculation, where rounding occurs and which rule applies. The data owner must approve those semantics. An agent should not invent them to make a reconciliation pass.

Methodology and what was actually checked

We reviewed Python's decimal and floating-point documentation and PostgreSQL's numeric-type documentation on October 5, 2026. The comparison is about representation and transformation boundaries, not investment advice or a prescription for tax calculations.

We also executed a small, synthetic calculation using Python 3.12.14 and its standard-library decimal module. No private records, payment APIs or third-party skills were involved. The results below demonstrate one arithmetic distinction; they are not a benchmark or validation of a production billing system.

This follows the earlier CSV reconciliation guide. That guide establishes which records and meanings enter the report. Here we examine the numeric operations after ingestion.

Choose representation by the required invariant

Python's floating-point tutorial explains why many decimal fractions have only approximate binary representations. Formatting a value to fewer digits changes its presentation, not the underlying stored approximation.

That does not make binary floating point a defective choice. For a measured physical quantity or statistical output, a defined tolerance may be more appropriate than exact decimal equality. Our recommendation is to specify the accepted error and comparison rule before choosing a type.

For amounts supplied as decimal text and requiring decimal arithmetic, Python's decimal documentation provides explicit precision and rounding controls. Constructing a Decimal from a float preserves that float's value; it does not recover the source text that existed before conversion. Parse the original validated decimal representation when that is the agreed input.

Integer minor units are another design option when the unit and allowed increments are fixed. They still need a policy for division, allocation and values smaller than the chosen unit. “Use integers” is not a complete answer to fractional calculations.

Separate rounding mode from rounding stage

Our fixture starts with two exact decimal values, each 1.005. We ask for a result at a scale of two decimal places.

The first decision is stage: round each line before addition, or add the original values and round the total. The second decision is mode: round ties away from zero using ROUND_HALF_UP, or to the nearest even result using ROUND_HALF_EVEN.

Synthetic calculationObserved result
Round each line with HALF_UP, then add2.02
Add first, then round with HALF_UP2.01
Round each line with HALF_EVEN, then add2.00
Add first, then round with HALF_EVEN2.01

All four results came from the same input strings. There is no corrupted row or missing transaction in this fixture. The difference is policy.

The core calculation used was:

from decimal import Decimal, ROUND_HALF_UP, ROUND_HALF_EVEN

values = [Decimal("1.005"), Decimal("1.005")]
unit = Decimal("0.01")

for mode in (ROUND_HALF_UP, ROUND_HALF_EVEN):
    line_total = sum(v.quantize(unit, rounding=mode) for v in values)
    final_total = sum(values).quantize(unit, rounding=mode)
    print(line_total, final_total)

This is a reproduction of the arithmetic used, not an instruction to replace an organization's approved accounting rules. The example deliberately uses simple positive inputs; refunds and allocation rules require additional agreed cases.

Check database boundaries as well as application code

The PostgreSQL numeric reference distinguishes exact numeric types from inexact floating-point types. A declared numeric scale can cause rounding on storage, and PostgreSQL numeric rounding handles ties away from zero.

Consequently, an application that keeps extra decimal places in memory may still round when writing into a constrained column. A calculation explicitly using HALF_EVEN in Python can also disagree with a database operation using a different tie rule. Calling both implementations “decimal” hides that difference.

Our recommended review maps each boundary: input text, application value, database column, aggregate, serialized response and displayed result. For each, record whether precision or scale changes. Do not silently convert a decimal result to a binary float merely because a downstream serializer accepts it more conveniently.

Test the actual driver and consumer in a non-production fixture. This article has not tested your database version, driver or export software.

Give the agent an explicit contract

Before implementation, ask the data owner to fill in these fields:

  • Quantity and unit: currency or other measurement, without inferring it from a symbol alone.
  • Accepted input form: decimal separator, scale and invalid-value policy.
  • Intermediate arithmetic: representation and required precision.
  • Rounding: mode, stage and output scale.
  • Reconciliation: which authoritative total is compared and whether any tolerance is permitted.
  • Exceptions: negative amounts, missing values, excessive precision and allocation remainders.

Keep display formatting separate from the transformation that produces the authoritative total. If a visible report shows rounded lines, explain whether its total sums those lines or higher-precision underlying amounts.

When a mismatch occurs, have the agent return both the calculation path and the first boundary where values diverge. Do not accept a patch that adds an unexplained adjustment to force agreement.

Limitations and the final acceptance question

Decimal arithmetic still has finite context and can round during operations. It does not supply correct business rules, repair invalid source data or eliminate every numerical-analysis concern. The four-case fixture above is intentionally narrow.

Extend it with approved examples covering negative values, division, boundary magnitudes and serialization round trips. Record which checks actually ran. Obtain qualified review where contractual, accounting or regulatory rules determine the result.

When evaluating a reporting workflow in the skills directory, ask it to explain a total difference before asking it to fix one. The strongest handoff is not a number that matches by accident; it is a reproducible calculation whose inputs and rounding policy are explicit.