From e0547ee7c53024d41d75217e0f61380174c8a4cc Mon Sep 17 00:00:00 2001 From: Disaster Date: Thu, 22 Oct 2026 21:36:41 +0300 Subject: [PATCH] feat(libc): implement _LCosh and _LSinh Add the long-double siblings of _Cosh and _Sinh with the same scaled finite-input path, signed-zero handling and Dinkumware two-argument contracts. The reductions use a long-double ln2 split, and the scaled-range tests pin 64-bit results instead of the intermediate writer's output. Cover known values, signs and non-finite inputs in guest_math; record the unverified console results and exception behavior in the libc debt list. --- core/libs/prx/libc/include/Api.hpp | 2 ++ core/libs/prx/libc/src/Math.cpp | 44 ++++++++++++++++++++++++++++++ core/libs/tests/GuestMath.cpp | 39 ++++++++++++++++++++++++++ docs/dev/debt/libc.md | 1 + 4 files changed, 86 insertions(+) diff --git a/core/libs/prx/libc/include/Api.hpp b/core/libs/prx/libc/include/Api.hpp index afdb17d63c..383128211d 100644 --- a/core/libs/prx/libc/include/Api.hpp +++ b/core/libs/prx/libc/include/Api.hpp @@ -662,6 +662,8 @@ void APS5_VABI srand_nid_postfix(unsigned int seed); int APS5_VABI __fpclassifyd_nid_postfix(double value); double APS5_VABI _Sinh_nid_postfix(double x, double y); double APS5_VABI _Cosh_nid_postfix(double x, double y); +long double APS5_VABI _LCosh_nid_postfix(long double x, long double y); +long double APS5_VABI _LSinh_nid_postfix(long double x, long double y); int APS5_VABI _Fltrounds_nid_postfix(void); int APS5_VABI fegetenv_nid_postfix(GuestFloatingPointEnvironment* environment); long double APS5_VABI copysignl_nid_postfix(long double x, long double y); diff --git a/core/libs/prx/libc/src/Math.cpp b/core/libs/prx/libc/src/Math.cpp index d4732e12f3..7be181570f 100644 --- a/core/libs/prx/libc/src/Math.cpp +++ b/core/libs/prx/libc/src/Math.cpp @@ -1303,6 +1303,50 @@ double APS5_VABI _Cosh_nid_postfix(double x, double y) { return std::scalbn(scale * (0.5 * std::exp(reduced)), scaleExponent + exponent); } +long double APS5_VABI _LCosh_nid_postfix(long double x, long double y) { + if (!std::isfinite(x) || !std::isfinite(y)) return y * std::cosh(x); + if (y == 0.0L) return std::copysign(0.0L, y); + constexpr long double Ln2High = 0x1.62e42fefa0000p-1L; + constexpr long double Ln2Low = 0x1.cf79abc9e3b398p-40L; + constexpr long double InverseLn2 = 0x1.71547652b82fe178p+0L; + const long double magnitude = std::abs(x); + if (magnitude <= (std::numeric_limits::max_exponent - 1) * (Ln2High + Ln2Low)) + return y * std::cosh(x); + int scaleExponent; + const long double scale = std::frexp(y, &scaleExponent); + constexpr int MaximumExponentSpan = std::numeric_limits::max_exponent - + std::numeric_limits::min_exponent + std::numeric_limits::digits + 4; + if (magnitude > MaximumExponentSpan * (Ln2High + Ln2Low)) + return std::scalbn(scale, INT_MAX); + const int exponent = static_cast(magnitude * InverseLn2); + const long double reduced = std::fma(-exponent, Ln2Low, std::fma(-exponent, Ln2High, magnitude)); + return std::scalbn(scale * (0.5L * std::exp(reduced)), scaleExponent + exponent); +} + +long double APS5_VABI _LSinh_nid_postfix(long double x, long double y) { + if (!std::isfinite(x) || !std::isfinite(y)) return y * std::sinh(x); + if (y == 0.0L) { + const bool negative = std::signbit(x) != std::signbit(y); + return std::copysign(0.0L, negative ? -1.0L : 1.0L); + } + constexpr long double Ln2High = 0x1.62e42fefa0000p-1L; + constexpr long double Ln2Low = 0x1.cf79abc9e3b398p-40L; + constexpr long double InverseLn2 = 0x1.71547652b82fe178p+0L; + const long double magnitude = std::abs(x); + if (magnitude <= (std::numeric_limits::max_exponent - 1) * (Ln2High + Ln2Low)) + return y * std::sinh(x); + int scaleExponent; + const long double scale = std::frexp(y, &scaleExponent); + constexpr int MaximumExponentSpan = std::numeric_limits::max_exponent - + std::numeric_limits::min_exponent + std::numeric_limits::digits + 4; + const long double sign = std::copysign(1.0L, x); + if (magnitude > MaximumExponentSpan * (Ln2High + Ln2Low)) + return sign * std::scalbn(scale, INT_MAX); + const int exponent = static_cast(magnitude * InverseLn2); + const long double reduced = std::fma(-exponent, Ln2Low, std::fma(-exponent, Ln2High, magnitude)); + return sign * std::scalbn(scale * (0.5L * std::exp(reduced)), scaleExponent + exponent); +} + int APS5_VABI _Fltrounds_nid_postfix(void) { return SonyFltrounds(); } diff --git a/core/libs/tests/GuestMath.cpp b/core/libs/tests/GuestMath.cpp index 6a11dd9caa..6d2c002a6f 100644 --- a/core/libs/tests/GuestMath.cpp +++ b/core/libs/tests/GuestMath.cpp @@ -1219,6 +1219,44 @@ static bool Near(long double actual, long double expected) { return std::fabs(actual - expected) <= std::fabs(expected) * 1e-17L; } +static void CheckLongDoubleHyperbolic() { + const long double infinity = std::numeric_limits::infinity(); + const long double nan = std::numeric_limits::quiet_NaN(); + Require(_LCosh_nid_postfix(0.0L, 2.0L) == 2.0L); + Require(Near(_LCosh_nid_postfix(1.0L, 2.0L), 3.08616126963048755696L)); + Require(Near(_LCosh_nid_postfix(-1.0L, -2.0L), -3.08616126963048755696L)); + Require(std::isinf(_LCosh_nid_postfix(infinity, -1.0L)) && std::signbit(_LCosh_nid_postfix(infinity, -1.0L))); + Require(std::isnan(_LCosh_nid_postfix(infinity, 0.0L))); + Require(std::isnan(_LCosh_nid_postfix(nan, 1.0L))); + Require(_LCosh_nid_postfix(720.0L, 0.0L) == 0.0L && !std::signbit(_LCosh_nid_postfix(720.0L, 0.0L))); + Require(_LCosh_nid_postfix(720.0L, -0.0L) == 0.0L && std::signbit(_LCosh_nid_postfix(720.0L, -0.0L))); + Require(Near(_LCosh_nid_postfix(11357.0L, 0x1p-16000L), 0xc.e2773666cc8cc68p+380L)); + Require(Near(_LCosh_nid_postfix(-11357.0L, 0x1p-16000L), 0xc.e2773666cc8cc68p+380L)); + Require(Near(_LCosh_nid_postfix(11360.0L, 0x1p-16384L), 0x8.165ba1f5c0ff231p+1L)); + Require(std::isinf(_LCosh_nid_postfix(11360.0L, 1.0L))); + Require(_LSinh_nid_postfix(0.0L, 2.0L) == 0.0L && !std::signbit(_LSinh_nid_postfix(0.0L, 2.0L))); + Require(_LSinh_nid_postfix(0.0L, -2.0L) == 0.0L && std::signbit(_LSinh_nid_postfix(0.0L, -2.0L))); + Require(Near(_LSinh_nid_postfix(1.0L, 2.0L), 2.35040238728760291376L)); + Require(Near(_LSinh_nid_postfix(-1.0L, -2.0L), 2.35040238728760291376L)); + Require(Near(_LSinh_nid_postfix(1.0L, -2.0L), -2.35040238728760291376L)); + Require(std::isinf(_LSinh_nid_postfix(infinity, -1.0L)) && std::signbit(_LSinh_nid_postfix(infinity, -1.0L))); + Require(std::isinf(_LSinh_nid_postfix(-infinity, -1.0L)) && !std::signbit(_LSinh_nid_postfix(-infinity, -1.0L))); + Require(std::isnan(_LSinh_nid_postfix(infinity, 0.0L))); + Require(std::isnan(_LSinh_nid_postfix(nan, 1.0L))); + Require(_LSinh_nid_postfix(720.0L, 0.0L) == 0.0L && !std::signbit(_LSinh_nid_postfix(720.0L, 0.0L))); + Require(_LSinh_nid_postfix(720.0L, -0.0L) == 0.0L && std::signbit(_LSinh_nid_postfix(720.0L, -0.0L))); + Require(_LSinh_nid_postfix(-720.0L, 0.0L) == 0.0L && std::signbit(_LSinh_nid_postfix(-720.0L, 0.0L))); + Require(_LSinh_nid_postfix(-720.0L, -0.0L) == 0.0L && !std::signbit(_LSinh_nid_postfix(-720.0L, -0.0L))); + Require(Near(_LSinh_nid_postfix(11357.0L, 0x1p-16000L), 0xc.e2773666cc8cc68p+380L)); + Require(Near(_LSinh_nid_postfix(-11357.0L, 0x1p-16000L), -0xc.e2773666cc8cc68p+380L)); + Require(Near(_LSinh_nid_postfix(11357.0L, -0x1p-16000L), -0xc.e2773666cc8cc68p+380L)); + Require(Near(_LSinh_nid_postfix(-11357.0L, -0x1p-16000L), 0xc.e2773666cc8cc68p+380L)); + Require(Near(_LSinh_nid_postfix(11360.0L, 0x1p-16384L), 0x8.165ba1f5c0ff231p+1L)); + Require(Near(_LSinh_nid_postfix(-11360.0L, 0x1p-16384L), -0x8.165ba1f5c0ff231p+1L)); + Require(std::isinf(_LSinh_nid_postfix(11360.0L, 1.0L))); + Require(std::isinf(_LSinh_nid_postfix(-11360.0L, 1.0L)) && std::signbit(_LSinh_nid_postfix(-11360.0L, 1.0L))); +} + static void CheckLongDoubleFunctions() { const long double infinity = std::numeric_limits::infinity(); const long double nan = std::numeric_limits::quiet_NaN(); @@ -1693,5 +1731,6 @@ int main() { CheckIntegerAbsAndDivision(); CheckScaling(); CheckLongDoubleFunctions(); + CheckLongDoubleHyperbolic(); CheckDinkumwareFloatCharacteristics(); } diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index bd27bf7a89..223c90ae7b 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -68,6 +68,7 @@ - [_Cosh](../../../core/libs/prx/libc/src/Math.cpp) (libc) follows the two-argument Dinkumware CRT helper used by `std::complex`, including a scaled large-finite-input path; the signature, exact result and floating-point exception behavior are not verified against the console. - [std::thread::hardware_concurrency](../../../core/libs/prx/libc/src/Threads.cpp) (libc, NID `CHrhwd8QSBs`) returns the host's `std::thread::hardware_concurrency()` (which may be 0 when unknown) instead of the console's processor count; no reference implements it and the value a title expects (PPSA34015 imports it) is unverified. - [fmodf](../../../core/libs/prx/libc/src/Math.cpp) (libc) - console `errno`, exception flags and NaN payloads remain unverified. The wrapper preserves `errno` like the existing double `fmod`, following FreeBSD 11.4 [`e_fmodf.c`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/e_fmodf.c); host `std::fmod` still supplies the result and exception flags. +- [_LCosh and _LSinh](../../../core/libs/prx/libc/src/Math.cpp) (libc) follow the two-argument Dinkumware CRT helpers `y * cosh(x)` and `y * sinh(x)`, including scaled large-finite-input paths; the signatures are confirmed by Dinkumware's public `ymath.h`, but the exact console results, range-error thresholds and floating-point exception behavior are not verified. - [_FSinh and _FCosh](../../../core/libs/prx/libc/src/Math.cpp) (libc) finite results are not verified bit for bit against the console: the scaled product uses the host's binary64 hyperbolic function before rounding to binary32, rather than the float rational and `_FExp` sequence in PRAGMATA's bundled `libc.prx`. NaN preference and payloads, signed zeros, infinite operands and the range-error thresholds follow that binary's instructions; the system `libSceLibcInternal` implementation and nondefault flush-to-zero modes are unverified. Against `libSceLibcInternal.sprx` on a PS5 (firmware 13.60), 12 of 36 measured results differ by one unit in the last place (`_FSinh(0.5, y)` and `_FCosh(1, y)`, `_FCosh(10, y)` for y of 0.5, 1 and 2); zeros, the overflow to infinity at 89 and 90 and the tiny-argument results matched - [sscanf_s and vsscanf_s](../../../core/libs/prx/libc/src/Formatting.cpp) (libc) return `EOF` when a reached conversion has a null destination, stopping before further assignments, following C11 K.3.5.3.7 and K.3.5.3.14. Earlier assignments are retained. The constraint handler is not invoked, and console behaviour is unverified.