Skip to content

fix(@formatjs/intl-durationformat): use BigDecimal for sub-second rollups#6466

Merged
longlho merged 2 commits intomainfrom
fix/durationformat-fractional-precision-6462
Apr 29, 2026
Merged

fix(@formatjs/intl-durationformat): use BigDecimal for sub-second rollups#6466
longlho merged 2 commits intomainfrom
fix/durationformat-fractional-precision-6462

Conversation

@longlho
Copy link
Copy Markdown
Member

@longlho longlho commented Apr 29, 2026

Summary

Sub-second units that roll up into a numeric parent (milliseconds: 'numeric', etc.) were combined with float arithmetic. 1 + 473/1e3 lands on 1.4729999999999998650, and roundingMode: 'trunc' truncated that to 1.472999999s instead of 1.473s.

Carry the value as BigDecimal end-to-end and pass its decimal string to NumberFormat. NumberFormat (V3, ES2023) parses the string as a Mathematical Value, sidestepping the IEEE 754 round-trip entirely.

Also adds ES2023.Intl to BASE_TSCONFIG.lib so formatToParts accepts the ${number} string overload at type-check time.

Fixes #6462

Test plan

  • New regression test covers the seconds/milliseconds/microseconds rollup cases
  • bazel test //packages/intl-durationformat/... — all 7 tests pass
  • bazel test //packages/... — full suite (374 tests) passes; the ES2023.Intl lib addition didn't break any other package's typecheck
  • CI green

🤖 Generated with Claude Code

…lups

Sub-second units that roll up into a numeric parent (`milliseconds:
'numeric'`, etc.) were combined with float arithmetic — `1 + 473/1e3`
lands on `1.4729999999999998650`, which `roundingMode: 'trunc'` then
truncated to `1.472999999s` instead of `1.473s`.

Carry the value as BigDecimal end-to-end and pass its decimal string to
NumberFormat. NumberFormat (V3, ES2023) parses the string as a
Mathematical Value, sidestepping the IEEE 754 round-trip entirely.

Also adds `ES2023.Intl` to BASE_TSCONFIG.lib so `formatToParts` accepts
the `${number}` string overload at type-check time.

Fixes #6462

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
})
}
let parts = nf.formatToParts(value)
let parts = nf.formatToParts(value.toString() as `${number}`)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is lossy, should carry BigDecimal all the way through

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might need to type guard downstream

…rFormat

Addresses review feedback on #6466: the previous version round-tripped
the BigDecimal through `.toString() as ${number}` before formatToParts,
which is conceptually lossy and required a hand-waving cast. NumberFormat
(V3) coerces non-primitive inputs through ToPrimitive → toString and
parses the result as a StringNumericLiteral, so passing the BigDecimal
straight through gives the same exact-decimal semantics with no cast.

- Widen `createMemoizedNumberFormat` to a `NumberFormatLike` that types
  `format`/`formatToParts` as accepting BigDecimal (and string).
- Drop the `${number}` cast at the formatToParts call.
- Revert the `ES2023.Intl` lib addition — no longer needed now that the
  call site doesn't depend on the V3 string overload signature.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@longlho
Copy link
Copy Markdown
Member Author

longlho commented Apr 29, 2026

Addressed in 17c55a9 — now passes BigDecimal straight through to nf.formatToParts (no .toString(), no ${number} cast). Native NumberFormat coerces non-primitive inputs via ToPrimitive→toString into a StringNumericLiteral, so the exact-decimal semantics survive without an intermediate string round-trip. Widened createMemoizedNumberFormat's return type (NumberFormatLike) to declare format/formatToParts as accepting BigDecimal | string so the call typechecks. Reverted the ES2023.Intl lib bump since it was only needed by the old cast.

@longlho longlho enabled auto-merge April 29, 2026 15:48
@longlho longlho added this pull request to the merge queue Apr 29, 2026
Merged via the queue into main with commit bd4cb1d Apr 29, 2026
3 checks passed
@longlho longlho deleted the fix/durationformat-fractional-precision-6462 branch April 29, 2026 15:53
longlho added a commit that referenced this pull request Apr 29, 2026
…ndency

The polyfill bundle imports BigDecimal (added in #6466 for sub-second
rollup precision), but @formatjs/bigdecimal was only listed in
BUILD.bazel — not in package.json. rolldown externalizes it, so consumers
hit "Rolldown failed to resolve import @formatjs/bigdecimal" at build
time after updating to 0.10.6.

Fixes #6467

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DurationFormat introduces error in fractional digits

1 participant