Merge pull request #10234 from JJenkx/windows/libc-exp-msun

fix(libc): compute exp with FreeBSD msun's algorithm, as titles' libc.prx does
This commit is contained in:
Dean authored and GitHub committed 2026-11-03 21:07:07 +13:00
commit 4a7770bf54
3 files changed
+111 -3

No files matched your search

+80 -3
View File
@@ -58,7 +58,7 @@
*
* Developed at SunSoft, a Sun Microsystems, Inc. business.
* Permission to use, copy, modify, and distribute this
* software is freely granted, provided that this notice
* software is freely granted, provided that this notice
* is preserved.
* ====================================================
*/
@@ -755,7 +755,84 @@ long double APS5_VABI frexpl_nid_postfix(long double x, int* exponent) {
if (!std::isfinite(x)) return x;
return std::frexp(x, exponent);
}
double APS5_VABI exp_nid_postfix(double x) { return std::exp(x); }
/*
* ====================================================
* Copyright (C) 2004 by Sun Microsystems, Inc. All rights reserved.
*
* Permission to use, copy, modify, and distribute this
* software is freely granted, provided that this notice
* is preserved.
* ====================================================
*/
/* Adapted from FreeBSD release/11.4.0 lib/msun/src/e_exp.c: https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/e_exp.c */
double APS5_VABI exp_nid_postfix(double x) {
static constexpr double halF[2] = {0.5, -0.5};
static constexpr double ln2HI[2] = {6.93147180369123816490e-01, -6.93147180369123816490e-01};
static constexpr double ln2LO[2] = {1.90821492927058770002e-10, -1.90821492927058770002e-10};
static volatile double huge = 1.0e+300;
static volatile double twom1000 = 9.33263618503218878990e-302;
const double invln2 = 1.44269504088896338700e+00;
const double P1 = 1.66666666666666019037e-01;
const double P2 = -2.77777777770155933842e-03;
const double P3 = 6.61375632143793436117e-05;
const double P4 = -1.65339022054652515390e-06;
const double P5 = 4.13813679705723846039e-08;
double hi = 0.0;
double lo = 0.0;
std::int32_t k = 0;
const std::uint64_t bits = std::bit_cast<std::uint64_t>(x);
std::uint32_t hx = static_cast<std::uint32_t>(bits >> 32);
const std::int32_t xsb = (hx >> 31) & 1;
hx &= 0x7fffffff;
if (hx >= 0x40862e42) {
if (hx >= 0x7ff00000) {
if (((hx & 0xfffff) | static_cast<std::uint32_t>(bits)) != 0) {
return x + x;
}
return xsb == 0 ? x : 0.0;
}
if (x > 7.09782712893383973096e+02) {
errno = ERANGE;
return huge * huge;
}
if (x < -7.45133219101941108420e+02) {
errno = ERANGE;
return twom1000 * twom1000;
}
}
if (x == 1.0) {
return std::bit_cast<double>(std::uint64_t{0x4005bf0a8b145769});
}
if (hx > 0x3fd62e42) {
if (hx < 0x3ff0a2b2) {
hi = x - ln2HI[xsb];
lo = ln2LO[xsb];
k = 1 - xsb - xsb;
} else {
k = static_cast<std::int32_t>(invln2 * x + halF[xsb]);
const double t = k;
hi = x - t * ln2HI[0];
lo = t * ln2LO[0];
}
x = hi - lo;
} else if (hx < 0x3e300000) {
return 1.0 + x;
}
const double t = x * x;
const double twopk = std::bit_cast<double>(static_cast<std::uint64_t>(0x3ff + (k >= -1021 ? k : k + 1000)) << 52);
const double c = x - t * (P1 + t * (P2 + t * (P3 + t * (P4 + t * P5))));
if (k == 0) {
return 1.0 - ((x * c) / (c - 2.0) - x);
}
const double y = 1.0 - ((lo - (x * c) / (2.0 - c)) - hi);
if (k >= -1021) {
if (k == 1024) {
return y * 2.0 * 0x1p1023;
}
return y * twopk;
}
return y * twopk * twom1000;
}
double APS5_VABI atan_nid_postfix(double x) {
static constexpr double atanhi[] = {4.63647609000806093515e-01, 7.85398163397448278999e-01, 9.82793723247329054082e-01, 1.57079632679489655800e+00};
static constexpr double atanlo[] = {2.26987774529616870924e-17, 3.06161699786838301793e-17, 1.39033110312309984516e-17, 6.12323399573676603587e-17};
@@ -1415,7 +1492,7 @@ static double SinhLdexpExpHalf(double x) {
*
* Developed at SunSoft, a Sun Microsystems, Inc. business.
* Permission to use, copy, modify, and distribute this
* software is freely granted, provided that this notice
* software is freely granted, provided that this notice
* is preserved.
* ====================================================
*/
+30
View File
@@ -921,6 +921,35 @@ static long double MakeExtended(std::uint64_t mantissa, std::uint16_t signExpone
return value;
}
static void CheckExpConsoleBits() {
constexpr struct {
std::uint64_t input;
std::uint64_t expected;
int error;
} cases[] = {
{0x0000000000000000ull, 0x3ff0000000000000ull, 0}, {0x8000000000000000ull, 0x3ff0000000000000ull, 0},
{0x3e2fffffffffffffull, 0x3ff0000001000000ull, 0}, {0x3e30000000000000ull, 0x3ff0000001000000ull, 0},
{0x3fd62e42fefa39efull, 0x3ff6a09e667f3bccull, 0}, {0x3fd62e4300000000ull, 0x3ff6a09e66dbc8daull, 0},
{0x3ff0a2b1ffffffffull, 0x4006a09e0d126a08ull, 0}, {0x3ff0a2b200000000ull, 0x4006a09e0d126a0aull, 0},
{0x3ff0000000000000ull, 0x4005bf0a8b145769ull, 0}, {0xbff0000000000000ull, 0x3fd78b56362cef38ull, 0},
{0x4024000000000000ull, 0x40d5829dcf950560ull, 0}, {0xc024000000000000ull, 0x3f07cd79b5647c9aull, 0},
{0x40862e42fefa39efull, 0x7fefffffffffff2aull, 0}, {0x40862e42fefa39f0ull, 0x7ff0000000000000ull, ERANGE},
{0xc086232bdd7abcd2ull, 0x001000000000007cull, 0}, {0xc086232bdd7abcd3ull, 0x000ffffffffffe7cull, 0},
{0xc0874910d52d3051ull, 0x0000000000000001ull, 0}, {0xc0874910d52d3052ull, 0x0000000000000000ull, ERANGE},
{0x7fefffffffffffffull, 0x7ff0000000000000ull, ERANGE}, {0xffefffffffffffffull, 0x0000000000000000ull, ERANGE},
{0x7ff0000000000000ull, 0x7ff0000000000000ull, 0}, {0xfff0000000000000ull, 0x0000000000000000ull, 0},
{0x7ff8000000000001ull, 0x7ff8000000000001ull, 0}, {0x4033ddceb4331e0aull, 0x41b94d703f21e868ull, 0},
{0x401ce991ff636adcull, 0x4095866363930ec4ull, 0}, {0xc05c538238a89dfaull, 0x35b731b89f8aa7a0ull, 0},
{0xbfcc83b6d471607dull, 0x3fe99c0d1b777558ull, 0}, {0x3fb6d36f6d11ceb0ull, 0x3ff17dfdd6f97518ull, 0},
{0x4046b271a77d0918ull, 0x440677f14023ea1cull, 0},
};
for (const auto& item : cases) {
errno = 1234;
Require(std::bit_cast<std::uint64_t>(exp_nid_postfix(std::bit_cast<double>(item.input))) == item.expected);
Require(errno == (item.error != 0 ? item.error : 1234));
}
}
static void CheckLog10EdgeFlags() {
for (const double input : {-1.0, -std::numeric_limits<double>::infinity(), -std::numeric_limits<double>::quiet_NaN()}) {
errno = 0;
@@ -2899,6 +2928,7 @@ int main() {
CheckNextafterEnvironment(nextafterf_nid_postfix, 0x80000000u, 0x3f800000u, 0x00800000u, 0x7f7fffffu, 0x7f800000u);
CheckNextafterEnvironment(nextafter_nid_postfix, std::uint64_t{0x8000000000000000ull}, std::uint64_t{0x3ff0000000000000ull}, std::uint64_t{0x0010000000000000ull}, std::uint64_t{0x7fefffffffffffffull}, std::uint64_t{0x7ff0000000000000ull});
CheckExpm1ScalingRounding();
CheckExpConsoleBits();
CheckLog10ConsoleBits();
CheckLog10EdgeFlags();
CheckLog2ConsoleBits();
+1
View File
@@ -182,6 +182,7 @@
- [log1p](../../../core/libs/prx/libc/src/Math.cpp) (libc) does not compare floating-point exceptions, and its double inputs are checked by sampling, not exhaustively. It is FreeBSD msun's [`s_log1p.c`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/s_log1p.c). As the titles' `libc.prx` does, it sets `EDOM` at msun's NaN return (arguments below -1, `-inf` and negative NaNs) and leaves `errno` alone for `log1p(-1)`, where it returns `-inf`. It gives the same bits, NaN payloads included, and `errno` as the `log1p` bundled with PPSA07923 and PPSA05916 on 320 million random and 68,216 structured arguments on a Windows host. The FMA-contracted build bundled with PPSA13934 differs on 73,682 of the random ones.
- [nextafter and nextafterf](../../../core/libs/prx/libc/src/Math.cpp) (libc) have no physical-console or unmasked-exception validation. A Linux CPU probe compares result bits, `errno` and masked floating-point flags with the unchanged math code in FAR: Changing Tides PPSA03766 bundled `libc.prx` (`nextafter` 0x51b30, `nextafterf` 0x51c80, `_Feraise` 0x11da0): 16,864 calls across both precisions, four rounding modes and all DAZ/FTZ combinations, not exhaustive inputs. Earlier Windows sampling of eleven bundled libraries compares only bits and `errno`; exception behavior outside the FAR module remains unverified.
- [tanh](../../../core/libs/prx/libc/src/Math.cpp) (libc) does not compare floating-point exceptions, and its double inputs are checked by sampling, not exhaustively. It is FreeBSD msun's [`s_tanh.c`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/s_tanh.c) over this file's msun `Expm1`, with `tiny` read at run time; it sets no `errno`, as the titles' `libc.prx` does not. It gives the same bits as the `tanh` bundled with PPSA07923 and PPSA05916 on 16 million random and 68,211 structured arguments on a Windows host; the FMA-contracted build bundled with PPSA13934 differs on 1,054 of them.
- [exp](../../../core/libs/prx/libc/src/Math.cpp) (libc) does not compare floating-point exceptions, its double inputs are checked by sampling, not exhaustively, and tiny arguments return `1 + x`, which may not raise `FE_INEXACT` when the addition is exact. It is FreeBSD msun's [`e_exp.c`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/e_exp.c) with `huge` and `twom1000` read at run time, except that `exp(1)` returns the correctly rounded `e` (`0x4005bf0a8b145769`, where msun gives `0x4005bf0a8b14576a`), as the titles' copy does. It sets `ERANGE` only in msun's overflow and underflow branches, as the titles' `libc.prx` does. It gives the same bits and `errno` as the `exp` bundled with PPSA07923 and PPSA05916 on 320 million random arguments on a Windows host. The FMA-contracted build bundled with PPSA13934 differs on 44,911 of them.
- [log10](../../../core/libs/prx/libc/src/Math.cpp) (libc) does not compare floating-point exceptions, and its double inputs are checked by sampling, not exhaustively. It is the fdlibm [`e_log10.c`](https://github.com/freebsd/freebsd-src/blob/59bc2f095289a10b8cfa55cbb2b45c3bd6c2d4ae/lib/msun/src/e_log10.c) over its [`k_log.h`](https://github.com/freebsd/freebsd-src/blob/59bc2f095289a10b8cfa55cbb2b45c3bd6c2d4ae/lib/msun/src/k_log.h), which FreeBSD 11.4 later replaced. As the titles' `libc.prx` does, it sets `EDOM` for every nonzero argument with the sign bit set (negatives, `-inf` and negative NaNs) and leaves `errno` alone for ±0, where it returns `-inf`, unlike `log`. It gives the same bits, NaN payloads included, and `errno` as the `log10` bundled with PPSA07923 and PPSA05916 on 320 million random and 31,165 structured arguments on a Windows host. The FMA-contracted build bundled with PPSA13934 differs on 229,944 of the random ones.
- [log2](../../../core/libs/prx/libc/src/Math.cpp) (libc) does not compare floating-point exceptions, and its double inputs are checked by sampling, not exhaustively. It is the fdlibm [`e_log2.c`](https://github.com/freebsd/freebsd-src/blob/59bc2f095289a10b8cfa55cbb2b45c3bd6c2d4ae/lib/msun/src/e_log2.c) over the same `k_log.h` kernel as `log10` (`KernelLog`), which FreeBSD 11.4 later replaced. As the titles' `libc.prx` does, it sets `EDOM` for every nonzero argument with the sign bit set and leaves `errno` alone for ±0. It gives the same bits, NaN payloads included, and `errno` as the `log2` bundled with PPSA07923 and PPSA05916 on 16 million random and 31,165 structured arguments on a Windows host; the FMA-contracted build bundled with PPSA13934 differs on 727 of the random ones.
- [sinhf](../../../core/libs/prx/libc/src/Math.cpp) (libc) follows the `sinhf` of the `libc.prx` bundled with PPSA28319 (disassembly): FreeBSD msun's [`e_sinhf.c`](https://github.com/freebsd/freebsd-src/blob/5aa554c7e5b6fd9042986df07f12ac5687e41392/lib/msun/src/e_sinhf.c) from before `__ldexp_expf`, which squares `expf(|x| / 2)` between `logf(FLT_MAX)` and the overflow threshold, calling msun's [`expm1f`](https://github.com/freebsd/freebsd-src/blob/59bc2f095289a10b8cfa55cbb2b45c3bd6c2d4ae/lib/msun/src/s_expm1f.c) and [`expf`](https://github.com/freebsd/freebsd-src/blob/59bc2f095289a10b8cfa55cbb2b45c3bd6c2d4ae/lib/msun/src/e_expf.c), whose paths for the arguments it passes are inlined here. Past the threshold Sony's error helper sets `errno` to `ERANGE` before returning `x * 1e37`; msun itself sets no `errno`. That code, run on a Windows host, gives the same bits as this implementation for every finite argument up to the threshold, of both signs; it was not run on Linux. That the system `libSceLibcInternal`, which Ruffle (PPSA68091) and RetroArch (PPSA99169) import while bundling a zero-return `libc.prx`, has the same code is assumed