jsonnumbersfloating-pointprecisionieee-754concepts

JSON Number Precision: Why 0.1 + 0.2 Isn't 0.3

·8 min read·JSON Concepts

JSON has one number type — and no precision rules

JSON's grammar defines a single number type and, crucially, says nothing about precision or range. It's just a sequence of digits with an optional fraction and exponent. That leaves the actual precision up to whatever parses the JSON — and in JavaScript (and most languages by default) that means IEEE 754 double-precision floating point. Doubles are binary, and binary can't represent every decimal exactly, which is where the surprises start. This is the decimal-precision companion to big integers in JSON, which covers the large-integer side of the same problem.

The famous example

js
0.1 + 0.2                     // 0.30000000000000004
0.1 + 0.2 === 0.3             // false
JSON.parse('{"total": 0.3}')  // 0.3 stored as the nearest double

This isn't a JavaScript bug — it's how binary floating point works in every language that uses IEEE 754 (Python, Java, Go, C, all do the same). The decimals 0.1, 0.2, and 0.3 have no exact binary representation, so each is stored as the *nearest* double, and the tiny rounding errors accumulate. The value round-trips through JSON fine; the issue is doing arithmetic on decimals that were never exact to begin with.

The other trap: the 2^53 integer limit

Doubles can represent integers exactly only up to 2^53 − 1 (Number.MAX_SAFE_INTEGER, 9007199254740991). Beyond that, integers silently lose precision:

js
JSON.parse('{"id": 9007199254740993}').id   // 9007199254740992  ← wrong!

The number parsed, no error was thrown, and the last digit is simply *wrong*. This bites hard with large IDs — Twitter/X snowflake IDs, database bigints, blockchain values — where a 64-bit integer from a backend gets mangled the moment JavaScript parses it. The data looks fine and is quietly corrupted.

How to store money, IDs, and exact decimals safely

The rule: don't put values that require exact precision into a JSON number. Use one of these instead.

  • Money → integer minor units. Store $19.99 as 1999 cents (an integer within safe range) and divide only for display. Never do financial math on JSON floats.
  • Money/decimals → strings. Store "19.99" as a string and parse with a decimal library (e.g. big.js, decimal.js) that does exact base-10 arithmetic.
  • Large IDs → strings. Serialize 64-bit IDs as "9007199254740993". This is why so many APIs return IDs as strings — it's the only way to survive a JavaScript client. See big numbers in JSON for the BigInt-based approach.
  • Comparisons → use an epsilon. If you must compare computed floats, check Math.abs(a - b) < Number.EPSILON rather than ===.
json
{
  "price_cents": 1999,
  "price_display": "19.99",
  "account_id": "9007199254740993"
}

What JSON preserves and what it doesn't

  • The text of a number is preserved across serialization — JSON.stringify(0.3) gives "0.3". The precision loss happens when the string is parsed into a double and when you compute with it, not in the JSON text itself.
  • Trailing zeros and leading formatting are not preserved1.50 parses to 1.5, and 1e3 becomes 1000. If exact formatting matters, use a string.
  • `NaN`, `Infinity`, and `-0` are not valid JSON numbers. JSON.stringify(NaN) produces null; you can't round-trip them as numbers.

The mental model: JSON numbers are perfect for *counts, quantities, and measurements where a little rounding is fine* — and wrong for *money, precise decimals, and large identifiers*, which belong in strings or integer minor units. For related handling see big numbers in JSON and the JSON.stringify guide.

Frequently asked questions

Because JSON numbers are parsed as IEEE 754 double-precision floats, and decimals like 0.1 and 0.2 have no exact binary representation. Each is stored as the nearest double, so the sum comes out as 0.30000000000000004. This happens in every language that uses IEEE 754, not just JavaScript.

Yes, in most parsers. Doubles represent integers exactly only up to 2^53 − 1. A larger integer like 9007199254740993 silently rounds to 9007199254740992 when parsed, with no error — which is why large IDs get corrupted.

Avoid floats. Store the amount as an integer number of minor units (e.g. 1999 for $19.99) and divide only for display, or store it as a string like "19.99" and use an exact-decimal library. Never perform financial arithmetic on JSON floating-point values.

Because 64-bit integer IDs exceed JavaScript's safe integer limit (2^53 − 1) and would lose precision when a JS client parses them. Serializing the ID as a string like "9007199254740993" preserves it exactly.

No. The JSON text preserves the number you wrote. The loss occurs when that text is parsed into an IEEE 754 double and when you do arithmetic on it. Keeping precision-sensitive values as strings avoids the parse-time conversion entirely.

Try JSONKit — JSON Developer Tools

Format, validate, convert and diff JSON. Instant, accurate and 100% private.