[BUG]: `math/base/special/expm1` overflows prematurely
I maintainer di solito rispondono entro 1 giorno
@kgryte ci sta già lavorando.
Dal 3/6/2026.
Valutazione
Questa issue non è ancora stata valutata.
Descrizione
Description
Stdlib's expm1(709.782712893384) incorrectly returns Infinity,
whereas five other similar expressions all correctly return 1.7976931348622732e+308
(which is the correctly-rounded-to-nearest-representable answer for all 6 expressions,
as I'll show below, and is safely hundreds of ulps away from overflowing):
Math.exp(709.782712893384) = 1.7976931348622732e+308
exp(709.782712893384) = 1.7976931348622732e+308
Math.exp(709.782712893384)-1 = 1.7976931348622732e+308
exp(709.782712893384)-1 = 1.7976931348622732e+308
Math.expm1(709.782712893384) = 1.7976931348622732e+308
expm1(709.782712893384) = Infinity
This is especially surprising and noticeable since it causes expm1's overflow threshold to be smaller than that of the mathematically-always-larger function exp.
Why 1.7976931348622732e+308 is the correctly-rounded-to-nearest answer
First, let's find the exact mathematical value of the input javascript/C/C++/python literal 709.782712893384.
In python3:
>>> '%.10000g' % 709.782712893384
'709.7827128933839730962063185870647430419921875'
Calculating exp (or expm1, the distinction is negligible) of that in high precision in worlframalpha:
exp(709.7827128933839730962063185870647430419921875)
yields:
1.797693134862273217839649630900041649872882... × 10^308
Using that as a literal in python3 should produce the correctly-rounded answer:
>>> 1.797693134862273217839649630900041649872882e+308
1.7976931348622732e+308
And javascript agrees:
> 1.797693134862273217839649630900041649872882e+308
1.7976931348622732e+308
Conclusion: stdlib's exp(), exp()-1, Math.exp(), Math.exp()-1, Math.expm1() all give the correctly-rounded-to-nearest answer 1.7976931348622732e+308,
and stdlib's expm1() does not.
Is Infinity a correct answer?
Despite 1.7976931348622732e+308 being the correctly-rounded-to-nearest answer as shown above, it is still conceivable that Infinity could be a correct answer.
For that to be the case, it would have to be that the mathematically-exact answer is within 1.5 (or so... I think) ulps of overflowing.
But that is not the case: Number.MAX_VALUE is 1.7976931348623157e+308, which is many (213 or so) ulps larger, as the reproducer program below shows.
So, Infinity is not a correct answer.
Other considerations
I noticed that the expectation that expm1's first overflow is (incorrectly) at exactly literal 709.782712893384 (which is the same number as literal 7.09782712893383973096e+02) is currently hard-coded here in the source code of the expm1rel function at https://github.com/stdlib-js/math-base-special-expm1rel/blob/4477702f9201e83666d913937b06a3667dc430f3/lib/main.js :
var HUGE_VALUE = 7.09782712893383973096e+02; // 0x40862E42 0xFEFA39EF
...
if ( x < HUGE_VALUE ) {
return expm1( x ) / x;
}
... alternative overflow-resistant implementation ...
So if the implementation of expm1 gets fixed so that it no longer overflows prematurely for this value, then presumably this definition of HUGE_VALUE then needs to be updated to the next higher floating-point number (at which expm1 correctly overflows).
In practice, failing to update HUGE_VALUE wouldn't be a disaster; it would just mean expm1rel(709.782712893384) would still use the slightly-less-accurate-in-general overflow-resistant alternative formula, which happens to return exactly the same answer in this case. But it seems bad form to depend on this accidental fact.
It seems to me that the strategy of depending on this magic HUGE_VALUE constant is perhaps not the best strategy to begin with.
A possibly-more-robust strategy would be to get rid of this constant, and instead make the code simply evaluate expm1(x) and test whether it returns Infinity, instead.
That would free expm1rel's implementation from such tight dependence on accidental properties of the expm1 function and implementation.
Related Issues
Related issue #12436 fixed premature overflow of expm1rel, whose implementation is still perhaps overly-entangled with the overflow threshold of expm1, as noted above.
Questions
No.
Demo
No response
Reproduction
For bare minimal reproduction, one can simply print out the values of the 6 expressions shown at the beginning of this issue report.
The following script provides that and more context, confirming that other nearby values are behaving correctly, and confirming other claims made above.
import exp from '@stdlib/math-base-special-exp'; // pnpm install @stdlib/math-base-special-exp
import expm1 from '@stdlib/math-base-special-expm1'; // pnpm install @stdlib/math-base-special-expm1
import nextafter from 'nextafter'; // pnpm install nextafter
const pred = x => nextafter(x, -Infinity); // predecessor
const succ = x => nextafter(x, Infinity); // successor
const data = {
"-Infinity": -Infinity,
"zero": 0,
"One": 1,
"Last value for which both Math.expm1 and stdlib's expm1 correctly returns a finite number": 709.7827128933839,
"The one value for which Math.exmp1 returns a finite answer but stdlib's expm1 overflows": 709.782712893384,
"HUGE_VALUE 7.09782712893383973096e+02 in expm1rel source code (same as previous)": 7.09782712893383973096e+02,
"First value for which both Math.expm1 and stdlib's expm1 return Infinity": 709.7827128933841,
"Infinity": Infinity,
"NaN": NaN,
};
for (const key in data) {
const x = data[key];
console.log(" "+key+": "+x);
if (x !== -Infinity) console.log(" pred("+x+") = "+pred(x));
if (x !== Infinity) console.log(" succ("+x+") = "+succ(x));
console.log(" Math.exp("+x+") = "+Math.exp(x));
console.log(" exp("+x+") = "+exp(x));
console.log(" Math.exp("+x+")-1 = "+(Math.exp(x)-1));
console.log(" exp("+x+")-1 = "+(exp(x)-1));
console.log(" Math.expm1("+x+") = "+Math.expm1(x));
console.log(" expm1("+x+") = "+expm1(x));
if (!Object.is(expm1(x), Math.expm1(x))) { // use Object.is instead of === so NaN will be considered equal to NaN
console.warn("HEY! Math.expm1 and stdlib's expm1 gave different answers!");
}
console.log();
}
console.log(" HUGE_VAL = 7.09782712893383973096e+02 = "+7.09782712893383973096e+02); // confirm that it's 709.782712893384 as claimed
const number_of_ulps_to_infinity = x => {
let answer = 0;
while (x < Infinity) {
answer++;
x = succ(x);
}
return answer;
};
console.log(" number_of_ulps_to_infinity(1.7976931348622732e+308) = "+number_of_ulps_to_infinity(1.7976931348622732e+308)); // 214
Expected Results
...
Math.exp(709.782712893384) = 1.7976931348622732e+308
exp(709.782712893384) = 1.7976931348622732e+308
Math.exp(709.782712893384)-1 = 1.7976931348622732e+308
exp(709.782712893384)-1 = 1.7976931348622732e+308
Math.expm1(709.782712893384) = 1.7976931348622732e+308
expm1(709.782712893384) = 1.7976931348622732e+308
...
Actual Results
...
Math.exp(709.782712893384) = 1.7976931348622732e+308
exp(709.782712893384) = 1.7976931348622732e+308
Math.exp(709.782712893384)-1 = 1.7976931348622732e+308
exp(709.782712893384)-1 = 1.7976931348622732e+308
Math.expm1(709.782712893384) = 1.7976931348622732e+308
expm1(709.782712893384) = Infinity
HEY! Math.expm1 and stdlib's expm1 gave different answers!
...
Version
pnpm list says: @stdlib/[email protected]
Environments
N/A
Browser Version
No response
Node.js / npm Version
No response
Platform
No response
Checklist
- Read and understood the Code of Conduct.
- Searched for existing issues and pull requests.
- Lingua principale
- JavaScript
- Stelle
- 6k
- Fork
- 1.3k
- Merge medio
- 1g 3h
- PR unite (30g)
- 585
Preparare l'ambiente
Avvia il container di sviluppo del progetto nel browser, con il tuo account GitHub.
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di stdlib-js/stdlib
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 75/100
I maintainer di solito rispondono entro 1 giorno
-
Bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 75/100
stdlib-js/stdlib#15456 · 3 commenti · 1 assegnatario ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
stdlib-js/stdlib#15193 · 2 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
Fix JavaScript lint errorsApertaGood First Issue
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
stdlib-js/stdlib#14759 · 3 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
Fix C lint errorsAperta
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 85/100
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di stdlib-js/stdlib
Issue simili
-
Complexity: Small P-Feature: Projects page ready for merge team role: back end/devOps role: front end size: 0.25pt
Difficoltà 1/5 1-3 ore Idoneità per principianti 88/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 67/100
bellingcat/toolkit#905 ·
-
self-care self-care:docs-build-time-investigator
Difficoltà 2/5 Mezza giornata Idoneità per principianti 76/100
githubnext/gh-aw-cao#14191 ·
I maintainer di solito rispondono entro 1 giorno
-
effort:low impact:medium RAG status: auto-triaged
Difficoltà 2/5 1-3 ore Idoneità per principianti 84/100
mastra-ai/mastra#25229 · 2 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 88/100
sugarlabs/musicblocks#8984 ·
I maintainer di solito rispondono entro 1 giorno