Skip to content

PERRY_JSON_TAPE=1: extreme-exponent numbers keep their source text instead of round-tripping through f64 #9252

Description

@proggeramlug

With the tape path forced on, three number literals survive JSON.parse as their source text rather than as the double they denote:

JSON.parse("[1e308]")    →  perry: [1e308]     node: [1e+308]
JSON.parse("[-1e308]")   →  perry: [-1e308]    node: [-1e+308]
JSON.parse("[1e-400]")   →  perry: [1e-400]    node: [0]

The third is the substantive one: 1e-400 is below the smallest subnormal double, so it is 0, and Node prints 0. Perry answering 1e-400 means the value never went through an f64 — the tape appears to carry the literal's bytes and re-emit them on stringify. The first two are the same mechanism showing up as a formatting difference (1e+308 is what Number.prototype.toString produces; 1e308 is the source spelling).

Scope — not the default path

  • Default (TapeMode::Auto): unaffected. All 64 cases of my JSON accept/reject battery match Node, and a 4001-element document containing 1e308, -1e308, 1e-400, 1.25e2 and 5e-324 round-trips identically.
  • PERRY_JSON_TAPE=1 (ForceOn): diverges on the three cases above. The same large document is also identical under ForceOn, so the trigger is the small/simple-document shape that ForceOn routes to the tape and Auto does not.

So this is an opt-in-mode bug rather than a live one. It still matters, because the knob exists to A/B the tape and anyone measuring with it — as I was, while investigating #9184 — gets wrong values back rather than a warning.

Suggested check

Whatever the tape stores for a number entry, the value handed back to JS should be the result of the same decimal→f64 conversion the direct parser performs, so that 1e-400 becomes +0 and 1e308 becomes the double whose toString is 1e+308. A round-trip assertion over exponent extremes (1e308, 1e-400, 5e-324, 1.7976931348623157e308, 2**53+1) would pin it.

Found with the 64-case accept/reject battery from #9184, run against the new one-pass parser from #9224 — which, for the record, passes all 64 on the default path. That work is solid; this is the tape path beside it.

https://claude.ai/code/session_012Ys25ni6VwDKE71o1NTYAT

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions