(0.7016570306969449).toString() // goja: "0.701657030696945" — parses to a DIFFERENT float64
// V8/spec: "0.7016570306969449"
ECMAScript NumberToString requires the produced digits to round-trip exactly; 0.701657030696945 is outside the half-ulp interval of the input, so JSON.parse(JSON.stringify(x)) !== x for these values (~1 in 15,000 uniform doubles; every affected value is one whose correct shortest repr ends in a round-up-to-9, and only when the Grisu fast path bails).
Root cause is in the bignum fallback of dtoa mode 0 (ftoa/ftoa.go, closest-digit choice in the Steele & White loop). Original dtoa.c:
if ((j1 > 0 || (j1 == 0 && (dig & 1 || bias_up))) && dig++ == '9')
goto round_9_up;
dig++ == '9' tests the pre-increment value — carry propagation applies only when the digit was already '9'. The Go port increments first and tests the post-increment value, so a round-up landing ON '9' (dig was '8') is wrongly routed through roundOff and rounded a second time, dropping a digit.
Fix (move the '9' check before the increment) with details and a differential test vs Go's strconv shortest formatter: grafana/sobek#142 (sobek carries the identical port; the same patch applies here).
Other examples: 0.24414061428229689, 0.5084920399817559, 0.19243255639630719, 0.5342431914336429, 0.9868917361939819.
ECMAScript NumberToString requires the produced digits to round-trip exactly;
0.701657030696945is outside the half-ulp interval of the input, soJSON.parse(JSON.stringify(x)) !== xfor these values (~1 in 15,000 uniform doubles; every affected value is one whose correct shortest repr ends in a round-up-to-9, and only when the Grisu fast path bails).Root cause is in the bignum fallback of dtoa mode 0 (
ftoa/ftoa.go, closest-digit choice in the Steele & White loop). Original dtoa.c:dig++ == '9'tests the pre-increment value — carry propagation applies only when the digit was already '9'. The Go port increments first and tests the post-increment value, so a round-up landing ON '9' (dig was '8') is wrongly routed throughroundOffand rounded a second time, dropping a digit.Fix (move the '9' check before the increment) with details and a differential test vs Go's
strconvshortest formatter: grafana/sobek#142 (sobek carries the identical port; the same patch applies here).Other examples: 0.24414061428229689, 0.5084920399817559, 0.19243255639630719, 0.5342431914336429, 0.9868917361939819.