Skip to content

Commit d04ddf5

Browse files
proggeramlugRalphclaude
authored
fix(intl): #5581 — NumberFormat numbering systems, roundingIncrement, zero-significant hang (#5737)
* fix(intl): #5581 — NumberFormat numbering-system digit transliteration + roundingIncrement Two spec-grounded gaps in `Intl.NumberFormat` rendering, both surfaced by the intl402/NumberFormat test262 cluster (#5581): 1. **Numbering systems.** Output digits were always Latin (`latn`); a formatter resolved to a non-Latin system (`-u-nu-arab`, the `numberingSystem` option, etc.) still emitted ASCII digits. Added `numbering_system_digits` (the full 77-system glyph table, generated from test262's `numberingSystemDigits`) and a `transliterate_parts_digits` pass that rewrites the digit glyphs of the `integer`/`fraction`/`exponentInteger` segments — separators, signs, and currency/unit/compact literals keep their locale glyphs. Applied in the shared `number_parts_from_resolved` core so `format`, `formatToParts`, and the DurationFormat consumer all benefit; `latn`/unknown is a no-op. 2. **roundingIncrement.** The option was validated and surfaced in resolvedOptions but never applied during rendering. Added `round_to_increment` (rounds the scaled integer to the nearest multiple of the increment, breaking ties via the shared `round_decision` core) and wired it into the standard notation branch. Refactored the ApplyUnsignedRoundingMode logic out of `rounding_up` into `round_decision` so fraction- and increment-rounding share one mode implementation. Flips 21 intl402/NumberFormat test262 cases (all 14 roundingIncrement tests, numbering-systems.js, and the fraction/significant cases that were gated on the non-Latin digit assertion) with zero regressions across the tree. Partial progress on #5581; the remaining NumberFormat clusters (units, accounting currency, compact-locale tables, formatRange) and the #5580/#5582 Temporal/ DateTimeFormat clusters are untouched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(intl): #5581 — NumberFormat significant-digit formatting of zero infinite-looped `round_to_significant` rendered a zero value as `("0", "")` and then ran the min-significant-digits padding loop `while significant_count(int, frac) < min_sig { frac.push('0') }`. But `significant_count` of an all-zeros decimal is always 0, so for any value of `0` formatted with significant digits (or compact notation, which rounds by significant digits) the loop never terminated — `format(0)` hung. The hang was latent (every locale, not numbering-specific); the preceding numbering-system commit merely unblocked the test262 rounding-mode cases far enough past the digit-set gate to reach `format(0)` and expose it. Render zero as a single "0" padded to `min_sig` total displayed digits (ECMA-402 ToRawPrecision: minSig 3 → "0.00") and return early, before the nonzero-only normalization loops. Flips the 9 intl402/NumberFormat `format-rounding-mode-*` cases plus format-significant-digits.js (+10); no regressions across NumberFormat / DurationFormat / PluralRules. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(intl): #5581 — honor roundingIncrement in currency/scientific/compact paths (CodeRabbit) Addresses CodeRabbit review on #5737: `roundingIncrement` was only applied in the standard decimal branch, so currency, scientific/engineering, and the fraction-mode compact path still ignored it (e.g. nearest-0.05 configs). Per ECMA-402 roundingIncrement is gated on fixed fraction-digit rounding, not on notation, and Node applies it uniformly — to the currency value, the scientific *mantissa*, and the compact value, all at maxFrac places. Factored the fraction-rounding step into a shared `round_fraction_or_increment` helper and routed the standard, scientific/engineering, and compact branches through it; the currency path (a separate native-float renderer) snaps its magnitude onto the increment grid up front via the same digit-string `round_to_increment`, respecting roundingMode. With `roundingIncrement == 1` (the overwhelmingly common case) the helper is byte-identical to the previous `round_to_fraction` + `trim_fraction`, so existing output is unchanged. Verified perry now matches Node byte-for-byte for currency (nearest 0.05), scientific (mantissa rounding), and compact increment configs. NumberFormat test262 unchanged at 158 pass / identical failure set (these notation+increment combos have no test262 coverage); DurationFormat (92) and PluralRules (39) unchanged — zero regressions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * style: cargo fmt number_format.rs (rustfmt wrapping) Reflow the generated numbering-system digit table and round_to_increment to satisfy cargo fmt --check (lint gate). Whitespace only — no behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(intl): #5581 — address CodeRabbit review (increment overflow, currency scale, mantissa carry) Three correctness findings from CodeRabbit on #5737, each verified byte-for-byte against Node: 1. roundingIncrement on wide fraction scales / large values. `round_to_increment` parsed the scaled integer as `u128`, so `maximumFractionDigits` near its 100 ceiling (or very large values) overflowed and silently fell back to plain fraction rounding, dropping the increment. The scaled integer is now kept as a digit string and reduced with small-divisor modular arithmetic (decimal_mod_small / decimal_add_small / decimal_sub_small); the increment (≤ 5000) and the dropped tail (bounded by the shortest decimal) stay small. Parity for halfEven is read off `q mod 2·increment`, avoiding any wide division. 2. Currency increment grid used the default scale. `currency_instance_parts` rounded/snapped on the currency's default fraction digits, ignoring minimum/maximumFractionDigits — so e.g. 3-digit USD snapped on 0.05 instead of 0.005. It now uses the resolved width `r.max_frac` (which already folds in the currency default plus options) for both the increment grid and the display, so grid and precision agree (1.234 → $1.235). Identical to before for the default-width common case. 3. Scientific/engineering mantissa carry. The exponent was computed before rounding, so a carry (9.9 → 10) emitted `10E0` instead of `1E1`. After rounding, a grown mantissa now renormalizes: recompute the exponent from msd+1 and reshape the significand — scientific `1E1`, while engineering keeps the digit when the new magnitude stays in the same power-of-1000 band (`9.9 → 10E0`, `999.9 → 1E3`, `99999 → 100E3`). NumberFormat test262 unchanged at 158 pass / identical failure set (these cases have no test262 coverage in the failing set); DurationFormat (92) and PluralRules (39) unchanged — zero regressions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Ralph <ralph@skelpo.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent ca63a7f commit d04ddf5

1 file changed

Lines changed: 902 additions & 24 deletions

File tree

0 commit comments

Comments
 (0)