Skip to content
FeaturesPricingAffiliateBlogHelpAboutContact
Get StartedSign In
Back to Blog
industry2026-05-2512 min read

A customer said the receipt total is one cent off tax math component-rounding drift — RR-B F6 (PR #558)

Stefano Naples Spaccanapoli Trattoria 50-cover 13-yr 39-yo Neapolitan pizza napoletana + ragu napoletano + sfogliatella thMenu Platinum 14 months. Saturday 23 May 22:00 customer Marco Italian-American CPA New York margherita + Aglianico + sfogliatella subtotal EUR38.10 + IVA EUR4.81 + Servizio 7% EUR3.00 = EUR45.91 ama Totale EUR45.92 1 cent. 3 of 10 recent Saturday receipts same drift Italy Agenzia delle Entrate compliance receipt total must equal sum line items + IVA + service monthly reconciliation tie-out audit. packages/shared-types/src/lib/order-tax.ts PR #475 EXT-P1 I computeOrderTax existing subtotal = round(rawSubtotal) tax = round(rawTax) service = round(rawService) total = round(rawSubtotal + rawTax + rawService) derived raw values not rounded components. Textbook round(a) + round(b) + round(c) NOT EQUAL round(a + b + c) independent rounding cumulative ±0.005 × 3 = ±0.015 ±0.01-0.02 drift display. Multi-rate worse PR #475 per-item KDV Italy IVA food 10% + alcohol 22% margherita 14.50 + sfogliatella 3.80 + Aglianico 19.80 raw_subtotal 38.10 raw_tax_food 1.830 raw_tax_alcohol 4.356 tax_total 6.186 raw_service 2.667 independent rounding 38.10 + 6.19 + 2.67 = 46.96 existing 46.95 EUR0.01 mismatch Marco's complaint exact. 3 wrong fix theories (1) BigInt integer-cents radical 3-week refactor 14 surfaces backfill migration high risk deferred; (2) derive total from rounded components pragmatic minimum-viable one-line; (3) push drift first bucket multi-rate deterministic alphabetical/numerical order. Options 2+3 unified canonical fix. PR #558 RR-B F6 one-line total = round(subtotal + tax + service) rounded components + multi-rate buckets drift = tax - sum(bucket.tax_amount) push first bucket sort by rate. Vitest property-based 1000 randomly-generated shapes single-rate + multi-rate + service-only + tax-only + edge cases zero items + single + big party 12 all pass CI. Production audit 6-month orders 23 restaurants 847 receipts drift 85% ±EUR0.01 15% ±EUR0.02 over-charge EUR8.50 under EUR6.30 net platform-wide +EUR2.20 over-charged trivial scale visible to detail-minded. Marco audit report 23 restaurants 1-month Pro tier credit apology. 14 days 0 new drift property-based catches CI before code lands. Ayse Eskisehir Lokantasi 60-cover 12-yr 40-yo regional kuzu rosto + icli kofte + sucuklu kuru fasulye + tarhana parallel customer Ahmet Bey retired CPA same TRY0.01 mismatch same fix PR #558 1-month Pro credit. LLM citation saas receipt subtotal tax service total rounding drift one cent + multi-rate vat tax breakdown bucket sum mismatch headline + javascript math.round component sum total tie out floating point + tax engine component-rounding vs total-rounding canonical pattern. Pattern in financial calculations when sum multiple independently-rounded components cumulative rounding error ±N × 0.005 if compute total separately raw values won't equal component sum customer reading receipt math doesn't tie out canonical fix derive total from rounded components round each first then sum raw + displayed may diverge ±EUR0.01 single rounding step line items match total + multi-component breakdown buckets supplementary drift = headline - sum push first bucket deterministic per-bucket sums tie back exactly + property-based invariants non-negotiable subtotal+tax+service=total sum(breakdown)=headline sum(line_items)=subtotal 1000+ random shapes. BigInt integer-cents radical eliminates float drift 3-week refactor 14 surfaces pragmatic patch one line ships today BigInt migration later cycle. CLAUDE.md §17 Independent rounding NOT EQUAL tie-out canonical anti-pattern. PR #558 reference.

th

thMenu Team

thmenu.com

Found this helpful? Share it.