Commit d04ddf5
* 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
0 commit comments