TypeScriptArchitectureLessons
Mar 8, 2026

Why I Store Core Values as Integers, Not Floats

Most production bugs around numbers are not algorithm bugs. They are representation bugs.

D
Darius
darius.codes
3 min read

Most production bugs around numbers are not algorithm bugs. They are representation bugs. You stored a value as a float. Somewhere downstream, it drifted. Now your invoice is off by a cent and accounting wants to know why.

If a value must be exact, I store it as an integer.

The core problem

Floating-point numbers are binary approximations of decimal values. Many decimal fractions cannot be represented exactly in binary.

js
0.1 + 0.2 === 0.3; // false

You get 0.30000000000000004.

This is not a JavaScript bug. It is how IEEE 754 floating-point works across every language. And it hurts in real systems:

  • Equality checks fail unexpectedly
  • Repeated additions drift over time
  • Rounding differs by language and runtime
  • Serialization can change least-significant bits
  • Cross-service comparisons become fragile

If your domain requires exactness, floats are the wrong default.

Where integers win

Integers are exact. Addition and subtraction on integers is deterministic. The trick is to pick a scale and stick with it:

  • Money: store cents, not dollars
  • Time: store milliseconds, not seconds as float
  • Rates: store basis points, not percentages as float
  • Counters: store whole units

Currency:

ts
const priceCents = 1999; // $19.99
const taxCents = 150;
const totalCents = priceCents + taxCents; // 2149

Percentages as basis points:

ts
// 1% = 100 bps
const discountBps = 1250; // 12.50%
const amountCents = 10000;
const discounted = Math.floor((amountCents * (10000 - discountBps)) / 10000);

No drift. No rounding surprises. The math is boring and that’s the point.

Where floats are fine

Floats are appropriate when approximation is expected — scientific computation, graphics, sensor data, statistical modeling, ML feature values. In these domains, “close enough” is acceptable and often intended.

Where floats are dangerous

Avoid floats for anything a human can dispute:

  • Billing and invoices
  • Ledger balances
  • Payout calculations
  • Deterministic workflows
  • Idempotency keys derived from numeric values
  • Reconciliation pipelines

If someone can look at a number and say “that’s wrong by a cent,” you used the wrong type.

Practical rules

Encode units in names

Bad: amount, price, timeout

Good: amountCents, priceCents, timeoutMs

The name carries the invariant. When a new developer reads amountCents, they know the scale without checking the docs.

Convert only at the edges

Keep internal logic integer-only. Convert at I/O boundaries:

  • Display: cents to dollar string
  • Input: decimal string to cents integer

Everything in between stays as integers. No intermediate float conversions, no accidental precision loss.

Define rounding policy once

If conversion is needed, centralize it. Round half up? Bankers rounding? Floor? Put the policy in one utility, test it with edge cases, and never duplicate the logic.

Keep transport integer-safe

  • JSON: use integer fields for scaled values
  • Database: use BIGINT for large totals
  • APIs: document unit and scale explicitly

The point

Using integers for core values is not about style. It’s about preserving truth over time. In production systems, exactness compounds just like errors do. A one-cent rounding error today is a thousand-dollar reconciliation problem next quarter.

Store it as an integer. Convert for display. Sleep at night.

ende