You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Float canonicalization differs from Go on exact decimal ties (round-half-even) #57
Found by the Rust-vs-Go generated-corpus check added for #54 (20 000 generated documents: the first divergence was at line 8270).
The same JSON number produces different canonical bytes, hence different digests, in Rust and Go:
input : {"v":[1124191380420304536, 912968984149762.3]}
rust : ...912968984149762.3
go : ...912968984149762.2
Both languages parse the literal to the same double (0x4309f2b93fedf812), whose exact value is 912968984149762.25 — exactly halfway between two 16-digit decimals that both round-trip. Isolated reproduction (same bit pattern on both sides):
912968984149762.3, .2, .25 all print as
Rust format!("{f}") (used by canonical_float)
912968984149762.3
Go strconv.FormatFloat(f, 'f', -1, 64) (the reference, canonicaljson.go)
912968984149762.2
Cause
On an exact tie between two equally short candidates, Go's Ryū-based shortest formatting rounds half to even (strconv/ftoaryu.go, ryuDigits32: cNextDigit == 5 && c0 && central&1 == 1). Rust's Display/{:e} picked the odd neighbour here. A3 says "matches Go", so this is a defect in the Rust port, in the digest-bearing path: canonical_float → to_canonical_json → digest. The same shortest-digit generation is used by collection.rs::go_float_display (the Go %v port), which has the same tie behaviour in Go.
The differential corpus did not catch it because no hand-written vector is an exact tie; the existing float vectors are all unambiguous.
Scope
One shared shortest-digit routine in the Rust engine that applies Go's tie rule (exact tie ⇒ the candidate with an even last digit, provided it round-trips), used by canonical_float and go_float_display.
Unit tests for ties (odd and even neighbour, negative values, a carry case) and for "no change off ties" against the current output.
A bulk Rust-vs-Go check over floats with short exact expansions (j · 2^-q), the family where ties occur, kept as a test or a differential vector.
Add the example above to conformance/differential / canonicalize so the Go peer pins it too.
Related: #54 (the cross-language property check that found it).
Resolved by #58 (merge f5f8bdb). Float canonicalization now matches Go on exact decimal ties (round-half-even). PR check was green on the merged head; main gate is green at 463ffde.
Activity
sinkog commentedon Oct 17, 2026
Resolved by #58 (merge
f5f8bdb). Float canonicalization now matches Go on exact decimal ties (round-half-even). PR check was green on the merged head;maingate is green at463ffde.