ERP & Billing
July 28, 2026
7 min read
By Adhya Engineering Team

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.

typescriptproduction pattern
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.

Store all monetary amounts as fixed-point Decimals in PostgreSQL.
Never perform client-side financial calculations; compute all totals on the authoritative server.
Snapshot item prices, tax percentages, and calculated totals permanently upon invoice generation.
Maintain an append-only audit ledger for all transaction status modifications.

Common Invoicing Arithmetic Mistakes

Watch out for these common accounting system pitfalls:

Using Math.round() on floating-point totals instead of Decimal.ROUND_HALF_UP.
Calculating tax on the rounded sum of line items rather than calculating and rounding each line item consistently.
Allowing floating-point currency conversion rates to introduce fractional paise discrepancies.
Failing to freeze pricing and tax rates at invoice finalization time.

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 Service & Capability

Custom ERP Software & Business Management

Explore our custom ERP systems engineered with exact GST invoicing, inventory management, and automated ledger accounting.

Topics:#ERP#Accounting#Node.js#Decimal.js#GST Compliance

Have questions about implementing these patterns?

Our engineering team designs and operates custom systems, payment gateways, and SaaS architectures tailored to your specifications.