Cashback Rounding Precision Automation: A Cent Lost Every Time, Always the Same Way

A number's decimal value and its binary representation are not always the same number
Ordinary decimal fractions — one that ends cleanly at two or three places, the kind money is denominated in — frequently have no exact representation in the binary floating-point format nearly every programming language uses by default for a plain number. The classic, well-known example is that `0.1 + 0.2` does not equal `0.3` in most languages; the same underlying fact applies just as much to a single value like 0.615, which a calculator or a spreadsheet would round up to 0.62 without hesitation, but which a computer's own floating-point hardware stores as a value fractionally smaller than 0.615 — meaning a rounding function operating on that stored binary value rounds DOWN, producing a result a person doing the identical math by hand would never arrive at.
How the underlying problem shows up before you fix it
A monetary calculation multiplies or divides using an ordinary floating-point number type, then rounds the result to a fixed number of decimal places using a standard rounding function.
The resulting values are, the overwhelming majority of the time, exactly correct — the discrepancy affects only specific combinations of inputs that happen to land on the wrong side of a binary-representation boundary, making the defect genuinely difficult to notice through spot-checking or casual testing.
When it does occur, the discrepancy is always exactly one unit of the smallest denomination (here, one cent) and always in the SAME direction for the same input values — never randomly high or low — because the underlying binary representation of any specific decimal value is itself fixed and repeatable.
Reviewing the formula itself finds nothing wrong, because the formula IS mathematically correct — the failure lives entirely in the gap between a decimal value and the binary approximation a standard number type actually stores for it, a layer most manual code review never inspects.
The cumulative effect only becomes visible in aggregate, over many transactions, as a small, unexplained gap between an expected running total and the total actually recorded — by which point tracing it back to one specific rounding call, out of an entire codebase, is genuinely difficult.
Why a mathematically correct formula can still produce a wrong answer
A calculation being written correctly, in the sense that its formula matches the intended math exactly, is a separate question from whether the number TYPE performing that calculation can represent every intermediate value exactly. Standard floating-point numbers trade exactness for a wide range and fast hardware arithmetic — a trade that is invisible for the overwhelming majority of everyday values, and specifically visible for currency, because currency depends on EXACT decimal values (never an approximation "close enough" for other purposes) and because a rounding function's own decision of which way to round depends entirely on which side of a threshold the stored value falls on — a question floating-point representation can silently get wrong even when every other part of the calculation was written with complete accuracy.
How Centriu Loop moved cashback math out of floating point entirely
Centriu Loop's cashback-launch flow previously computed a sale's cashback amount as `(saleValue * percentage) / 100`, a plain floating-point calculation, then rounded the result to two decimal places with `Number(value.toFixed(2))` before applying an existing cap. That rounding step is the specific point of failure: `toFixed(2)` rounds against the number's own stored BINARY value, not the decimal value a person reading the formula would expect — and for specific combinations of sale value and percentage, those two are not the same number. The fix's own code comment documents a concrete, worked case: a R$ 12,30 sale at 5% genuinely computes to R$ 0,615, which should round up to R$ 0,62 under ordinary rounding rules — but 0.615, stored as a standard binary floating-point value, is actually approximately 0.6149999999999999911182, a value just fractionally below the halfway point, so the rounding function rounded it DOWN to R$ 0,61 instead. One cent, lost, on that specific combination of inputs, every single time it recurs — never randomly, because the same inputs always produce the identical binary approximation.
The fix replaces the entire calculation with integer arithmetic from the very first step. A sale's value in reais is converted to whole cents using `Math.round(reais * 100)` — a conversion the fix's own documentation confirms is exact for any value carrying at most two decimal places, because a single rounding step at that specific point corrects the tiny representation error before it can propagate any further. The cashback percentage, which the database stores with two decimal places of its own precision, is converted to hundredths of a percentage point (7.25% becomes 725) using the identical technique. From there, the entire multiplication happens in pure integers — cents multiplied by hundredths-of-a-percent, divided by ten thousand — with no floating-point division anywhere in the chain, and the final integer-cent result is converted back to a currency value only once, at the very last step, purely for display and for what gets written to the database.
The corresponding database function that actually applies and records the movement performs the equivalent calculation using PostgreSQL's own exact `numeric` type, with an explicit half-up rounding rule specified directly in the SQL rather than left to a default — so the two independent implementations, one in application code and one inside the database, are guaranteed to agree on every cashback amount by the shared design of the calculation itself, not by coincidence or by one side simply trusting the other's arithmetic.
What is actually built today
Every cashback calculation in Centriu Loop converts a sale's value to whole integer cents as its very first step, before any multiplication or division happens.
The cashback percentage is converted to hundredths of a percentage point using the same integer-safe technique, and the entire calculation runs in pure integer arithmetic from that point forward.
A currency value is produced only once, at the very last step of the calculation, specifically for display and for the value ultimately written to the database — never as an intermediate step floating-point rounding could still corrupt.
The database's own function that applies and records a cashback movement performs an equivalent, exact `numeric`-type calculation with an explicit half-up rounding rule, so the application and the database independently agree on every amount.
A dedicated function applying an organization's cashback cap operates on the same integer-cent values throughout, so the cap comparison itself never reintroduces a floating-point rounding step the core calculation had just eliminated.
A calculator that agrees with a computer, except when it does not (illustrative framing of the actual measured finding)
A shopkeeper doing the math on a R$ 12,30 sale at 5% cashback by hand, or on an ordinary calculator, arrives at R$ 0,615 and rounds it up to R$ 0,62, the way rounding rules normally work. Before the fix, Centriu Loop's own system, computing the identical formula, silently arrived at R$ 0,61 instead — not because anyone made an arithmetic mistake, but because the number 0.615, stored as an ordinary binary floating-point value, is not quite 0.615. After the fix, the same sale computes entirely in integer cents and correctly produces R$ 0,62, matching the shopkeeper's own calculator exactly.
What changes operationally
Centriu Loop's cashback calculation now runs entirely in integer-cent arithmetic, on both the application side and the database side, eliminating a class of one-cent rounding discrepancy that previously recurred silently and systematically on specific combinations of sale value and percentage — always in the same direction, for the same inputs, and invisible to a review of the formula itself.
When this is not the right fit
This automation covers specifically how Centriu Loop calculates a cashback amount from a sale's value and percentage. It does not cover the separate database-level guarantees this pillar's other Loop pages document — that a balance can never go negative, that the ledger is append-only, or that an exact-balance redemption is correctly accepted — each of which addresses a different, independent mechanism inside the same broader cashback engine.
Floating point with a rounding function vs. integer arithmetic throughout
Performing a monetary calculation in an ordinary floating-point number type and rounding the final result is simpler to write and is correct for the overwhelming majority of specific input values — which is exactly what makes the defect easy to miss during normal testing. It becomes silently incorrect specifically when a particular combination of inputs produces a decimal value whose nearest binary floating-point representation falls on the wrong side of the rounding function's own threshold. Converting every value to an integer unit (cents, in this case) before any arithmetic happens removes the ambiguity entirely, because integer arithmetic has no equivalent representation gap to fall into — the only version of the two that is correct for every possible input, not merely the overwhelming majority of them.
Related systems
Main system: Centriu Loop.
What it does NOT do
- Does not change the cashback PERCENTAGE or cap an organization has configured — this fix corrects only how an already-configured percentage is calculated against a sale's value, never what that percentage or cap itself is set to.
- Does not retroactively recalculate or correct any cashback amount that was already recorded before this fix shipped — it changes how every NEW calculation is performed going forward.
- Does not affect any other currency calculation outside Centriu Loop's own cashback-launch flow — this fix is specific to the code path confirmed to carry this exact floating-point rounding gap.
- Does not overlap with this pillar's separate pages on Loop's ledger append-only enforcement, balance non-negativity, or exact-balance redemption — each covers a different, independent mechanism in the same broader cashback engine.
- Does not claim every possible decimal input is now handled with unlimited precision — the fix's own documentation is explicit that a value carrying more than two decimal places of true precision would need a different representation entirely; no path in Centriu Loop's own cashback flow ever passes a value with that shape through this calculation.
Security and governance
Centriu Loop's cashback calculation runs entirely in integer-cent arithmetic on both the application and database side, eliminating a systematic floating-point rounding discrepancy that previously affected specific sale-and-percentage combinations. Any personal or financial data referenced in cashback records remains subject to Brazil's LGPD (Law No. 13,709/2018). Full detail on data handling lives at /governanca and /iso.
Pricing and contracting
Available by monthly subscription, with tiered plans. Values and terms come from the official pricing table at /precos (Centriu's central source — never restated here).
Frequently asked questions
How much money did this actually cost, in total?
The fix's own account does not claim a total historical figure — the discrepancy was exactly one cent per affected transaction, recurring only for specific combinations of sale value and percentage that fall on a particular side of a binary-representation boundary, not on every transaction.
Why does 0.615 not just round to 0.62 the way a calculator would?
Because a standard floating-point number type does not store 0.615 exactly — it stores the closest binary approximation, which for this specific value happens to be fractionally below 0.615, causing a rounding function to round down instead of up.
Could this same defect exist elsewhere in a system that handles money?
The same class of defect (floating-point arithmetic followed by decimal-place rounding) can occur anywhere money is calculated in an ordinary number type — this fix specifically closes the one code path confirmed to carry it inside Centriu Loop's cashback-launch flow.
Does the database independently verify this calculation, or does it just trust the application?
The database's own function performs an equivalent, independent calculation using PostgreSQL's exact numeric type with an explicit rounding rule — the two sides are designed to agree by construction, not by one simply accepting whatever value the other sends.
Was this bug found by a customer complaint or by testing?
Confirmed from the fix's own account: found and documented directly in the code's own explanatory comments alongside a broader hardening effort, with a concrete worked example, rather than described as originating from a specific customer report.
What does Centriu Loop cost?
It is sold by subscription with a published starting price — exact current values are on the central pricing page.
See how Centriu Loop calculates cashback down to the exact cent
Reach our commercial team directly, or leave your details below — we'll follow up with guidance for your case.
