JSON Precision Auditor

JSON Precision Auditor

Paste JSON. This page lexes the raw text itself, computes the exact decimal value of every number literal with BigInt arithmetic, computes the exact value of the IEEE 754 double your parser will produce, and reports the difference. It ranks findings by whether the value survives a round trip, not by whether the value is exact.

Headline finding

A literal can change value and still re-print byte-for-byte as what you pasted

The obvious check for this is String(JSON.parse(x)) !== x: parse it, print it back, compare the text. It reports nothing for any of the three literals below, and all three change value. They are 17 to 19 digit IDs ending in round digits, which is what you receive when an upstream JavaScript service already round-tripped a bigint through a double before handing it to you.

Computed in this browser when the page loaded, from these three literals only. Nothing above is stored in the page as text.

Audit your own JSON

Nothing leaves the browser. Large documents are audited in full; only the rendered tables are capped, and the cap always prints the real total.

How the ranking works

Findings are ordered by round-trip survival: whether the bytes you pasted come back out the other side, and whether the value behind them is the value you sent.

Severity 1, invisible
The literal is integer valued, the double holds a different integer, and the double re-prints as the exact same characters you pasted. The text round-trips. The value does not. The naive heuristic is silent here, which is why this class leads.
Severity 1, visible
The value changed and the re-print differs. Four sub-classes: an integer past 2^53-1 such as a Snowflake or Discord ID, a bigint primary key or a nanosecond timestamp; a decimal carrying more precision than binary64 holds; an exponent that overflows to IEEE 754 infinity; and a magnitude that underflows to zero.
Severity 2
The value is exact today, but the literal is an integer outside the interoperability range RFC 8259 names. Nothing is wrong with the value in a binary64 parser. The guarantee that every implementation agrees on it is gone.
Severity 3
Ordinary inexact decimals: the literal is not integer valued, the double is off by at most half an ulp, and the double re-prints identically. This collapses into one row with a count. A thousand row price list would otherwise produce a thousand findings with deltas near 1e-15 and bury the one that matters.
No value change, formatting only
The value survives exactly and only the spelling changes: 1.0, 1e2, 1.500, -0. These are what the naive heuristic reports as damage. One row with a count.

The naive heuristic, run beside the exact one

Every audit runs two engines over the same literals and prints both answers.

The summary prints how many literals the exact engine says changed, how many of those the naive engine misses, and how many literals the naive engine flags whose value did not change at all. Every one of those counts comes from the document you pasted.

For every literal whose value changed, the exact engine also checks that the double is the correctly rounded nearest one, by verifying in BigInt that twice the absolute error is at most one ulp. That check is reported per document rather than assumed, because the double being compared against is the one this browser produced, which is the one your JavaScript code will see.

What this refuses to do

Sources

RFC 8259, section 6, Numbers. Quoted verbatim:

This specification allows implementations to set limits on the range and precision of numbers accepted.
Note that when such software is used, numbers that are integers and are in the range [-(2**53)+1, (2**53)-1] are interoperable in the sense that implementations will agree exactly on their numeric values.
A JSON number such as 1E400 or 3.141592653589793238462643383279 may indicate potential interoperability problems, since it suggests that the software that created it expects receiving software to have greater capabilities for numeric magnitude and precision than is widely available.
Numeric values that cannot be represented in the grammar below (such as Infinity and NaN) are not permitted.
Leading zeros are not allowed.

The grammar itself carries no magnitude or precision bound. The repetition operators are unbounded, so an arbitrarily long integer part, fraction part and exponent are all well formed JSON. RFC 8259, section 6, quoted verbatim:

      number = [ minus ] int [ frac ] [ exp ]

      decimal-point = %x2E       ; .

      digit1-9 = %x31-39         ; 1-9

      e = %x65 / %x45            ; e E

      exp = e [ minus / plus ] 1*DIGIT

      frac = decimal-point 1*DIGIT

      int = zero / ( digit1-9 *DIGIT )

      minus = %x2D               ; -

      plus = %x2B                ; +

      zero = %x30                ; 0

Source: https://www.rfc-editor.org/rfc/rfc8259#section-6. That is why the grammar lane on this page is reported separately from the value lane: a literal can be perfectly legal JSON and still be a value your parser cannot hold, and a literal can be rejected by a strict parser while its value is exactly representable.