# When does float accumulation over a ledger diverge from decimal arithmetic? **Date of Record:** 2026 November 1 **Scope:** Applies to monetary or unit-sensitive accumulation where values are stored or summed as binary floating point and compared against a decimal or integer reference. Excludes analytics where relative error is tolerated and no reconciliation occurs. ## Core Distinction **Predicate.** Binary floating point represents sums exactly if and only if every addend and every partial sum is a dyadic rational within the mantissa width. Monetary values in base 10 are generally not dyadic, so error accumulates as O(n * eps * magnitude). Safe if and only if values are held as integer minor units or decimal, and conversion to float happens only at a display boundary. **Failure.** Each addition rounds. The rounding is unbiased per operation but the accumulated total drifts, and the drift depends on the order of addition. Two systems summing the same set in different orders produce different totals. **Asymmetry.** A single wrong value is caught by a range check. Accumulated drift is not, because every individual value is plausible and the total is plausible; it is only wrong when compared against an independent recomputation. ## What Was Observed **Signature.** Reconciliation mismatch between two ledgers that both compute correctly. The difference is small, non-constant, and changes when the processing order changes. Re-running the same batch on a different shard assignment changes the delta. **Trace.** Ten million additions of values near 0.1 in float64 produce a total that differs from the decimal sum by roughly 1e-6 relative. At cent resolution on a billion-unit ledger, that is thousands of cents. Partial sums near 2^53 lose unit-level precision entirely, so late additions in a large account are silently absorbed. **Why monitoring stays green.** Every written value is a valid float. Every total is within tolerance of the expected magnitude. The error is only visible against an exact reference, which most systems do not maintain. ## Condition of Validity Accumulation is exact only while at least one holds: 1. **Representation.** Values are stored as integer minor units, and all arithmetic is integer or decimal. 2. **Bound.** The number of additions and the magnitude are provably within the range where float error is below the required resolution. 3. **Order.** Addition order is fixed and the rounding error is measured and reconciled, not assumed away. Condition 1 is the only one that does not degrade as volume grows. ## Decision Boundary - Integer or decimal representation: exact. - Float with a proven error bound below resolution: acceptable, must be re-proven when volume or magnitude changes. - Float with no bound, reconciled against a decimal reference: the reference catches it, but only if reconciliation is mandatory and alerted. - Float with no bound and no reference: unsound, and undetectable by inspection. ## Operational Effect The defect is invisible until a second computation exists, so it survives every test that checks consistency with itself. Remediation is a representation change at the storage boundary, not a rounding fix at the display boundary. The trap is to round at output. That hides the drift without removing it, and it makes the drift non-associative, so the ledger no longer sums to itself across shard boundaries. ```python from dataclasses import dataclass from decimal import Decimal class LedgerPrecisionError(ValueError): """Raised when a monetary accumulator cannot represent its sum exactly.""" @dataclass(frozen=True) class LedgerGate: representation: str # "integer-minor", "decimal", "float" max_additions: int = 10 ** 6 resolution_units: int = 1 def verify(self) -> bool: if self.representation in ("integer-minor", "decimal"): return True if self.representation != "float": raise LedgerPrecisionError(f"unknown representation {self.representation!r}") raise LedgerPrecisionError( f"float accumulator over {self.max_additions} additions cannot " f"guarantee resolution {self.resolution_units}" ) def dispatch_accumulator(gate: LedgerGate): try: gate.verify() except LedgerPrecisionError: return Decimal return float ```