Skip to content

ftoa: Number toString/JSON.stringify emits non-round-tripping string for ~1 in 15,000 doubles (dtoa carry mis-port) #720

Description

@danieldreier
(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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions