ReviewOS

zig-utils/zig-js

Intl.NumberFormat: round numeric components without arena-format churn

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

Parent: #475 Depends on: #564

Problem

After #564 removes structural-part materialization from plain format, exact attribution still records roughly 1,920,016 backing allocations for 384,000 steady default NumberFormat calls (about five per call). The scored invocation adds 257,820,286 backing bytes for the default row, 258,996,523 for fixed fraction, 258,996,515 for localized fixed fraction, and 341,523,169 for the significant-digit/compact uncommon row.

nfRound currently formats the integer into arena storage, builds the fraction in an independently growing list, copies it through toOwnedSlice, and rebuilds zero-padded integers through repeated allocPrint. Scientific exponents and exact-decimal padding add related temporary formatting allocations.

Scope

  • Freeze exact-checksum controls for default integer/fraction, fixed fraction, significant digits, rounding increment/mode, scientific/engineering, compact, large finite values, negative zero, and exact decimal-string inputs.
  • Represent rounded ASCII integer/fraction components in caller-owned native scratch with a capacity derived from ECMA-402 option bounds and finite-number limits, formatting and padding each component once.
  • Let the direct text sink consume scratch while it is live; the structural sink must own only bytes retained by returned parts. Preserve borrowed exact-decimal input when no transformation is required.
  • Cover exponent formatting and every rounding path without duplicating the rounding algorithm.

No-workaround rules

  • No workload/locale recognition, arbitrary fixed truncation, unsafe stack borrowing, lossy f64 shortcuts for exact decimal strings or BigInt, unbounded cache, or skipped numbering/grouping behavior.
  • Preserve ECMA-402 rounding modes/increments/priorities, significant/fraction digit limits, coercion order, negative zero, non-finite values, exact part boundaries, OOM behavior, and moving/no-GIL ownership.

Acceptance

  • Focused controls and attribution are frozen at the exact parent before implementation.
  • Seven-pair exact-parent evidence improves common direct-text rows and does not regress significant/scientific/compact or structural controls outside noise.
  • Scored allocation requests, cumulative backing bytes, and current backing bytes fall on the frozen steady rows.
  • format(x) === formatToParts(x).map(p => p.value).join("") remains true across every covered rounding family and numbering system.
  • Exact-parent Intl402 NumberFormat and BigInt locale compatibility remain regression-free.
  • OOM, bounded heap, moving GC, no-GIL, TSan, and full units pass with zero leaks.
9 days ago

Benchmark boundary landed on main in 8e708e16. The text/parts controls cover all scoped rounding families and match system JavaScriptCore checksums exactly at 13,162,000 / 19,442,000.

Exact-parent seven-sample medians are 53,147,083 ns text and 125,230,916 ns parts. Scored attribution deltas are:

  • text: 80,023 allocations, 19,295,712 cumulative backing bytes, 12,713,350 current backing bytes
  • parts: 268,019 allocations, 29,360,112 cumulative backing bytes, 8,596,268 current backing bytes

The known explicit-compact-precision and BigInt-part discrepancies discovered while constructing the matrix are deliberately tracked in #566 and #567 rather than hidden in this performance checksum.

9 days ago

Implemented and pushed to main in 339e3d1b, following the exact-checksum boundary in 8e708e16.

nfRound now writes integer, fraction, padding, and exponent digits into caller-owned native buffers whose capacities are derived from finite-f64 and ECMA-402 digit bounds. Plain text consumes those slices synchronously; structural parts duplicate only scratch-backed numeric storage, and RelativeTimeFormat explicitly retains the components it returns. Exact decimal strings remain borrowed unless padding is required.

Seven-pair exact-parent medians (all checksums exact):

  • rounding text: 53,715,667 -> 52,426,333 ns (-2.40%)
  • rounding parts: 124,344,083 -> 123,506,875 ns (-0.67%)
  • steady default: 716,443,958 -> 708,923,125 ns (-1.05%)
  • steady fraction: 739,678,542 -> 730,941,959 ns (-1.18%)
  • steady uncommon: 1,001,225,875 -> 999,493,542 ns (-0.17%, neutral)
  • representative Intl: 520,177,917 -> 508,394,000 ns (-2.27%)

Frozen text attribution falls from 80,023 allocations / 19,295,712 cumulative bytes / 12,713,350 current bytes to 80,022 / 9,023,932 / 2,426,990 (-53.23% cumulative, -80.91% current). Structural parts remains flat at 268,019 allocations / 29,360,004 cumulative bytes because returned parts must retain their numeric bytes.

Validation:

  • Intl402: 3,341/3,341 (exact parent 3,341/3,341; 0 test262 flips)
  • NumberFormat: 249/249
  • RelativeTimeFormat: 80/80
  • Number locale formatting: 7/7
  • BigInt locale formatting: 11/11
  • focused NumberFormat and TSan: 7/7, including OOM, bounded heap, moving nursery, and no-GIL publication
  • full sharded units: 1,646 pass, 1 skip, 0 fail, 0 leak

All scoped acceptance criteria are satisfied. The separately discovered compact-precision and BigInt-part correctness gaps remain tracked in #566 and #567.

Sign in to comment on this issue.