-
Notifications
You must be signed in to change notification settings - Fork 3
perf(engine): speed up FormatDouble shortest-representation scan #899
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+106
−11
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
daae716
perf(engine): speed up FormatDouble shortest-representation scan
frostney 755d09d
Merge remote-tracking branch 'origin/main' into claude/ecstatic-cohen…
frostney 1495fa5
docs(adr): link ADR 0079 to PR #899
frostney 61d1636
test(benchmarks): cover non-integer float toString; correct speedup t…
frostney 6d9aba9
Merge origin/main into claude/ecstatic-cohen-c61125
frostney 2f5bdcb
test(number): add non-integer coercion check and hoist toString bench…
frostney 80aeabe
test(number): assert all three coercion forms for both non-integer sa…
frostney File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| # FormatDouble first-hit precision scan | ||
|
|
||
| **Date:** 2026-06-28 | ||
| **Area:** `engine` | ||
| **Issue:** [#812](https://github.com/frostney/GocciaScript/issues/812) | ||
| **Pull Request:** [#899](https://github.com/frostney/GocciaScript/pull/899) | ||
|
|
||
| `FormatDouble` (`Goccia.Values.Primitives`) implements ES2026 §6.1.6.1.20 `Number::toString` for the non-integer case by finding the shortest decimal that round-trips: it scans the `Str(V:W)` precision width `W` from 9 (2 significant digits) to 24 (17 significant digits) and takes the **first** width whose output parses back to the original double. The normative step requires `k` (the digit count) to be "as small as possible", so the shortest representation is a conformance requirement, not a quality-of-implementation nicety. This path backs `Number.prototype.toString`, `String(x)`, template interpolation, property-key stringification, and `JSON.stringify` of floats; `toFixed`/`toExponential`/`toPrecision` use a separate `FormatDoubleToPrecision` path and are unaffected. | ||
|
|
||
| Issue #812 proposed replacing the linear scan with a binary search over `W` ("same candidates, fewer probes", assumed low risk). It is not low risk: it is incorrect. A sweep of ~70M doubles (FPC 3.2.2, prod `-O4` with `NOFASTMATH`) found the round-trip predicate `Val(Str(V:W)) = V` is **not monotonic** in `W` — 14,241 general-case values have a width that round-trips, a wider width that does not, then a wider one that does again, because FPC `Str` is not correctly rounded at every width. The upward first-hit scan is robust to these holes (the first hit is still the smallest, hence shortest), but a binary search can converge onto a hole above the true minimum: for 115 of ~60M sampled doubles it selected a wider width and emitted a non-shortest string (for example `9.18742501042000e+222` instead of `9.18742501042e+222`, or `6.110371725116101e+201` instead of `6.1103717251161e+201`), violating "k as small as possible". Every probe-skipping variant (stride, galloping, scan-down-until-false) fails for the same reason. **Decision: the scan stays first-hit-from-the-bottom; binary search and probe-skipping are rejected for this function.** A correct single-pass alternative would be a Ryū/Grisu shortest-representation algorithm, which removes the dependence on `Str`'s per-width rounding entirely; that is a larger spec-exact rewrite left for a future decision. | ||
|
|
||
| The performance concern behind #812 is addressed without changing the algorithm or its output. Each probe now reads `Str(V:W)` into a fixed `ShortString`, strips the right-justification padding in place, and parses with the locale-free `Val` instead of `Trim` + `TryStrToFloat`. `Val` selects the identical width — verified byte-for-byte against `TryStrToFloat` over 74.9M doubles with zero divergence — while avoiding the per-iteration heap allocation and the `TFormatSettings` scan. The probe loop itself is roughly halved; end-to-end the change is about **1.4× faster (−28% execution time)** on a `toString`-dominated float workload (2M `Number.prototype.toString` calls over 15–17-significant-digit doubles, bytecode, `--prod`), with the engine's per-call string allocation and dispatch a roughly constant overhead around `FormatDouble`. `benchmarks/numbers.js` covers this path (the `toString non-integer` bench). Regression tests in `tests/built-ins/Number/prototype/toString.js` and `tests/built-ins/JSON/stringify.js` lock the exact shortest output for computed fractional values and for several of the non-monotonic "hole" doubles, so any future move to binary search (or any change that lengthens these strings) fails the suite immediately. |
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
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
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
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
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
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.