Conversation
Normalize niceMin and niceMax to _decimalPlaces(spacing) when bounds === 'ticks'. Prevents IEEE-754 precision artifacts from inflating decimalPlaces and preserving floating point drift in generated tick values. Fixes chartjs#12281 Signed-off-by: aoright <102943475+aoright@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #12281
Motivation and Root Cause
When generating linear scale ticks with
bounds === 'ticks'on non-round bounds (e.g.,min: 49.894, max: 51.5264, spacing: 0.2),niceMin = Math.floor(rmin / spacing) * spacingevaluates to49.800000000000004due to IEEE-754 floating-point multiplication.Because
_decimalPlaces(niceMin)counts the decimal digits needed to write that value, it evaluates to 16 instead of 1.Consequently,
decimalPlaces = Math.max(_decimalPlaces(spacing), _decimalPlaces(niceMin))becomes 16, resulting infactor = 1e16.The subsequent tick rounding safeguard
Math.round((niceMin + j * spacing) * factor) / factorthen rounds to 16 decimal places, preserving floating-point drift in intermediate ticks (e.g., producing50.00000000000001instead of50).This causes custom
ticks.callbackfunctions checking integer values (e.g.,value => Number.isInteger(value) ? value : '') to silently fail to match integer ticks and discard axis labels.Changes Summary
src/scales/scale.linearbase.js: NormalizedniceMinandniceMaxto_decimalPlaces(spacing)usingspacingFactor = Math.pow(10, _decimalPlaces(spacing) || 0)immediately after computing floor and ceil multiples in thebounds === 'ticks'branch. SinceniceMinandniceMaxare integer multiples ofspacingby definition, their mathematical precision cannot exceed_decimalPlaces(spacing).test/specs/scale.linear.tests.js: Added regression unit tests for positive non-round bounds (min: 49.894, max: 51.5264, stepSize: 0.2) and negative non-round bounds (min: -51.5264, max: -49.894, stepSize: 0.2) to verify that generated tick values are clean and integer ticks matchNumber.isInteger().Verification Evidence
pnpm lint-typescompleted with 0 errors.pnpm lint-jscompleted with 0 errors../node_modules/.bin/karma start ./karma.conf.cjs --single-run --browsers chrome --grep scale.linearexecuted 75 of 75 tests with SUCCESS.