Skip to content

fix: prevent _decimalPlaces infinite loop when multiplier overflows to Infinity#12266

Open
JSap0914 wants to merge 1 commit into
chartjs:masterfrom
JSap0914:fix/decimal-places-infinite-loop
Open

fix: prevent _decimalPlaces infinite loop when multiplier overflows to Infinity#12266
JSap0914 wants to merge 1 commit into
chartjs:masterfrom
JSap0914:fix/decimal-places-infinite-loop

Conversation

@JSap0914

Copy link
Copy Markdown

Summary

_decimalPlaces(x) in src/helpers/helpers.math.ts can enter an infinite loop for certain finite numbers — specifically, values smaller than approximately 1e-305.

Root Cause

The algorithm multiplies e by 10 each iteration, looking for a power of 10 where Math.round(x * e) / e === x. For very small numbers, the required power of 10 exceeds Number.MAX_VALUE (~1.8e308). When e *= 10 overflows to Infinity:

  • x * Infinity = Infinity
  • Math.round(Infinity) / Infinity = NaN
  • NaN !== x is always true → infinite loop

Fix

A single guard inside the while loop:

if (!isFinite(e)) {
  return;
}

This returns undefined when e overflows, consistent with the function's existing behavior for non-finite inputs (!isFiniteNumber(x) path).

Affected values

Any finite x where the loop needs e > Number.MAX_VALUE to converge — in practice, numbers smaller than approximately 1e-305. Examples: 1e-306, 1e-307, 1e-308, Number.MIN_VALUE.

Impact on callers

_decimalPlaces is called in scale.linearbase.js during tick generation. Returning undefined there falls through to Math.max(undefined, ...) = NaN in the decimalPlaces variable — the same degenerate outcome as passing a non-finite axis range, which is already an unsupported input. The infinite loop (current behavior) is strictly worse than returning undefined.

Testing

New assertions added to the existing it('should get the correct number of decimal places') block — no existing tests were modified.

This fix was developed with AI assistance.

When x is a finite number smaller than ~1e-305, the multiplier e grows
beyond Number.MAX_VALUE and overflows to Infinity. At that point:
  - Math.round(x * Infinity) / Infinity evaluates to NaN
  - NaN !== x is always true, causing an infinite loop

Add an isFinite(e) guard that exits the loop and returns undefined,
consistent with the function's existing behavior for non-finite inputs.

Fixes: _decimalPlaces(1e-305), _decimalPlaces(Number.MIN_VALUE), etc.
Related: chartjs#5992
Copilot AI review requested due to automatic review settings June 21, 2026 12:55

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

2 participants