Why Float Math Fails Invoices: Decimal Precision in Node.js
How floating-point inaccuracies cause accounting leakage and GST compliance issues, and how Decimal.js guarantees arithmetic exactness.
The IEEE 754 Floating-Point Trap
JavaScript numbers are double-precision 64-bit binary format IEEE 754 values. Because binary floating-point cannot accurately represent decimal fractions like 0.1 or 0.2, evaluating `0.1 + 0.2` produces `0.30000000000000004`.
While negligible in simple UI animations, this discrepancy is catastrophic in financial software. Over thousands of invoices and ledger entries, rounding anomalies produce balancing discrepancies, failed tax reconciliation, and audit warnings.
Statutory GST Calculations and Rounding Rules
In commercial invoicing, statutory tax frameworks require exact arithmetic. For Indian GST compliance, split calculations for Central GST (CGST) and State GST (SGST) must be computed with standard half-up rounding to two decimal places.
If line-item taxes are calculated using native JavaScript floats, small inaccuracies accumulate. When multiplying item price by quantity and tax rate, the sum of line items will often fail to equal the invoice grand total by one or two paise.
import Decimal from "decimal.js";
// Configure Decimal.js for financial arithmetic
Decimal.set({ precision: 20, rounding: Decimal.ROUND_HALF_UP });
export interface InvoiceLineCalculation {
taxableAmount: string;
cgstAmount: string;
sgstAmount: string;
totalLineAmount: string;
}
export function calculateLineTax(
unitPrice: string,
quantity: number,
gstRatePercent: number
): InvoiceLineCalculation {
const price = new Decimal(unitPrice);
const qty = new Decimal(quantity);
const rate = new Decimal(gstRatePercent).dividedBy(100);
const taxable = price.times(qty);
const halfRate = rate.dividedBy(2);
const cgst = taxable.times(halfRate).toDecimalPlaces(2);
const sgst = taxable.times(halfRate).toDecimalPlaces(2);
const total = taxable.plus(cgst).plus(sgst).toDecimalPlaces(2);
return {
taxableAmount: taxable.toFixed(2),
cgstAmount: cgst.toFixed(2),
sgstAmount: sgst.toFixed(2),
totalLineAmount: total.toFixed(2),
};
}Decimal-to-Number Conversion Risks
A frequent developer mistake when using libraries like Decimal.js or Prisma Decimal is converting values back to native JavaScript numbers via `Number(decimalVal)` or `parseFloat()` right before passing them to helper functions or JSON responses.
Always serialize financial values as formatted strings (e.g. `decimal.toFixed(2)`) across API boundaries. This preserves exact precision in transit and prevents client-side floating-point degradation.
PostgreSQL Database Types: DECIMAL vs FLOAT
In your database schema, monetary values must never use `FLOAT` or `DOUBLE PRECISION`. Always specify `DECIMAL(12, 2)` or `NUMERIC(14, 4)`.
When utilizing Prisma ORM, Decimal columns map directly to `@prisma/client/runtime` Decimal instances, preventing implicit coercion to IEEE 754 floats during database reads and writes.
Immutable Financial Snapshots & Audit Trails
A critical rule of accounting software is immutability: once an invoice is finalized and issued, its numbers must never change, even if underlying product catalog prices or tax tables are updated in the future.
Always persist exact snapshot values (subtotal, each tax component, discount, and final payable amount) as immutable Decimal fields on the invoice record at the moment of issuance. If corrections are required, issue a formal Credit Note or Debit Note rather than mutating the original invoice.
Common Invoicing Arithmetic Mistakes
Watch out for these common accounting system pitfalls:
Enterprise Systems Context
Explore our Tailored Industry Solutions to review how we engineer specialized ERP platforms for fleet logistics, retail inventory, and clinic operations.
If your business is evaluating whether to build bespoke operational software or subscribe to packaged tools, read our strategic guide on Custom ERP vs Off-the-Shelf ERP: When Should a Business Build?
Related Engineering Guides
Custom ERP vs Off-the-Shelf ERP: When Should a Business Build? | Adhya Enterprises
An objective architectural and commercial evaluation comparing custom ERP software engineering with pre-packaged SaaS solutions.
Architecting Secure Payment Gateways & Webhooks in Next.js
Why frontend payment confirmations are inherently dangerous, and how to build server-authoritative HMAC webhook handlers with idempotency.