20 August 2026
Why Rechvix Never Uses Floats for Money
Ask most engineers why you shouldn’t use float64for money and you’ll get the textbook answer: floating point can’t represent every decimal fraction exactly, so 0.1 + 0.2 doesn’t equal 0.3. True, but it undersells the actual danger in a billing system — this isn’t a rounding curiosity, it’s a compliance and trust problem.
What actually goes wrong
An invoice with GST at 18% on ₹2,480.00 has to produce a tax amount that matches, to the paisa, what the GST portal computes, what your customer’s accountant computes, and what your own GSTR-1/3B filing reports. If your invoice engine stores that ₹2,480.00 as a float64, you are not computing on ₹2,480.00 — you are computing on whatever the nearest representable binary approximation happens to be, and that approximation compounds across every line item, every tax component, and every rounding step in the calculation.
At small scale this occasionally produces a one-paisa mismatch that looks like a typo. At real transaction volume, across thousands of invoices, it becomes a reconciliation nightmare: your ledger doesn’t balance, your GST filing doesn’t match your books, and there is no way to point at the exact cause because the error isn’t in your business logic — it’s in the number type underneath it.
What Rechvix does instead
Every monetary column in the database is NUMERIC(20,6) (amounts) or NUMERIC(24,12)(rates, which need more precision because they get multiplied) — PostgreSQL’s exact, arbitrary-precision decimal type. In Go, we never let a raw float32/float64touch a monetary value. Instead there’s an internal Money type wrapping shopspring/decimal:
// internal/platform/money
type Money struct {
amount decimal.Decimal
currency string
}
// Arithmetic always goes through Money's own methods —
// there is no path from a Money value back to a raw float.
func (m Money) Add(other Money) (Money, error) { ... }
func (m Money) Mul(factor decimal.Decimal) Money { ... }This isn’t a style preference enforced by a linter comment — the type system makes the mistake structurally hard to make. A monetary field simply isn’t declared as float64anywhere in the schema or the Go structs that model it, so there’s no accidental path for a float to sneak into a tax calculation.
The cost of doing it right
Decimal arithmetic is slower than native float operations and a little more verbose to write. For a billing and tax platform, that trade is not close: a fast, wrong number is worse than a slightly slower, exactly correct one. Rechvix is built for double-entry correctness first — see the next post on how that correctness is enforced at three separate layers, not just assumed.