Skip to content

fix: ExpTaylor rounds correctly, computed with error bounds like Ln - #478

Open
ybai08 wants to merge 1 commit into
shopspring:masterfrom
ybai08:fix-exptaylor-rounding
Open

ybai08 wants to merge 1 commit into
shopspring:masterfrom
ybai08:fix-exptaylor-rounding

Conversation

@ybai08

@ybai08 ybai08 commented Oct 18, 2026

Copy link
Copy Markdown

Fixes ExpTaylor returning wrong last digits, like ExpTaylor(0.0003, 11) giving 1.00030004501 instead of 1.00030004500 (e^0.0003 = 1.000300045004500...). It is the same problem #471 fixed for Ln.

Each Taylor term was rounded to precision + 1 places before being added, so the result had only one guard digit and the rounding errors of all terms added up. Negative inputs were rounded twice, first e^|d| and then 1/e^|d|. That is why the existing test expected ExpTaylor(-3, 1) to be 0.1. e^-3 is 0.0498, so the right answer is 0, and this PR changes that expectation.

ExpTaylor now writes e^d = 10^q * e^s, with d = q*ln(10) + s and 0 <= s < ln(10). It computes this with the expFixed/ln10Fixed helpers that Pow and Ln already use, and raises the working precision until the whole error interval rounds the same way. The lower half of powFracFixed already did exactly this for Pow, so it moves into a shared expBounds helper. Pow's results are unchanged: 2,326 random PowWithPrecision cases give byte-identical output before and after.

  • Correct on all 6,952 random checks against mpmath at 60+ extra digits. They cover |d| up to 1490, inputs with up to 30 digits and precision from -30 to 60. Master got 666 of them wrong.
  • About 70x faster on the existing benchmark: BenchmarkDecimal_ExpTaylor goes from 111 µs to 1.5 µs. Large inputs gain the most. 274 random cases with 0 < d < 1490 took 47 s on master and 0.02 s here.
  • The factorial cache and its mutex aren't needed anymore and are removed. The concurrency test still checks that concurrent calls agree and is race-clean, but it no longer looks at the cache.
  • ExpTaylor could only return a nil error before. It now returns the same out-of-range error as Pow when the result or the precision is too large to represent, for example 1e10, where master had no result after 30 s. Results that round to 0 still return 0, for example -1e5 and -1e10, where master also had no result after 30 s. ExpTaylor(1e5, 2) now takes about 1 s.

Behaviour change: some results change in the last digit, like Ln in v1.6.0. All the other existing ExpTaylor test expectations were already correctly rounded and still pass. Only -3.0 at precision 1 changes, from 0.1 to 0.

New tests: last-digit cases checked against mpmath, two near-tie inputs where e^x is 2.5 - 2e-45 and 0.25 - 2e-46 (so the first working precisions can't decide the rounding), and out-of-range/underflow cases.

Checks run

  • go test . passes. go test -race -run 'ExpTaylor|Pow|Ln' . passes.
  • golangci-lint run ./... (v2.13.2 locally, CI pins v2.14.0) reports 0 issues. gofmt -l . is clean.
  • Red/green: with the new tests on master's math.go, TestDecimal_ExpTaylor fails 7 cases, and the out-of-range test does not finish before go test's 10-minute timeout. With the fix, everything passes.
  • Not run: the Go 1.10 to 1.26 CI matrix. Only Go 1.27.1 was available locally. The change uses no API newer than the code already uses (math.Ln10, big.Int.DivMod, the existing helpers).

ExpTaylor summed Taylor terms that were each rounded to one guard digit,
and for negative d rounded 1/e^|d| twice, so about 1 in 10 results had a
wrong last digit, like ExpTaylor(0.0003, 11) = 1.00030004501 instead of
1.00030004500 and ExpTaylor(-3, 1) = 0.1 instead of 0.

It now evaluates e^d = 10^q * e^s with the expFixed and ln10Fixed helpers
that Pow and Ln use, and raises the working precision until the whole
error interval rounds the same way. The lower half of powFracFixed moves
into expBounds so both share it. Results too large to represent return
an error instead of running out of memory.
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.

1 participant