Found while verifying #7264 (fixed in #7265). Separate bug, separate path — filing so it isn't lost.
With PERRY_JSON_TAPE=0 (the direct recursive-descent parser, used as a bisection escape hatch and as the perry (mark-sweep, no lazy) row in benchmarks/json_polyglot), a JSON.parse → JSON.stringify round-trip does not preserve the shortest round-trippable double. The default tape path and node both do.
Reproducer
const items: any[] = [];
for (let i = 0; i < 10000; i++) items.push({ id: i, name: "item_" + i, value: i * 3.14159, tags: ["t"], nested: { x: i, y: i * 2 } });
const blob = JSON.stringify(items);
const parsed = JSON.parse(blob);
let s = 0;
for (let i = 0; i < parsed.length; i++) s += parsed[i].nested.x; // force materialization
const re = JSON.stringify(parsed);
console.log("len=" + re.length + " same=" + (re === blob));
perry (default, tape) len=944711 same=true
perry PERRY_JSON_TAPE=0 len=944728 same=false
node 26.5.1 len=944711 same=true
First divergence:
blob: ..."name":"item_83","value":260.75197,...
re : ..."name":"item_83","value":260.75197000000003,...
So the direct parser produces a double one ULP away from the one the tape parser (and node) produce for the same text 260.75197, and the re-serialization is 17 characters longer per 10k-record blob.
Impact
Likely area: the number-token → f64 conversion in crates/perry-runtime/src/json/parser.rs, versus materialize_number in crates/perry-runtime/src/json_tape.rs. One of them is not using a correctly-rounded decimal→binary conversion.
Found while verifying #7264 (fixed in #7265). Separate bug, separate path — filing so it isn't lost.
With
PERRY_JSON_TAPE=0(the direct recursive-descent parser, used as a bisection escape hatch and as theperry (mark-sweep, no lazy)row inbenchmarks/json_polyglot), aJSON.parse→JSON.stringifyround-trip does not preserve the shortest round-trippable double. The default tape path and node both do.Reproducer
First divergence:
So the direct parser produces a double one ULP away from the one the tape parser (and node) produce for the same text
260.75197, and the re-serialization is 17 characters longer per 10k-record blob.Impact
PERRY_JSON_TAPE=0— bisection runs, theidiomaticperry row ofbenchmarks/json_polyglot— sees different data than the default path. That makes the escape hatch unsound as a bisection tool for anything numeric: "the bug goes away with the tape off" can be the tape being right.bench_field_access's checksum by +850 (17 chars × 50 iterations).optimized(default/tape) row, and the cross-runtime checksum gate added in fix(json): stringify dropped every array element's properties past the inline slot floor #7265 mirrors that selection. So this is latent, not currently gating.Likely area: the number-token → f64 conversion in
crates/perry-runtime/src/json/parser.rs, versusmaterialize_numberincrates/perry-runtime/src/json_tape.rs. One of them is not using a correctly-rounded decimal→binary conversion.