ReviewOS

zig-utils/zig-js

Intl.NumberFormat: generate locale currency display names from owned CLDR data

#569
Closed chris opened this 9 days ago ·
9 days ago

Parent: #475 Related: #567, #568

Problem

The #567 BigInt oracle exposed a distinct locale-data gap: new Intl.NumberFormat("de-DE", { style: "currency", currency: "EUR", currencyDisplay: "name" }).format(2n) emits 2,00 EUR in zig-js, while the locale result is 2,00 Euro. The shared NumberFormat emitter currently resolves long currency names only through the English DisplayNames subset and falls back to the ISO code for other locales. This affects Number and BigInt equally; it is not a BigInt structural defect.

Scope

  • Generate owned locale currency display names and plural forms from the repository-pinned CLDR inputs.
  • Resolve the display name from the formatter locale and rounded plural operands for Number and exact BigInt mathematical values.
  • Share generated immutable data across realms and preserve exact placement/source attribution in text, parts, and ranges.

No-workaround rules

No locale switch expansion by hand, system ICU/JSC dependency, copied output fixtures, English name presented as another locale, ISO fallback for data that exists in owned CLDR, or mutable process-global cache. Edit the generator and regenerate tables; never hand-edit generated data.

Acceptance

  • CLDR-derived singular/plural controls cover every supported locale/currency fallback family.
  • Exact Number and BigInt text/part/range oracles cover prefix/suffix placement and shared range sources.
  • Relevant Intl402 cases gain with no regressions.
  • Generator determinism, construction/steady allocation, OOM, bounded heap, moving GC, no-GIL, TSan, and full units pass.
9 days ago

Benchmark controls landed on main: bench(intl): isolate currency display names (#569).

The four workloads cover text, parts, ranges, and range parts across 16 CLDR locale/currency combinations with Number and exact BigInt values, default currency fractions, trailingZeroDisplay, non-Latin digits, zero-fraction currencies, and plural-sensitive languages.

Pre-fix checksums at 4 jobs/5 samples:

Sinkzig-jsJavaScriptCore
text74,1281,732,688
parts139,6961,653,952
range75,3121,733,504
range parts2,368,1924,298,272

The large semantic gap is expected: zig-js currently emits locale currency names only for English and falls back to the ISO code elsewhere. Median zig-js times are 772,750 ns text, 2,062,875 ns parts, 1,048,125 ns range, and 3,886,709 ns range parts. This benchmark-only commit flips 0 test262 cases.

9 days ago

Implemented and pushed directly to main:

The engine now owns generated currency names, distinct plural forms, plural-sensitive unit patterns, and currency-affix part boundaries for every supported locale in 49 deduplicated immutable data families. Selection uses exact rounded Number/BigInt operands, supports trailingZeroDisplay, and uses generated CLDR plural-range rules so ranges share one correct currency name/source. Pattern-insensitive ranges reuse their numeric parts; structural rebuild is reserved for actual pattern changes such as Romanian de.

Validation on the landed candidate:

  • NumberFormat test262: 249/249 (249 -> 249; 0 flips, no regressions)
  • full Intl402: 3,341/3,341
  • JavaScriptCore differential coverage: 17 locale/currency families exact for text, part types/values, Number, arbitrary-precision BigInt, equal/unequal ranges, Asian no-space patterns, explicit non-Latin digits, and Romanian currency-part classification
  • focused normal: 11/11; focused TSan: 11/11, including OOM, bounded heap, moving GC, parallel no-GIL publication, exact BigInt, and sink equivalence
  • full units: 1,650 passed, 1 skipped, 0 failed, 0 leaked
  • both generators reproduce byte-identical formatted output

Corrected 4-job checksums exactly match JavaScriptCore for text (1,732,688), parts (1,653,952), and range text (1,733,504). Range-parts checksum is 3,904,992 versus 4,298,272 solely because locale interval separators remain owned by #570. Fifteen-sample zig-js medians are 901,916 ns text, 2,161,083 ns parts, 1,278,833 ns range, and 4,126,083 ns range parts.

The Arabic default-numbering-system discrepancy exposed during attribution is independently tracked by #571; #569 uses an explicit -u-nu-arab control so its currency-name evidence is not confounded.

Sign in to comment on this issue.