fixes #10660; treat digit separators consistently when parsing floats - #10744
Merged
lukewilliamboswell merged 1 commit intoAug 13, 2026
Merged
Conversation
…g floats `_` is a separator everywhere in a float literal, but three places in the parser disagreed about that, so a literal with separators was read as a different number than the same digits without them: - `skipChars` advanced the offset without counting the underscores it skipped, so `offsetTrue` (offset minus consumed underscores) drifted and moved the decimal point; - the leading-zero skip in the fractional part used `"0"`, so it stopped at the first separator and the remaining zeros were counted as significant digits; - the trailing-zero scan indexed the original string with `offsetTrue`, walking back into the middle of the literal, and stopped at the first separator. On the fuzzer-found input from the issue this moved the decimal point by 163 orders of magnitude, and the slow path then spent minutes shifting to reconcile it. It now parses in the same time as the separator-free spelling and returns the same value. The added test asserts the invariant directly: a literal parses to the same bits with and without separators, across separators in the integer part, the fraction, the exponent and negatives.
Contributor
Greptile SummaryThe PR makes float parsing consistently exclude digit separators from logical offsets and significant-digit accounting.
Confidence Score: 5/5The PR appears safe to merge with no actionable correctness, security, or repository-rule issues identified. The changed offset bookkeeping is consistent with the stream invariant, malformed separator placement remains validated before slow conversion, and the regression tests exercise the corrected separator-bearing paths.
|
| Filename | Overview |
|---|---|
| src/builtins/num.zig | Adds focused tests proving valid digit separators do not change parsed f64 bits. |
| vendor/parse_float/FloatStream.zig | Keeps the separator count synchronized when skipChars consumes underscores. |
| vendor/parse_float/decimal.zig | Corrects leading- and trailing-zero accounting for underscore-bearing decimal input. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart LR
A[Float literal] --> B[Validate separator placement]
B --> C[FloatStream scanning]
C --> D[Physical offset]
C --> E[Logical offset excluding underscores]
D --> F[Trailing-zero scan]
E --> G[Decimal-point and significant-digit accounting]
F --> H[Float conversion]
G --> H
Reviews (1): Last reviewed commit: "fixes #10660; treat digit separators con..." | Re-trigger Greptile
lukewilliamboswell
approved these changes
Aug 13, 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 join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Reproduced the hang from the issue and found it comes from an inconsistency rather than from the slow path being slow:
_is a separator everywhere in a float literal, but three places in the parser disagreed about that, so the same digits parse as a different number depending on whether separators are present.FloatStream.skipCharsadvanced the offset without counting the underscores it skipped.offsetTrueis defined as offset minus consumed underscores, and callers use it to measure digit positions, so every later position shifted and the decimal point moved with them.skipChars("0"), which stops at the first separator, so the remaining leading zeros were counted as significant digits.offsetTrue. Since the string still contains the underscores, that walks back into the middle of the literal, and the scan also stopped at the first separator it met.On the input from the issue the combination put the decimal point at -2 instead of -165 and reported 89 significant digits instead of 9.
convertSlowthen had to reconcile a 163-order-of-magnitude difference by shifting, which is where the minutes went. With the fix that input parses as fast as its separator-free spelling and returns the same value,1.34781624e-166.The test asserts the invariant rather than the symptom: a literal must parse to the same bits with and without separators. It covers separators in the integer part, the fraction, the exponent, negatives, and the fuzzer-found input itself. I checked it fails without the fix — it aborts on that last case — and passes with it.
zig build run-test-zig: 4410/4410 pass with the fix (4409 before, plus the new one). Reverting only the parser change and keeping the test gives 4409/4411 with 1 crashed, which is the new test.fixes #10660
Disclosure: I work with an AI assistant (Claude Code, Anthropic). It did the bisection, wrote the patch and the test, and ran the builds; I checked the reasoning against
decimal.zigandFloatStream.zigand verified the before/after runs myself.