Repository navigation
json: document that an unquoted float can be rounded twice - #701
Merged
iainmcgin merged 2 commits intoOct 14, 2026
Merged
Conversation
Refs anthropics#647. An unquoted JSON number with a fraction or exponent reaches `json_helpers::float` as the `f64` the deserializer parsed and is then narrowed to `f32`, while a quoted decimal (and an unquoted integer in the i64/u64 range) is rounded once. A decimal close enough to an `f32` midpoint becomes that midpoint as an `f64`, and narrowing rounds it to even, so `1.0000000596046448` decodes to 1.0 unquoted and 1.0000001 quoted. A serde visitor sees only the `f64`, so the original decimal cannot be recovered; rounding once needs the token text (`arbitrary_precision`, see anthropics#482, or a raw-value capture). Document the behaviour where users look for it: the `json_helpers::float` rustdoc, the `FloatValue` `Deserialize` impl, `DynamicMessage::from_json`, and the JSON section of the guide. Pin the double-rounded and single-rounded paths, and the single rounding of integer tokens, for generated helpers and `DynamicMessage::from_json`.
|
All contributors have signed the CLA ✍️ ✅ |
The change adds documentation and two tests that pin the current behaviour. A Changed entry would read as a behaviour change in the release notes.
iainmcgin
reviewed
Oct 14, 2026
iainmcgin
left a comment
Collaborator
There was a problem hiding this comment.
[claude code] Pushed one commit (befada9) that removes .changes/unreleased/changed-20261013-062603.yaml. The PR is documentation plus two tests that pin current behaviour, and a docs-only change carries no fragment here: a Changed entry would read as a behaviour change in the release notes. Everything else is as you wrote it.
iainmcgin
approved these changes
Oct 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #647. This documents the behaviour rather than changing it, so the issue can stay open to track a real fix through #482.
An unquoted JSON
floatwith a fraction or exponent reachesjson_helpers::floatas thef64serde parsed and is then narrowed withas f32, so it is rounded twice. A quoted decimal, and an unquoted integer in thei64/u64range, is rounded once. A decimal close enough to anf32midpoint becomes that midpoint as anf64and then rounds to even, so1.0000000596046448decodes to1.0unquoted and1.0000001quoted.The visitor sees only the
f64, and anf64sitting exactly on a midpoint does not say which side the decimal was on, so the helper cannot correct it. Rounding once needs the token text:arbitrary_precision(#482), or a raw-value capture, which would tie the helpers toserde_jsonwhile they stay generic overDeserializer. As the issue suggests for that case, this PR documents the behaviour:json_helpers::floatrustdoc: when the two paths differ (by onef32ulp), the issue's example, the overflow-threshold case (rejected unquoted, accepted quoted), that protobuf C++ narrows the same way while protobuf-go rounds once, and that only a producer can avoid it, by quoting.FloatValue'sDeserializeimpl (buffa-types/src/wrapper_ext.rs) and inDynamicMessage::from_json, since users of generated messages, wrappers and reflection never namejson_helpersdirectly.docs/guide.md, next to thearbitrary_precisionnote.DynamicMessage::from_json. They compare exact bits, so a future single-rounding fix will fail them and prompt a docs update. The example input gives the samef64with or withoutserde_json'sfloat_roundtrip.Changedchangelog fragment marked as documentation, with no behaviour change.Verification
task lint,task docandtask test-local(4186 passed, 0 failed), with the repository-pinned protoc 33.5.rust-code-reviewerandrust-api-ergonomics-reviewerper CLAUDE.md: no Critical or High findings. Addressed the Mediums: the midpoint bound in the rustdoc no longer assumesfloat_roundtrip, the note now also appears onFloatValue,DynamicMessage::from_jsonand in the guide, and the guidance says only the producer can avoid the difference. Also addressed the Lows: integer tokens are excluded (with a test), the overflow case is noted, and the changelog isChangedwith "No behaviour change".