[BUG]: `math/base/special/expm1` overflows prematurely
Maintainer thường phản hồi trong vòng 1 ngày
@kgryte đang làm issue này rồi.
Từ ngày 3/6/2026.
Đánh giá
Issue này chưa được đánh giá.
Mô tả
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.
- Ngôn ngữ chính
- JavaScript
- Star
- 6k
- Fork
- 1.3k
- Merge trung bình
- 1 ngày 10 giờ
- Pull request đã merge (30 ngày)
- 495
Chuẩn bị môi trường
Khởi chạy dev container của dự án ngay trên trình duyệt, bằng tài khoản GitHub của bạn.
- Không có Dockerfile hay tệp Docker Compose
- Có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của stdlib-js/stdlib
-
Fix JavaScript lint errorsCó thể đã có người làm @Devansh-18155 đã nhận hôm nay. Đang mởGood First Issue
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 82/100
stdlib-js/stdlib#15970 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Fix JavaScript lint errorsCó thể đã có người làm @lb1192176991-lab đã nhận 5 ngày trước. Đang mởGood First Issue
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 90/100
stdlib-js/stdlib#15831 · 2 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
`@stdlib/string/base/percent-encode` produces malformed encoding and silently drops charactersCó thể đã có người làm @barbierajput378-pixel đã nhận 10 ngày trước. Đang mởBug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 84/100
stdlib-js/stdlib#15595 · 6 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
[Bug]: kumaraswamy/kurtosis returns non-excess kurtosis (missing −3)Có thể đã có người làm @Planeshifter đã nhận 10 ngày trước. Đang mởBug Statistics
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
stdlib-js/stdlib#15461 · 1 bình luận · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
-
[Bug]: rayleigh/mgf returns wrong values due to misplaced parenthesisCó thể đã có người làm @anandkaranubc đã nhận 13 ngày trước. Đang mởBug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
stdlib-js/stdlib#15456 · 6 bình luận · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của stdlib-js/stdlib
Issue tương tự
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
answerLoops/answerLoops#344 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Engineering
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 66/100
techmatters/terraso-web-client#3095 ·
-
bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
-
Service process inherits the caller's cwd at first use, holding that folder open on Windows (EBUSY)Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
nextcloud/viewer#3424 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày