Skip to content

Fix ln() rounding up on a result just below a rounding tie - #261

Open
spokodev wants to merge 1 commit into
MikeMcl:masterfrom
spokodev:fix/ln-near-tie-correct-rounding
Open

Fix ln() rounding up on a result just below a rounding tie#261
spokodev wants to merge 1 commit into
MikeMcl:masterfrom
spokodev:fix/ln-near-tie-correct-rounding

Conversation

@spokodev

Copy link
Copy Markdown

Problem

ln() is documented as correctly rounded, but for an argument whose natural
log lands just below a rounding tie it rounds up instead of down:

Decimal.set({ precision: 20, rounding: Decimal.ROUND_HALF_UP });
new Decimal('4.25914212183600678912449997353532582823393815').ln().toString();
// → 1.4490677601509316344   (should be 1.4490677601509316343)

The true value, computed at high precision, is
1.44906776015093163434999999999999999999999999975859147… — the 21st
significant digit is 4 followed by a long run of 9s, i.e. strictly below
the …3435 halfway point, so every half-rounding mode must round down to
…343. mpmath and this library at precision: 60 agree.

Affects all five HALF rounding modes (the default). Incidence on random input is
tiny but the failures are deterministic and reachable.

Root cause

naturalLogarithm and naturalExponential extend precision in a loop guarded by
checkRoundingDigits(…, repeating). When the argument-reduction reconstruction
lands the working value on a false exact-tie form (…5000 / …0000), the
repeating branch of checkRoundingDigits only detects a trailing …9999
run — unlike the non-repeating branch, which also detects …5000/…0000 ties:

// non-repeating branch already has:  … || rd == 50000 || rd == 0
// and:                               … || (rd == k / 2 || rd == 0) && … == 0

So the near-tie was reported as resolved and finalise rounded the false tie up.

Fix

Add the same …5000/…0000 tie/zero detection to the repeating branch, mirroring
the non-repeating branch (only naturalLogarithm/naturalExponential reach it).
Applied identically to decimal.js and decimal.mjs.

Verification

  • The full test suite passes (22633/22633), including the added ln cases.
  • A near-tie sweep of ln and exp across all five HALF rounding modes
    (20000 cases) against an 80-digit oracle reports 0 mis-roundings; several
    of the same inputs were mis-rounded before the fix.

Test

Added five correctly-rounded ln cases to test/modules/ln.js (results ending
…d 4999…). They fail on master (round up) and pass with the fix.

For an argument whose natural log lands just below a rounding tie
(the digit after the rounding position is followed by a long run of 9s,
i.e. `...d 4999...`), ln() rounded up instead of down, violating the
documented "correctly rounded" guarantee.

naturalLogarithm/naturalExponential drive checkRoundingDigits with the
`repeating` flag. In that branch the argument-reduction reconstruction can
land the working value on a false exact-tie form (`...5000` / `...0000`),
but the repeating branch only detected a trailing `...9999` run — unlike
the non-repeating branch, which also detects `...5000`/`...0000` ties. So
the near-tie was treated as resolved and finalise() rounded the false tie
up. Mirror the tie/zero detection the non-repeating branch already has.

Verified: a near-tie sweep of ln and exp across the five half-rounding
modes (20000 cases) vs an 80-digit oracle now reports 0 mis-roundings
(several were wrong before), and the full test suite passes.
@MikeMcl

MikeMcl commented Aug 3, 2026

Copy link
Copy Markdown
Owner

Hi. I have seen this and will be on it shortly. Thank you.

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