Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 34 additions & 2 deletions core/libs/prx/libc/src/Math.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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" {
Expand Down Expand Up @@ -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}};
Expand Down
95 changes: 95 additions & 0 deletions core/libs/tests/GuestMath.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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[] = {
Expand Down Expand Up @@ -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();
Expand Down
2 changes: 2 additions & 0 deletions docs/dev/debt/libc.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading