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
With the tape path forced on, three number literals survive
JSON.parseas their source text rather than as the double they denote:The third is the substantive one:
1e-400is below the smallest subnormal double, so it is0, and Node prints0. Perry answering1e-400means 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+308is whatNumber.prototype.toStringproduces;1e308is the source spelling).Scope — not the default path
TapeMode::Auto): unaffected. All 64 cases of my JSON accept/reject battery match Node, and a 4001-element document containing1e308,-1e308,1e-400,1.25e2and5e-324round-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-400becomes+0and1e308becomes the double whosetoStringis1e+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