Skip to content

Fix toFixed rounding 0 up to a power of ten at negative decimal places - #414

Merged
MikeMcl merged 1 commit into
MikeMcl:mainfrom
spokodev:fix-tofixed-zero-negative-dp
Aug 31, 2026
Merged

Fix toFixed rounding 0 up to a power of ten at negative decimal places#414
MikeMcl merged 1 commit into
MikeMcl:mainfrom
spokodev:fix-tofixed-zero-negative-dp

Conversation

@spokodev

Copy link
Copy Markdown
Contributor

round() computes the sticky/remainder flag as r = r || sd < 0 || .... The sd < 0 disjunct means "there are discarded digits to the right of the rounding position", which is correct for a non-zero value, but an exact zero has no digits at all, so the flag must stay false. Because it is forced true, ROUND_UP / ROUND_CEIL / ROUND_FLOOR round 0 away from zero and fabricate a power of ten.

sd < 0 is reached from the public API through negative decimal places (added in #262), so this surfaces in toFixed, decimalPlaces/dp and toFormat:

new BigNumber(0).toFixed(-2, 0)     // '100'    — should be '0'
new BigNumber(0).toFixed(-100, 0)   // 1e+100   — should be '0'
new BigNumber(0).toFixed(-2, 2)     // '100'  (ROUND_CEIL)
new BigNumber('-0').toFixed(-3, 3)  // '1000' (ROUND_FLOOR)

Rounding the exact value 0 to any number of places, in any mode, is 0 — every reference decimal (Java BigDecimal, Python decimal, .NET) agrees.

Fix

Gate the disjunct on the value being non-zero: sd < 0 && xc[0]. xc[0] is 0 only when the value is zero — the same invariant the branch just below (if (sd < 1 || !xc[0])) already relies on. && binds tighter than ||, so the rest of the expression is unchanged, and non-zero values round exactly as before.

Tests

Added zero-at-negative-dp cases to the existing ROUND_UP block and new ROUND_CEIL / ROUND_FLOOR blocks in test/methods/toFixed.js. They fail on the current code (Expected: 0, Actual: 100) and pass with the fix. Full suite: 65737 of 65737 passing. dist/ rebuilt via npm run build.

round() forced the sticky/remainder flag true whenever sd < 0 (there are
digits to the right of the rounding position), but an exact zero has no
such digits. With ROUND_UP, ROUND_CEIL or ROUND_FLOOR this made the value
0 round away from zero, so toFixed/decimalPlaces/toFormat fabricated a
power of ten:

  new BigNumber(0).toFixed(-2, 0)    // '100'  -> should be '0'
  new BigNumber(0).toFixed(-100, 0)  // 1e100  -> should be '0'

Gate the disjunct on the value being non-zero (xc[0] is 0 only for zero,
the same invariant the branch below already relies on). Non-zero values
are unaffected.
@MikeMcl

MikeMcl commented Aug 31, 2026

Copy link
Copy Markdown
Owner

Thank you. It was Claude who handled #262 in the first place, which was probably the first time I trusted it here. My mistake. It is appropiate that it is now correcting its own work - assuming it is Claude here.

@MikeMcl
MikeMcl merged commit 02109ca into MikeMcl:main Aug 31, 2026
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