mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-28 00:35:47 +00:00
fix(libc): avoid premature scaled hyperbolic overflow
This commit is contained in:
1 parent
8444508241
commit
ce81a77d1a
3 files changed
+131
-2
No files matched your search
@@ -210,6 +210,38 @@ float GuestBesselSecondKindZero() {
|
||||
return -std::numeric_limits<float>::infinity();
|
||||
}
|
||||
|
||||
float ScaledHyperbolic(float x, float y, bool sine) {
|
||||
const auto xBits = std::bit_cast<std::uint32_t>(x);
|
||||
const auto xMagnitude = xBits & 0x7fffffffu;
|
||||
const auto yMagnitude = std::bit_cast<std::uint32_t>(y) & 0x7fffffffu;
|
||||
constexpr std::uint32_t infinity = 0x7f800000u;
|
||||
if (xMagnitude > infinity) return x;
|
||||
if (yMagnitude > infinity) return y;
|
||||
if ((xMagnitude == infinity && yMagnitude == 0) || (sine && xMagnitude == 0 && yMagnitude == infinity)) {
|
||||
errno = EDOM;
|
||||
std::feraiseexcept(FE_INVALID);
|
||||
return std::numeric_limits<float>::quiet_NaN();
|
||||
}
|
||||
if (xMagnitude == infinity) return sine ? x * y : std::copysign(std::numeric_limits<float>::infinity(), y);
|
||||
if (xMagnitude == 0 || yMagnitude == 0 || yMagnitude == infinity) return sine ? x * y : y;
|
||||
constexpr double limit = 2.0 * std::numeric_limits<float>::max_exponent;
|
||||
const float magnitude = std::bit_cast<float>(xMagnitude);
|
||||
const double argument = magnitude < limit ? static_cast<double>(magnitude) : limit;
|
||||
const double value = static_cast<double>(y) * (sine ? std::sinh(argument) : std::cosh(argument));
|
||||
const bool exponential = magnitude >= (sine ? 7.7f : 8.675f);
|
||||
float result;
|
||||
if (exponential && std::fabs(value) >= 0x1p128) {
|
||||
result = std::copysign(std::numeric_limits<float>::infinity(), y);
|
||||
} else {
|
||||
result = static_cast<float>(value);
|
||||
}
|
||||
if (exponential && std::isinf(result)) {
|
||||
errno = ERANGE;
|
||||
std::feraiseexcept(FE_OVERFLOW | FE_INEXACT);
|
||||
}
|
||||
return sine && (xBits & 0x80000000u) != 0 ? -result : result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
@@ -581,8 +613,8 @@ int APS5_VABI ilogbf_nid_postfix(float x) { return ilogb_nid_postfix(static_cast
|
||||
double APS5_VABI remainder_nid_postfix(double x, double y) { return std::remainder(x, y); }
|
||||
double APS5_VABI remquo_nid_postfix(double x, double y, int* quo) { return std::remquo(x, y, quo); }
|
||||
float APS5_VABI remquof_nid_postfix(float x, float y, int* quo) { return std::remquo(x, y, quo); }
|
||||
float APS5_VABI _FSinh_nid_postfix(float x, float y) { return y * std::sinh(x); }
|
||||
float APS5_VABI _FCosh_nid_postfix(float x, float y) { return y * std::cosh(x); }
|
||||
float APS5_VABI _FSinh_nid_postfix(float x, float y) { return ScaledHyperbolic(x, y, true); }
|
||||
float APS5_VABI _FCosh_nid_postfix(float x, float y) { return ScaledHyperbolic(x, y, false); }
|
||||
|
||||
struct alignas(16) LibcFloatConstant { std::uint32_t bits[4]; };
|
||||
LibcFloatConstant _FInf_nid_postfix {{0x7f800000u, 0, 0, 0}};
|
||||
|
||||
@@ -116,6 +116,8 @@ double APS5_VABI sinh_nid_postfix(double);
|
||||
double APS5_VABI cosh_nid_postfix(double);
|
||||
float APS5_VABI coshf_nid_postfix(float);
|
||||
float APS5_VABI sinhf_nid_postfix(float);
|
||||
float APS5_VABI _FSinh_nid_postfix(float, float);
|
||||
float APS5_VABI _FCosh_nid_postfix(float, float);
|
||||
double APS5_VABI asinh_nid_postfix(double);
|
||||
float APS5_VABI asinhf_nid_postfix(float);
|
||||
double APS5_VABI acosh_nid_postfix(double);
|
||||
@@ -696,6 +698,98 @@ static void CheckHyperbolic() {
|
||||
Require(Near(acosh_nid_postfix(1e300), 691.4686750787736, 1e-15));
|
||||
}
|
||||
|
||||
static void CheckScaledHyperbolic() {
|
||||
const int rounding = std::fegetround();
|
||||
Require(std::fesetround(FE_TONEAREST) == 0);
|
||||
unsigned checks = 0;
|
||||
for (bool sine : {true, false}) {
|
||||
const auto function = sine ? _FSinh_nid_postfix : _FCosh_nid_postfix;
|
||||
const char* name = sine ? "_FSinh" : "_FCosh";
|
||||
const auto checkValue = [&](float x, float y) {
|
||||
const long double value = sine ? std::sinh(static_cast<long double>(x)) : std::cosh(static_cast<long double>(x));
|
||||
const float expected = static_cast<float>(value * static_cast<long double>(y));
|
||||
std::feclearexcept(FE_ALL_EXCEPT);
|
||||
errno = 12345;
|
||||
const float result = function(x, y);
|
||||
const auto actualBits = std::bit_cast<std::uint32_t>(result);
|
||||
const auto expectedBits = std::bit_cast<std::uint32_t>(expected);
|
||||
const auto distance = actualBits > expectedBits ? actualBits - expectedBits : expectedBits - actualBits;
|
||||
const bool exact = expected == 0.0f || std::isinf(expected);
|
||||
if ((exact ? actualBits != expectedBits : !std::isfinite(result) || distance > 2) ||
|
||||
(std::isfinite(expected) && (std::fetestexcept(FE_OVERFLOW | FE_INVALID) != 0 || errno != 12345))) {
|
||||
std::fprintf(stderr, "%s(%a, %a): got %08x, expected %08x, errno %d, flags %x\n",
|
||||
name, static_cast<double>(x), static_cast<double>(y), actualBits, expectedBits, errno, std::fetestexcept(FE_ALL_EXCEPT));
|
||||
std::abort();
|
||||
}
|
||||
++checks;
|
||||
};
|
||||
checkValue(90.0f, 1e-20f);
|
||||
checkValue(100.0f, 1e-40f);
|
||||
const float magnitudes[] = {0.0f, 0x1p-126f, 0.001f, 0.5f, 1.0f, 4.0f, 7.0f, 8.0f, 10.0f, 20.0f, 50.0f, 88.0f, 90.0f, 100.0f, 150.0f, 180.0f, 190.0f, 193.0f};
|
||||
const float scales[] = {0.0f, 0x1p-149f, 0x1p-140f, 0x1p-126f, 1e-20f, 0.5f, 1.0f, 1e20f, std::numeric_limits<float>::max()};
|
||||
for (float magnitude : magnitudes) {
|
||||
for (float scale : scales) {
|
||||
for (float xSign : {1.0f, -1.0f}) {
|
||||
for (float ySign : {1.0f, -1.0f}) checkValue(magnitude * xSign, scale * ySign);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (float scale : scales) {
|
||||
if (scale == 0.0f) continue;
|
||||
for (long double target : {static_cast<long double>(std::numeric_limits<float>::max()), 0x1p-150L}) {
|
||||
if (!sine && target < scale) continue;
|
||||
const long double ratio = target / static_cast<long double>(scale);
|
||||
const float boundary = static_cast<float>(sine ? std::asinh(ratio) : std::acosh(ratio));
|
||||
float magnitude = std::nextafter(std::nextafter(boundary, -INFINITY), -INFINITY);
|
||||
for (unsigned neighbor = 0; neighbor != 5; ++neighbor) {
|
||||
for (float xSign : {1.0f, -1.0f}) {
|
||||
for (float ySign : {1.0f, -1.0f}) checkValue(magnitude * xSign, scale * ySign);
|
||||
}
|
||||
magnitude = std::nextafter(magnitude, INFINITY);
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto checkSpecial = [&](float x, float y, std::uint32_t expectedBits, int error, int flags) {
|
||||
std::feclearexcept(FE_ALL_EXCEPT);
|
||||
std::feraiseexcept(FE_DIVBYZERO);
|
||||
errno = 12345;
|
||||
const float result = function(x, y);
|
||||
const int raised = std::fetestexcept(FE_ALL_EXCEPT);
|
||||
if (std::bit_cast<std::uint32_t>(result) != expectedBits || errno != error || raised != (FE_DIVBYZERO | flags)) {
|
||||
std::fprintf(stderr, "%s special (%a, %a): got %08x, expected %08x, errno %d, flags %x\n",
|
||||
name, static_cast<double>(x), static_cast<double>(y), std::bit_cast<std::uint32_t>(result), expectedBits, errno, raised);
|
||||
std::abort();
|
||||
}
|
||||
++checks;
|
||||
};
|
||||
const float infinity = std::numeric_limits<float>::infinity();
|
||||
for (float sign : {1.0f, -1.0f}) {
|
||||
checkSpecial(sign * infinity, 0.0f, 0x7fc00000u, EDOM, FE_INVALID);
|
||||
checkSpecial(sign * 100.0f, 0.0f, sine && sign < 0 ? 0x80000000u : 0u, 12345, 0);
|
||||
checkSpecial(sign * 100.0f, -0.0f, sine && sign < 0 ? 0u : 0x80000000u, 12345, 0);
|
||||
checkSpecial(sign * 1.0f, infinity, sine && sign < 0 ? 0xff800000u : 0x7f800000u, 12345, 0);
|
||||
}
|
||||
for (std::uint32_t bits : {0x7fc12345u, 0xffc54321u, 0x7fa12345u, 0xffa54321u}) {
|
||||
const float nan = std::bit_cast<float>(bits);
|
||||
checkSpecial(nan, 0.0f, bits, 12345, 0);
|
||||
checkSpecial(infinity, nan, bits, 12345, 0);
|
||||
checkSpecial(nan, std::bit_cast<float>(0x7fc99999u), bits, 12345, 0);
|
||||
}
|
||||
checkSpecial(0.0f, infinity, sine ? 0x7fc00000u : 0x7f800000u, sine ? EDOM : 12345, sine ? FE_INVALID : 0);
|
||||
checkSpecial(1.0f, std::numeric_limits<float>::max(), 0x7f800000u, 12345, FE_OVERFLOW | FE_INEXACT);
|
||||
checkSpecial(100.0f, 1.0f, 0x7f800000u, ERANGE, FE_OVERFLOW | FE_INEXACT);
|
||||
for (int mode : {FE_TONEAREST, FE_DOWNWARD, FE_UPWARD, FE_TOWARDZERO}) {
|
||||
Require(std::fesetround(mode) == 0);
|
||||
checkSpecial(std::numeric_limits<float>::max(), 0x1p-149f, 0x7f800000u, ERANGE, FE_OVERFLOW | FE_INEXACT);
|
||||
}
|
||||
Require(std::fesetround(FE_TONEAREST) == 0);
|
||||
if (sine) checkSpecial(0x1p-126f, 0x1p-149f, 0u, 12345, FE_UNDERFLOW | FE_INEXACT);
|
||||
}
|
||||
std::feclearexcept(FE_ALL_EXCEPT);
|
||||
Require(std::fesetround(rounding) == 0);
|
||||
std::printf("Scaled hyperbolic checks: %u\n", checks);
|
||||
}
|
||||
|
||||
static void CheckSinhf() {
|
||||
const float infinity = std::numeric_limits<float>::infinity();
|
||||
const struct { std::uint32_t input; std::uint32_t output; } cases[] = {
|
||||
@@ -1257,6 +1351,7 @@ int main() {
|
||||
Require(std::isinf(__powidf2_nid_postfix(-0.0, -3)) && std::signbit(__powidf2_nid_postfix(-0.0, -3)));
|
||||
Require(__powidf2_nid_postfix(2.0, -127) == 0x1p-127);
|
||||
CheckHyperbolic();
|
||||
CheckScaledHyperbolic();
|
||||
CheckSinhf();
|
||||
CheckAtanhf();
|
||||
CheckHypotSpecialValue();
|
||||
|
||||
@@ -52,6 +52,8 @@
|
||||
|
||||
### Functional
|
||||
|
||||
- [_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.
|
||||
|
||||
- [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.
|
||||
|
||||
- [Guest time conversions](../../../core/libs/prx/libc/src/Time.cpp) rewrite the host process `TZ` when a guest changes its own `TZ`, so other host consumers of that variable (including libSceRtc local time) shift too; code outside libc does not take the zone lock. Zone syntax follows the host C runtime, not FreeBSD: Windows only accepts the `std offset [dst]` form, treats an empty guest `TZ` as `UTC0` and reads `:Europe/Berlin` as an abbreviation (for example `rop`); only Linux honours zone file names. If the guest accepts a `TZ` value that the host rejects (on Windows, for example, one longer than the host environment limit), conversions throw `std::runtime_error`. A `tm_zone` saved before a `TZ` change can refer to storage now holding the new zone name, changing `%Z` output.
|
||||
|
||||
Reference in new issue
Block a user