Skip to content

EPBDS-16211: a number shown with decimals is formatted by a formatter made once - #221

Merged
AlexSamBY merged 2 commits into
masterfrom
render-float
Oct 13, 2026
Merged

AlexSamBY merged 2 commits into
masterfrom
render-float

Conversation

@AlexSamBY

Copy link
Copy Markdown
Member

What

renderFloat (src/core/components/renders.tsx) shows a number with a faded fraction. The Currency, Float, Double5 and Percent renderers show numbers through it, and so does Counter. With decimals, it formatted the fraction like this:

Number('0.' + fraction).toLocaleString(undefined, { minimumFractionDigits: decimals, maximumFractionDigits: decimals })

ECMA-402 specifies that call as a new Intl.NumberFormat each time. It now keeps one formatter for each count of decimals (fractionFormat), in the runtime's locale, as toLocaleString(undefined, …) used.

  • Dead code removed. The options' decimals == null ? undefined : branch is gone: inside showFraction, decimals is always above 0.
  • Not changed: the integer part's toLocaleString() takes no options, and costs 0.16 µs a call as it is.

Measured

Per value, in Node 24 (V8), 20 000 calls each:

call µs a call
toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2}) 9.65
one Intl.NumberFormat(undefined, …), kept, .format(x) 0.24

Output, byte for byte the same. I rendered renderFloat in 189 cases, on master and on this branch, and compared the HTML: 7 counts of decimals (none, 0, 1, 2, 3, 4, 6), 9 values (0, 0.5, 1.005, 12.3456, 1234.1, -7.25, 99.999, '3.14159', 10 000 000.123), and 3 sets of options (none, truncated, faded: false).

A whole document: no change, and why. I proposed this after a CPU profile of the ratingDetails demo example put 4.8 ms of its mount in renderFloat. That mount does not change:

  • Chromium, five runs each: 72 ms of main-thread time before and after. renderFloat's self time stayed at 4.8–5.0 ms.
  • Counted in jsdom: the mount calls renderFloat 32 times, 12 of them with decimals. That saves about 0.1 ms.
  • What the profile showed was not the per-call cost: 12 calls at 9.65 µs make 0.1 ms. The rest stayed with the formatter made once. That points to a cost paid once, by the first formatting, though I did not isolate it.

The gain is per value shown with decimals: about 9 µs each, every time the value renders. For example, a 200-row table with a Currency column would spend about 1.9 ms less each time that column renders. That is computed from the per-call numbers above, not measured in a document.

Tests

A new case in renders.edge-cases.test.js renders five values with four decimals. It checks that Intl.NumberFormat is made once, and what each shows. The check of the count fails on master, where nothing calls the constructor.

Gates

All 22 gates and the manifest contract exit 0 on the change, on Node 24.14.0, as committed on master before #220.

After merging master. #220 then merged, and master was merged into this branch. The only conflict was the changelog, where both add an entry at the top of the fixes; both are kept. The PR's diff is the change as gated.

On the merge, lint:js, lint:css, typecheck, test:coverage, build, test:react16 and test:react17 exit 0 again. That run has 2902 tests: all pass on React 18; on 16.14 and 17, 2898 pass and the same 4 are skipped.

🤖 Generated with Claude Code

… made once

`renderFloat`, behind the Currency, Float, Double5 and Percent renderers and Counter, formatted each fraction
with `toLocaleString(undefined, {minimumFractionDigits, maximumFractionDigits})`, which ECMA-402 specifies as a
new `Intl.NumberFormat` on each call. In Node 24's V8 that cost 9.65 µs a value, against 0.24 µs for a formatter
made once. It now keeps one formatter for each count of decimals. Its output is the same, byte for byte, in 189
cases: 7 counts of decimals, 9 values and 3 sets of options.

Mounting the ratingDetails demo example formats 12 values with decimals, and measured the same in Chromium
before and after. The 5 ms its profile showed in `renderFloat` stayed with the formatter made once, which points
to a cost the first formatting pays either way. The gain is per value shown: about 9 µs each.
# Conflicts:
#	src/demo/markdowns/changelog.md
@AlexSamBY
AlexSamBY merged commit 9a7995a into master Oct 13, 2026
8 checks passed
@AlexSamBY
AlexSamBY deleted the render-float branch October 13, 2026 12:17
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.

1 participant