Skip to content

[number field] Preserve full numeric precision by default#5040

Draft
atomiks wants to merge 2 commits into
mui:masterfrom
atomiks:claude/heuristic-ramanujan-976212
Draft

[number field] Preserve full numeric precision by default#5040
atomiks wants to merge 2 commits into
mui:masterfrom
atomiks:claude/heuristic-ramanujan-976212

Conversation

@atomiks

@atomiks atomiks commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Restores v1.5.0's committed-value precision and fixes step/smallStep values finer than 0.001 being silent no-ops.

Root cause

removeFloatingPointErrors rounded the stored numeric value to 3 fraction digits whenever format had no Intl rounding options. The 3-digit default was accidental: the original implementation intended a full-precision fallback (format?.maximumFractionDigits ?? defaultOptions.maximumFractionDigits ?? 20), but new Intl.NumberFormat().resolvedOptions().maximumFractionDigits is 3, so the ?? 20 never applied.

Consequences:

  • step={0.0001} (or smallStep below 0.001) was a silent no-op: incrementing from 0 rounded 0.0001 back to 0, so onValueChange never fired and the value never advanced. Native <input type="number" step="0.0001"> works fine.
  • After [number field] Fix committed values and keyboard stepping #4905 made the blur commit report the stored value (correct for clamping), onValueCommitted reported the rounded value (1.235 when typing 1.23456) instead of full precision as in v1.5.0.

Changes

The default branch now cleans only binary floating-point noise (0.1 + 0.20.3) via toPrecision(15) instead of truncating to 3 digits. A Number.isInteger guard returns safe-range integers verbatim, since toPrecision(15) would corrupt 16-digit integers like Number.MAX_SAFE_INTEGER.

The Intl round-trip branch for explicit rounding options (#4804) is unchanged: roundingMode, significant digits, roundingIncrement, and percent display-scale rounding on blur are still respected (covered by the existing tests, all passing).

Behavior comparison (no format, typing 1.23456)

v1.5.0 before after
onValueCommitted on blur 1.23456 1.235 1.23456
onValueChange while typing 1.235 1.235 1.23456
Displayed text after blur 1.235 1.235 1.23456
Increment +1 from 1.23456 2.235 2.235 2.23456
step={0.0001} from 0 silent no-op silent no-op works

v1.5.0 was internally split: it committed full precision on blur while storing and reporting 3-digit rounded values. Since the step no-op and the stored-value rounding share the same root cause, the stored value now uniformly keeps full precision — matching what v1.5.0 already committed and how the native number input steps. Rounding the displayed value remains the job of format.

Tests

  • Updated the three tests that encoded the accidental 3-digit default.
  • Added regression tests: sub-0.001 stepping advances and fires onValueChange; typed high-precision values are reported in full by onValueChange/onValueCommitted; unit tests for noise cleanup, precision preservation, and safe-integer exactness in validate.test.ts.

The stored value was rounded to 3 fraction digits when no Intl rounding
options were provided, an accidental default inherited from
Intl.NumberFormat's resolved maximumFractionDigits. This truncated
committed values (a regression from v1.5.0, which committed full
precision on blur) and made step/smallStep values finer than 0.001
silent no-ops.

Clean only binary floating-point noise in the default branch instead,
preserving legitimate precision. The Intl round-trip branch for explicit
rounding options is unchanged.
@atomiks atomiks added component: number field Changes related to the number field component. type: bug It doesn't behave as expected. labels Jun 12, 2026
@pkg-pr-new

pkg-pr-new Bot commented Jun 12, 2026

Copy link
Copy Markdown

commit: b696e7d

@code-infra-dashboard

code-infra-dashboard Bot commented Jun 12, 2026

Copy link
Copy Markdown

Bundle size

Bundle Parsed size Gzip size
@base-ui/react 🔺+53B(+0.01%) 🔺+16B(+0.01%)

Details of bundle changes

Performance

Total duration: 847.39 ms ▼-606.85 ms(-41.7%) | Renders: 50 (+0) | Paint: 1,297.20 ms ▼-887.19 ms(-40.6%)

Test Duration Renders
Tabs mount (200 instances) 163.34 ms ▼-108.11 ms(-39.8%) 4 (+0)
Select mount (200 instances) 94.70 ms ▼-79.82 ms(-45.7%) 3 (+0)
Menu mount (300 instances) 86.59 ms ▼-69.40 ms(-44.5%) 2 (+0)
Slider mount (300 instances) 126.55 ms ▼-68.45 ms(-35.1%) 3 (+0)
Checkbox mount (500 instances) 46.49 ms ▼-67.60 ms(-59.2%) 1 (+0)

…and 7 more — details


Check out the code infra dashboard for more information about this PR.

@atomiks atomiks added type: regression A bug, but worse, it used to behave as expected. internal Behind-the-scenes enhancement. Formerly called “core”. and removed type: bug It doesn't behave as expected. labels Jun 12, 2026
@netlify

netlify Bot commented Jun 12, 2026

Copy link
Copy Markdown

Deploy Preview for base-ui ready!

Name Link
🔨 Latest commit b696e7d
🔍 Latest deploy log https://app.netlify.com/projects/base-ui/deploys/6a2bd85fd2edd10008063568
😎 Deploy Preview https://deploy-preview-5040--base-ui.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

Values typed with more than 15 significant digits were rounded by the
toPrecision(15) noise cleanup, so a no-format field could report and
commit a different value than the user entered.

Parsed input (typing, paste, blur) involves no arithmetic — percent and
permille scaling shift the decimal exponent as a string — so it carries
no binary noise and is now returned verbatim. Cleanup still applies to
stepping and snapping, whose arithmetic can introduce noise; a flat cap
preserving 16 digits could not clean 16-digit noise patterns such as
0.1 + 0.7 === 0.7999999999999999.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component: number field Changes related to the number field component. internal Behind-the-scenes enhancement. Formerly called “core”. type: regression A bug, but worse, it used to behave as expected.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant