From 878124772c13c76de4e180bc7e801a7c4abb0525 Mon Sep 17 00:00:00 2001 From: Disaster Date: Fri, 23 Oct 2026 00:58:52 +0300 Subject: [PATCH 1/2] feat(libc): implement _Exp and _FExp --- core/libs/prx/libc/include/Api.hpp | 2 + .../libs/prx/libc/src/DinkumwareInternals.cpp | 186 ++++++++++++++++++ core/libs/tests/GuestDinkumwareInternals.cpp | 157 +++++++++++++++ docs/dev/debt/libc.md | 1 + 4 files changed, 346 insertions(+) diff --git a/core/libs/prx/libc/include/Api.hpp b/core/libs/prx/libc/include/Api.hpp index afdb17d63c..ed63181454 100644 --- a/core/libs/prx/libc/include/Api.hpp +++ b/core/libs/prx/libc/include/Api.hpp @@ -601,6 +601,8 @@ float APS5_VABI __powisf2_nid_postfix(float base, int exponent); double APS5_VABI __powidf2_nid_postfix(double base, int exponent); float APS5_VABI expf_nid_postfix(float x); float APS5_VABI exp2f_nid_postfix(float x); +short APS5_VABI _Exp_nid_postfix(double* value, double y, short offset); +short APS5_VABI _FExp_nid_postfix(float* value, float y, short offset); float APS5_VABI logf_nid_postfix(float x); float APS5_VABI log2f_nid_postfix(float x); double APS5_VABI log10_nid_postfix(double x); diff --git a/core/libs/prx/libc/src/DinkumwareInternals.cpp b/core/libs/prx/libc/src/DinkumwareInternals.cpp index d1f8ac33f4..b98a5ed820 100644 --- a/core/libs/prx/libc/src/DinkumwareInternals.cpp +++ b/core/libs/prx/libc/src/DinkumwareInternals.cpp @@ -1,9 +1,13 @@ #include "prx/libc/include/Api.hpp" #include #include +#include +#include #include #include #include +#include +#include #include #include "prx/libc/include/General.hpp" @@ -73,6 +77,76 @@ TInteger ParsedInteger(const char* text, char** end, int base, int* error, TInte return value; } +constexpr short Denormal = -2, Finite = -1, Zero = 0, Infinite = 1, NotANumber = 2; + +short ClassifyDouble(double value) { + switch (std::fpclassify(value)) { + case FP_NAN: + return NotANumber; + case FP_INFINITE: + return Infinite; + case FP_ZERO: + return Zero; + case FP_SUBNORMAL: + return Denormal; + default: + return Finite; + } +} + +short ClassifyFloat(float value) { + switch (std::fpclassify(value)) { + case FP_NAN: + return NotANumber; + case FP_INFINITE: + return Infinite; + case FP_ZERO: + return Zero; + case FP_SUBNORMAL: + return Denormal; + default: + return Finite; + } +} + +void RaiseLibcError(int exceptions) { + if ((exceptions & (FE_OVERFLOW | FE_UNDERFLOW)) != 0) exceptions |= FE_INEXACT; + std::feraiseexcept(exceptions); + const bool range = (exceptions & FE_INVALID) == 0 && + (exceptions & (FE_DIVBYZERO | FE_OVERFLOW | FE_UNDERFLOW)) != 0; + errno = range ? ERANGE : EDOM; +} + +short ScaleDouble(double& value, long exponent) { + const short kind = ClassifyDouble(value); + if (kind == NotANumber) return kind; + if (kind == Infinite) { + RaiseLibcError(FE_OVERFLOW); + return Infinite; + } + if (kind == Zero) return Zero; + value = std::scalbn(value, static_cast(exponent)); + const short result = ClassifyDouble(value); + if (result == Infinite) RaiseLibcError(FE_OVERFLOW); + if (result == Zero || result == Denormal) RaiseLibcError(FE_UNDERFLOW); + return result; +} + +short ScaleFloat(float& value, long exponent) { + const short kind = ClassifyFloat(value); + if (kind == NotANumber) return kind; + if (kind == Infinite) { + RaiseLibcError(FE_OVERFLOW); + return Infinite; + } + if (kind == Zero) return Zero; + value = std::scalbn(value, static_cast(exponent)); + const short result = ClassifyFloat(value); + if (result == Infinite) RaiseLibcError(FE_OVERFLOW); + if (result == Zero || result == Denormal) RaiseLibcError(FE_UNDERFLOW); + return result; +} + } extern "C" { @@ -131,6 +205,118 @@ long long APS5_VABI _Stoll_nid_postfix(const char* text, char** end, int base) { return _Stollx_nid_postfix(text, end, base, nullptr); } +short APS5_VABI _Exp_nid_postfix(double* value, double y, short offset) { + if (value == nullptr) throw std::invalid_argument("_Exp: null value"); + const double x = *value; + constexpr double HugeExp = 1842.0; + constexpr double Eps = std::numeric_limits::epsilon(); + constexpr double InverseLn2 = 1.4426950408889634073599246810018921; + constexpr double Ln2High = 22713.0 / 32768.0; + constexpr double Ln2Low = 1.4286068203094172321214581765680755e-6; + constexpr double P1 = 420.30235984910635; + constexpr double P2 = 15132.70094680474802; + constexpr double Q0 = 30.01511290683317; + constexpr double Q1 = 3362.72154416553028; + constexpr double Q2 = 30265.40189360949691; + if (std::isnan(x)) { + *value = x; + return NotANumber; + } + if (std::isnan(y)) { + *value = y; + return NotANumber; + } + if (y == 0.0) { + *value = 0.0; + return Zero; + } + if (x < -HugeExp) { + *value = std::copysign(0.0, y); + RaiseLibcError(FE_UNDERFLOW); + return Zero; + } + if (x > HugeExp) { + *value = std::copysign(std::numeric_limits::infinity(), y); + RaiseLibcError(FE_OVERFLOW); + return Infinite; + } + if (std::isinf(y)) { + *value = y; + return Infinite; + } + double g = x * InverseLn2; + short exponent = static_cast(g + (g < 0.0 ? -0.5 : 0.5)); + g = static_cast(exponent); + g = (x - g * Ln2High) - g * Ln2Low; + double result; + if (-Eps < g && g < Eps) { + result = y; + } else { + const double z = g * g; + const double w = (Q0 * z + Q1) * z + Q2; + g *= (z + P1) * z + P2; + result = (w + g) / (w - g) * 2.0 * y; + --exponent; + } + *value = result; + return ScaleDouble(*value, static_cast(exponent) + offset); +} + +short APS5_VABI _FExp_nid_postfix(float* value, float y, short offset) { + if (value == nullptr) throw std::invalid_argument("_FExp: null value"); + const float x = *value; + constexpr float HugeExp = 229.0f; + constexpr float Eps = std::numeric_limits::epsilon(); + constexpr float InverseLn2 = 1.4426950408889634073599246810018921f; + constexpr float Ln2High = 22713.0f / 32768.0f; + constexpr float Ln2Low = 1.4286068203094172321214581765680755e-6f; + constexpr float P1 = 60.09114349f; + constexpr float Q0 = 12.01517514f; + constexpr float Q1 = 120.18228722f; + if (std::isnan(x)) { + *value = x; + return NotANumber; + } + if (std::isnan(y)) { + *value = y; + return NotANumber; + } + if (y == 0.0f) { + *value = 0.0f; + return Zero; + } + if (x < -HugeExp) { + *value = std::copysign(0.0f, y); + RaiseLibcError(FE_UNDERFLOW); + return Zero; + } + if (x > HugeExp) { + *value = std::copysign(std::numeric_limits::infinity(), y); + RaiseLibcError(FE_OVERFLOW); + return Infinite; + } + if (std::isinf(y)) { + *value = y; + return Infinite; + } + float g = x * InverseLn2; + short exponent = static_cast(g + (g < 0.0f ? -0.5f : 0.5f)); + g = static_cast(exponent); + g = (x - g * Ln2High) - g * Ln2Low; + float result; + if (-Eps < g && g < Eps) { + result = y; + } else { + const float z = g * g; + const float w = Q0 * z + Q1; + g *= z + P1; + result = (w + g) / (w - g) * 2.0f * y; + --exponent; + } + *value = result; + return ScaleFloat(*value, static_cast(exponent) + offset); +} + double APS5_VABI _Sin_nid_postfix(double x, unsigned int quadrant) { return quadrant != 0 ? cos_nid_postfix(x) : sin_nid_postfix(x); } diff --git a/core/libs/tests/GuestDinkumwareInternals.cpp b/core/libs/tests/GuestDinkumwareInternals.cpp index 6e1ffdf7e4..0aa1bdd59c 100644 --- a/core/libs/tests/GuestDinkumwareInternals.cpp +++ b/core/libs/tests/GuestDinkumwareInternals.cpp @@ -1,5 +1,7 @@ #include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" +#include +#include #include #include #include @@ -158,6 +160,160 @@ void TestIntegerErrors() { Require(_Stoullx_nid_postfix("18446744073709551616", &end, 10, &error) == 18446744073709551615ull && error == RangeError && *__error_nid_postfix() == RangeError, "_Stoullx overflow"); } +void TestDoubleHelpers() { + const double e = std::exp(1.0); + double result = 1.0; + Require(_Exp_nid_postfix(&result, 1.0, 0) == -1 && std::fabs(result - e) <= e * 1e-14, "_Exp e"); + result = 0.0; + Require(_Exp_nid_postfix(&result, 1.0, 0) == -1 && result == 1.0, "_Exp zero exponent"); + result = 1.0; + Require(_Exp_nid_postfix(&result, 3.0, 0) == -1 && std::fabs(result - 3.0 * e) <= 3.0 * e * 1e-14, "_Exp multiplier"); + result = 1.0; + Require(_Exp_nid_postfix(&result, -2.0, 0) == -1 && std::fabs(result + 2.0 * e) <= 2.0 * e * 1e-14, "_Exp negative multiplier"); + result = 0.0; + Require(_Exp_nid_postfix(&result, 5.0, 0) == -1 && result == 5.0, "_Exp multiplier at zero exponent"); + result = 1.0; + Require(_Exp_nid_postfix(&result, 1.0, 10) == -1 && std::fabs(result - e * 1024.0) <= e * 1024.0 * 1e-14, "_Exp scale"); + for (int i = -200; i <= 200; ++i) { + const double x = static_cast(i) / 16.0; + result = x; + Require(_Exp_nid_postfix(&result, 1.0, 0) == -1 && std::fabs(result - std::exp(x)) <= std::fabs(std::exp(x)) * 1e-14, "_Exp finite sweep"); + } + result = 1.0; + Require(_Exp_nid_postfix(&result, 0.0, 0) == 0 && result == 0.0, "_Exp zero multiplier"); + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + result = NAN; + Require(_Exp_nid_postfix(&result, 0.0, 0) == 2 && std::isnan(result) && errno == 0 && std::fetestexcept(FE_ALL_EXCEPT) == 0, "_Exp NaN zero multiplier"); + result = NAN; + Require(_Exp_nid_postfix(&result, 0.0, 17) == 2 && std::isnan(result), "_Exp scaled NaN zero multiplier"); + result = 1843.0; + Require(_Exp_nid_postfix(&result, 0.0, 0) == 0 && result == 0.0, "_Exp overflow exponent with zero multiplier"); + result = INFINITY; + Require(_Exp_nid_postfix(&result, 0.0, -17) == 0 && result == 0.0 && errno == 0 && std::fetestexcept(FE_ALL_EXCEPT) == 0, "_Exp infinite exponent with zero multiplier"); + result = -INFINITY; + Require(_Exp_nid_postfix(&result, 1.0, 0) == 0 && result == 0.0, "_Exp negative infinity"); + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + result = 1843.0; + Require(_Exp_nid_postfix(&result, -1.0, 0) == 1 && std::isinf(result) && std::signbit(result) && errno == ERANGE && (std::fetestexcept(FE_ALL_EXCEPT) & FE_OVERFLOW) != 0, "_Exp overflow preserves multiplier sign"); + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + result = -1843.0; + Require(_Exp_nid_postfix(&result, -1.0, 0) == 0 && result == 0.0 && std::signbit(result) && errno == ERANGE && (std::fetestexcept(FE_ALL_EXCEPT) & (FE_UNDERFLOW | FE_INEXACT)) == (FE_UNDERFLOW | FE_INEXACT), "_Exp underflow preserves multiplier sign"); + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + result = -1843.0; + Require(_Exp_nid_postfix(&result, 1.0, 0) == 0 && result == 0.0 && !std::signbit(result) && errno == ERANGE && (std::fetestexcept(FE_ALL_EXCEPT) & (FE_UNDERFLOW | FE_INEXACT)) == (FE_UNDERFLOW | FE_INEXACT), "_Exp negative cutoff reports underflow"); + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + result = 1.0; + Require(_Exp_nid_postfix(&result, std::numeric_limits::max(), 0) == 1 && std::isinf(result) && errno == ERANGE && (std::fetestexcept(FE_ALL_EXCEPT) & FE_OVERFLOW) != 0, "_Exp finite multiplier overflow"); + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + result = 1.0; + Require(_Exp_nid_postfix(&result, std::numeric_limits::denorm_min(), 0) == -2 && std::fpclassify(result) == FP_SUBNORMAL && errno == ERANGE && (std::fetestexcept(FE_ALL_EXCEPT) & (FE_UNDERFLOW | FE_INEXACT)) == (FE_UNDERFLOW | FE_INEXACT), "_Exp subnormal result classification"); + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + result = INFINITY; + Require(_Exp_nid_postfix(&result, 1.0, 0) == 1 && std::isinf(result) && errno == ERANGE && (std::fetestexcept(FE_ALL_EXCEPT) & FE_OVERFLOW) != 0, "_Exp positive infinity"); + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + result = NAN; + Require(_Exp_nid_postfix(&result, 1.0, 0) == 2 && std::isnan(result) && errno == 0 && std::fetestexcept(FE_ALL_EXCEPT) == 0, "_Exp NaN exponent"); + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + result = 1.0; + Require(_Exp_nid_postfix(&result, NAN, 0) == 2 && std::isnan(result) && errno == 0 && std::fetestexcept(FE_ALL_EXCEPT) == 0, "_Exp NaN multiplier"); + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + result = 1.0; + Require(_Exp_nid_postfix(&result, INFINITY, 0) == 1 && std::isinf(result) && errno == 0 && std::fetestexcept(FE_ALL_EXCEPT) == 0, "_Exp infinite multiplier"); + result = 1000.0; + Require(_Exp_nid_postfix(&result, 1.0, 0) == 1 && std::isinf(result), "_Exp overflow"); + result = -1000.0; + Require(_Exp_nid_postfix(&result, 1.0, 0) == 0 && result == 0.0, "_Exp underflow"); + result = 1.0; + Require(_Exp_nid_postfix(&result, 1.0, -2000) == 0 && result == 0.0, "_Exp scaled underflow"); + result = 1.0; + Require(_Exp_nid_postfix(&result, 1.0, 2000) == 1 && std::isinf(result), "_Exp scaled overflow"); + + const float ef = std::exp(1.0f); + float resultf = 1.0f; + Require(_FExp_nid_postfix(&resultf, 1.0f, 0) == -1 && std::fabs(resultf - ef) <= ef * 1e-6f, "_FExp e"); + resultf = 0.0f; + Require(_FExp_nid_postfix(&resultf, 1.0f, 0) == -1 && resultf == 1.0f, "_FExp zero exponent"); + resultf = 1.0f; + Require(_FExp_nid_postfix(&resultf, 3.0f, 0) == -1 && std::fabs(resultf - 3.0f * ef) <= 3.0f * ef * 1e-6f, "_FExp multiplier"); + resultf = 1.0f; + Require(_FExp_nid_postfix(&resultf, -2.0f, 0) == -1 && std::fabs(resultf + 2.0f * ef) <= 2.0f * ef * 1e-6f, "_FExp negative multiplier"); + resultf = 1.0f; + Require(_FExp_nid_postfix(&resultf, 1.0f, 10) == -1 && std::fabs(resultf - ef * 1024.0f) <= ef * 1024.0f * 1e-6f, "_FExp scale"); + for (int i = -120; i <= 120; ++i) { + const float x = static_cast(i) / 16.0f; + resultf = x; + Require(_FExp_nid_postfix(&resultf, 1.0f, 0) == -1 && std::fabs(resultf - std::exp(x)) <= std::fabs(std::exp(x)) * 1e-6f, "_FExp finite sweep"); + } + resultf = 1.0f; + Require(_FExp_nid_postfix(&resultf, 0.0f, 0) == 0 && resultf == 0.0f, "_FExp zero multiplier"); + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + resultf = NAN; + Require(_FExp_nid_postfix(&resultf, 0.0f, 0) == 2 && std::isnan(resultf) && errno == 0 && std::fetestexcept(FE_ALL_EXCEPT) == 0, "_FExp NaN zero multiplier"); + resultf = NAN; + Require(_FExp_nid_postfix(&resultf, 0.0f, 17) == 2 && std::isnan(resultf), "_FExp scaled NaN zero multiplier"); + resultf = 230.0f; + Require(_FExp_nid_postfix(&resultf, 0.0f, 0) == 0 && resultf == 0.0f, "_FExp overflow exponent with zero multiplier"); + resultf = INFINITY; + Require(_FExp_nid_postfix(&resultf, 0.0f, -17) == 0 && resultf == 0.0f && errno == 0 && std::fetestexcept(FE_ALL_EXCEPT) == 0, "_FExp infinite exponent with zero multiplier"); + resultf = -INFINITY; + Require(_FExp_nid_postfix(&resultf, 1.0f, 0) == 0 && resultf == 0.0f, "_FExp negative infinity"); + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + resultf = 230.0f; + Require(_FExp_nid_postfix(&resultf, -1.0f, 0) == 1 && std::isinf(resultf) && std::signbit(resultf) && errno == ERANGE && (std::fetestexcept(FE_ALL_EXCEPT) & FE_OVERFLOW) != 0, "_FExp overflow preserves multiplier sign"); + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + resultf = -230.0f; + Require(_FExp_nid_postfix(&resultf, -1.0f, 0) == 0 && resultf == 0.0f && std::signbit(resultf) && errno == ERANGE && (std::fetestexcept(FE_ALL_EXCEPT) & (FE_UNDERFLOW | FE_INEXACT)) == (FE_UNDERFLOW | FE_INEXACT), "_FExp underflow preserves multiplier sign"); + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + resultf = -230.0f; + Require(_FExp_nid_postfix(&resultf, 1.0f, 0) == 0 && resultf == 0.0f && !std::signbit(resultf) && errno == ERANGE && (std::fetestexcept(FE_ALL_EXCEPT) & (FE_UNDERFLOW | FE_INEXACT)) == (FE_UNDERFLOW | FE_INEXACT), "_FExp negative cutoff reports underflow"); + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + resultf = 1.0f; + Require(_FExp_nid_postfix(&resultf, std::numeric_limits::max(), 0) == 1 && std::isinf(resultf) && errno == ERANGE && (std::fetestexcept(FE_ALL_EXCEPT) & FE_OVERFLOW) != 0, "_FExp finite multiplier overflow"); + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + resultf = 1.0f; + Require(_FExp_nid_postfix(&resultf, std::numeric_limits::denorm_min(), 0) == -2 && std::fpclassify(resultf) == FP_SUBNORMAL && errno == ERANGE && (std::fetestexcept(FE_ALL_EXCEPT) & (FE_UNDERFLOW | FE_INEXACT)) == (FE_UNDERFLOW | FE_INEXACT), "_FExp subnormal result classification"); + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + resultf = INFINITY; + Require(_FExp_nid_postfix(&resultf, 1.0f, 0) == 1 && std::isinf(resultf) && errno == ERANGE && (std::fetestexcept(FE_ALL_EXCEPT) & FE_OVERFLOW) != 0, "_FExp positive infinity"); + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + resultf = NAN; + Require(_FExp_nid_postfix(&resultf, 1.0f, 0) == 2 && std::isnan(resultf) && errno == 0 && std::fetestexcept(FE_ALL_EXCEPT) == 0, "_FExp NaN exponent"); + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + resultf = 1.0f; + Require(_FExp_nid_postfix(&resultf, NAN, 0) == 2 && std::isnan(resultf) && errno == 0 && std::fetestexcept(FE_ALL_EXCEPT) == 0, "_FExp NaN multiplier"); + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + resultf = 1.0f; + Require(_FExp_nid_postfix(&resultf, INFINITY, 0) == 1 && std::isinf(resultf) && errno == 0 && std::fetestexcept(FE_ALL_EXCEPT) == 0, "_FExp infinite multiplier"); + resultf = 100.0f; + Require(_FExp_nid_postfix(&resultf, 1.0f, 0) == 1 && std::isinf(resultf), "_FExp overflow"); + resultf = -120.0f; + Require(_FExp_nid_postfix(&resultf, 1.0f, 0) == 0 && resultf == 0.0f, "_FExp underflow"); + resultf = 1.0f; + Require(_FExp_nid_postfix(&resultf, 1.0f, -200) == 0 && resultf == 0.0f, "_FExp scaled underflow"); + resultf = 1.0f; + Require(_FExp_nid_postfix(&resultf, 1.0f, 200) == 1 && std::isinf(resultf), "_FExp scaled overflow"); +} + void TestDispatch() { Require(_Sin_nid_postfix(0.5, 0) == std::sin(0.5) && _Sin_nid_postfix(0.5, 1) == std::cos(0.5) && _Sin_nid_postfix(0.5, 7) == std::cos(0.5), "_Sin"); Require(_FSin_nid_postfix(0.5f, 0) == std::sin(0.5f) && _FSin_nid_postfix(0.5f, 2) == std::cos(0.5f), "_FSin"); @@ -171,6 +327,7 @@ void TestDispatch() { int main() { TestStod(); TestStoll(); + TestDoubleHelpers(); TestFloatErrors(); TestIntegerErrors(); TestDispatch(); diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index 2d4e0fc01d..757d256856 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -157,6 +157,7 @@ - [std::_Throw_Cpp_error](../../../core/libs/prx/libc/src/exception/StandardExceptions.cpp) (libc) follows Dinkumware `thread0.cpp` ([VC12](https://github.com/ganboing/crt.vc12/blob/f1d47126/src/thread0.cpp), [STL](https://github.com/microsoft/STL/blob/da52dc0f/stl/src/thread0.cpp)), without console verification. Codes outside 0..6 throw `std::out_of_range`, the deleting destructor throws, and STREAMS errnos absent from FreeBSD 11.4 [`errno.h`](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/sys/errno.h) (`ENODATA`, `ENOSR`, `ENOSTR`, `ETIME`) are omitted. Codes 0..6 throw a 32-byte [VC12 `system_error`](https://github.com/ganboing/crt.vc12/blob/f1d47126/src/system_error) with FreeBSD errnos (16, 22, 3, 12, 1, 11, 35), a static `generic_category` (not `_Generic_object`), and unverified 40-byte [GuestLocale::String](../../../core/libs/prx/libc/include/GuestLocale.hpp) messages from [`syserror.cpp`](https://github.com/ganboing/crt.vc12/blob/f1d47126/src/syserror.cpp) - [Call tracing](../../../core/libs/prx/libc/src/specifics/x86_64/CallTrace.cpp) (`--trace-imports`, `APS5_TRACE_CALLS`) is missing on macOS and records only the entry of a call, not its arguments or result. One process-wide ring keeps the last 65536 calls, so a busy thread pushes the others out; the reporting thread's own calls are listed only while they are still in that ring. Calls are counted per traced target; an import bound to a target already traced under another name is counted under the first name. The thunk saves the integer argument registers and `xmm0` to `xmm7`; wider vector arguments survive only while the recording code avoids AVX, which is untested. Linux has no crash report, so it prints the trace only on `SIGABRT` (guest or host `abort()`), not on `SIGSEGV`; a caller in a module loaded after the last traced import prints without a module name, and a thread aborting while another writes the report waits for it at most 2 seconds - [_Dtest](../../../core/libs/prx/libc/src/Math.cpp) (libc) follows Dinkumware double classification matching _FDtest: finite values return -1, subnormals -2, zeros 0, infinities 1, and NaNs 2. +- [_Exp and _FExp](../../../core/libs/prx/libc/src/DinkumwareInternals.cpp) (libc) compute `y * exp(x) * 2^offset` following the public Dinkumware CRT contracts. The implementation classifies NaN operands first so they propagate, then applies the zero-multiplier shortcut before the exponent range checks: `y == 0` returns zero even for an infinite exponent. The multiplier sign is preserved on both range cutoffs; the negative cutoff raises underflow and returns a signed zero, and the overflow cutoff raises overflow. Scaling preserves range errors for an intermediate overflow and returns the denormal classification for a subnormal final result. The exact console results, the precedence for NaN and infinite special values, range-error thresholds, directed-rounding behavior and floating-point exception behavior are not verified. - [app0.zar](../../../core/libs/prx/libc/src/AppArchive.cpp) (libc) - a [ZArchive](https://github.com/Exzap/ZArchive) beside `app0/` is a read-only layer under it: a path missing from `app0/` is looked up in the archive, case-insensitively. It is not a console format; titles ship their files in a PFS image instead. Resolving an archive-only path through any call other than the archive-aware libkernel ones throws, so `fopen`, `stat`, `access`, `opendir` and the rest do not read the archive yet, and `opendir` of a directory that exists in both places, `app0/` itself included, and `fdopen` of an archived descriptor throw instead of missing its entries or reading the null device. A damaged compressed block throws instead of returning `EIO` - [fopen_s](../../../core/libs/prx/libc/src/FileAndHeap.cpp) (libc) - returns the errno of the failed open, as C11 K.3.5.2.1 leaves the nonzero value open and Microsoft's CRT returns errno; the console's Dinkumware `fopen_s` is not verified - [codecvt vtable](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc, NID `H-TDszhsYuY`) is thirteen zeroed `uintptr_t` slots matching `st_size` 104 on title `libc.prx` from PPSA26344 and on `libSceLibcInternal.sprx` (offset-to-top, typeinfo, two destructors, `_Incref`/`_Decref`, and seven conversion virtuals). Facet calls through the object fault until those methods are wired. From 35c38ce8d59c1d62401f6cf6408d0229ba2e57b1 Mon Sep 17 00:00:00 2001 From: Disaster Date: Sun, 25 Oct 2026 22:34:46 +0300 Subject: [PATCH 2/2] fix(libc): match measured _Exp and _FExp special values --- .../libs/prx/libc/src/DinkumwareInternals.cpp | 273 ++++++++++------ core/libs/tests/GuestDinkumwareInternals.cpp | 301 ++++++++++-------- docs/dev/debt/libc.md | 2 +- 3 files changed, 348 insertions(+), 228 deletions(-) diff --git a/core/libs/prx/libc/src/DinkumwareInternals.cpp b/core/libs/prx/libc/src/DinkumwareInternals.cpp index c701c41046..dc45984759 100644 --- a/core/libs/prx/libc/src/DinkumwareInternals.cpp +++ b/core/libs/prx/libc/src/DinkumwareInternals.cpp @@ -79,36 +79,6 @@ TInteger ParsedInteger(const char* text, char** end, int base, int* error, TInte constexpr short Denormal = -2, Finite = -1, Zero = 0, Infinite = 1, NotANumber = 2; -short ClassifyDouble(double value) { - switch (std::fpclassify(value)) { - case FP_NAN: - return NotANumber; - case FP_INFINITE: - return Infinite; - case FP_ZERO: - return Zero; - case FP_SUBNORMAL: - return Denormal; - default: - return Finite; - } -} - -short ClassifyFloat(float value) { - switch (std::fpclassify(value)) { - case FP_NAN: - return NotANumber; - case FP_INFINITE: - return Infinite; - case FP_ZERO: - return Zero; - case FP_SUBNORMAL: - return Denormal; - default: - return Finite; - } -} - void RaiseLibcError(int exceptions) { if ((exceptions & (FE_OVERFLOW | FE_UNDERFLOW)) != 0) exceptions |= FE_INEXACT; std::feraiseexcept(exceptions); @@ -117,34 +87,55 @@ void RaiseLibcError(int exceptions) { errno = range ? ERANGE : EDOM; } -short ScaleDouble(double& value, long exponent) { - const short kind = ClassifyDouble(value); - if (kind == NotANumber) return kind; - if (kind == Infinite) { - RaiseLibcError(FE_OVERFLOW); +template +short ClassifyValue(TFloat value) { + switch (std::fpclassify(value)) { + case FP_NAN: + return NotANumber; + case FP_INFINITE: return Infinite; + case FP_ZERO: + return Zero; + case FP_SUBNORMAL: + return Denormal; + default: + return Finite; } - if (kind == Zero) return Zero; - value = std::scalbn(value, static_cast(exponent)); - const short result = ClassifyDouble(value); - if (result == Infinite) RaiseLibcError(FE_OVERFLOW); - if (result == Zero || result == Denormal) RaiseLibcError(FE_UNDERFLOW); - return result; } -short ScaleFloat(float& value, long exponent) { - const short kind = ClassifyFloat(value); - if (kind == NotANumber) return kind; +template +short ScaleProduct(TFloat& value, TFloat factor, TFloat multiplier, long exponent) { + std::fexcept_t saved; + std::fegetexceptflag(&saved, FE_ALL_EXCEPT); + const int savedErrno = errno; + TFloat result = factor * multiplier; + if (!std::isfinite(result) || result == static_cast(0)) { + int factorExponent = 0; + int multiplierExponent = 0; + const TFloat factorMantissa = std::frexp(factor, &factorExponent); + const TFloat multiplierMantissa = std::frexp(multiplier, &multiplierExponent); + const long long combinedExponent = static_cast(factorExponent) + multiplierExponent + exponent; + result = std::scalbn(factorMantissa * multiplierMantissa, static_cast(combinedExponent)); + } else { + result = std::scalbn(result, static_cast(exponent)); + } + value = result; + const short kind = ClassifyValue(result); + const int raised = std::fetestexcept(FE_ALL_EXCEPT) & ~static_cast(saved) & FE_ALL_EXCEPT; + std::fesetexceptflag(&saved, FE_ALL_EXCEPT); + errno = savedErrno; if (kind == Infinite) { RaiseLibcError(FE_OVERFLOW); return Infinite; } - if (kind == Zero) return Zero; - value = std::scalbn(value, static_cast(exponent)); - const short result = ClassifyFloat(value); - if (result == Infinite) RaiseLibcError(FE_OVERFLOW); - if (result == Zero || result == Denormal) RaiseLibcError(FE_UNDERFLOW); - return result; + if (kind == Zero) { + RaiseLibcError(FE_UNDERFLOW); + return Zero; + } + if (kind == Denormal) return Finite; + if (kind == NotANumber) return NotANumber; + if ((raised & FE_INEXACT) != 0) std::feraiseexcept(FE_INEXACT); + return Finite; } } @@ -207,6 +198,9 @@ long long APS5_VABI _Stoll_nid_postfix(const char* text, char** end, int base) { short APS5_VABI _Exp_nid_postfix(double* value, double y, short offset) { if (value == nullptr) throw std::invalid_argument("_Exp: null value"); + std::fexcept_t entryFlags; + std::fegetexceptflag(&entryFlags, FE_ALL_EXCEPT); + const int entryErrno = errno; const double x = *value; constexpr double HugeExp = 1842.0; constexpr double Eps = std::numeric_limits::epsilon(); @@ -218,21 +212,59 @@ short APS5_VABI _Exp_nid_postfix(double* value, double y, short offset) { constexpr double Q0 = 30.01511290683317; constexpr double Q1 = 3362.72154416553028; constexpr double Q2 = 30265.40189360949691; - if (std::isnan(x)) { - *value = x; - return NotANumber; - } - if (std::isnan(y)) { - *value = y; - return NotANumber; - } - if (y == 0.0) { - *value = 0.0; + constexpr double E = 0x1.5bf0a8b145769p+1; + const short xKind = ClassifyValue(x); + const short yKind = ClassifyValue(y); + if (xKind == NotANumber) return NotANumber; + if (yKind == NotANumber) return NotANumber; + if (xKind == Infinite) { + if (x > 0.0) { + if (yKind == Zero) { + *value = std::numeric_limits::quiet_NaN(); + RaiseLibcError(FE_INVALID); + return NotANumber; + } + if (yKind == Infinite) { + *value = y; + } else { + *value = std::copysign(std::numeric_limits::infinity(), y); + } + return Infinite; + } + if (yKind == Infinite) { + *value = std::numeric_limits::quiet_NaN(); + RaiseLibcError(FE_INVALID); + return NotANumber; + } + *value = std::copysign(0.0, y); return Zero; } + if (yKind == Zero) { + *value = y; + return Zero; + } + if (yKind == Infinite) { + *value = y; + return Infinite; + } + if (xKind == Zero) { + if (offset == 0) { + *value = y; + return Finite; + } + const short result = ScaleProduct(*value, 1.0, y, offset); + if (result == Finite && std::fpclassify(*value) == FP_SUBNORMAL) { + std::fesetexceptflag(&entryFlags, FE_ALL_EXCEPT); + errno = entryErrno; + } + return result; + } + if (offset == 0 && x == 1.0 && y == 1.0) { + *value = E; + return Finite; + } if (x < -HugeExp) { *value = std::copysign(0.0, y); - RaiseLibcError(FE_UNDERFLOW); return Zero; } if (x > HugeExp) { @@ -240,54 +272,95 @@ short APS5_VABI _Exp_nid_postfix(double* value, double y, short offset) { RaiseLibcError(FE_OVERFLOW); return Infinite; } - if (std::isinf(y)) { - *value = y; - return Infinite; - } double g = x * InverseLn2; - short exponent = static_cast(g + (g < 0.0 ? -0.5 : 0.5)); + long exponent = static_cast(g + (g < 0.0 ? -0.5 : 0.5)); g = static_cast(exponent); g = (x - g * Ln2High) - g * Ln2Low; - double result; + double factor; if (-Eps < g && g < Eps) { - result = y; + factor = 1.0; } else { const double z = g * g; const double w = (Q0 * z + Q1) * z + Q2; g *= (z + P1) * z + P2; - result = (w + g) / (w - g) * 2.0 * y; + factor = (w + g) / (w - g) * 2.0; --exponent; } - *value = result; - return ScaleDouble(*value, static_cast(exponent) + offset); + const short result = ScaleProduct(*value, factor, y, exponent + offset); + if (result == Finite && std::fpclassify(*value) == FP_SUBNORMAL) { + std::fesetexceptflag(&entryFlags, FE_ALL_EXCEPT); + errno = entryErrno; + } + return result; } short APS5_VABI _FExp_nid_postfix(float* value, float y, short offset) { if (value == nullptr) throw std::invalid_argument("_FExp: null value"); + std::fexcept_t entryFlags; + std::fegetexceptflag(&entryFlags, FE_ALL_EXCEPT); + const int entryErrno = errno; const float x = *value; constexpr float HugeExp = 229.0f; constexpr float Eps = std::numeric_limits::epsilon(); - constexpr float InverseLn2 = 1.4426950408889634073599246810018921f; - constexpr float Ln2High = 22713.0f / 32768.0f; - constexpr float Ln2Low = 1.4286068203094172321214581765680755e-6f; - constexpr float P1 = 60.09114349f; - constexpr float Q0 = 12.01517514f; - constexpr float Q1 = 120.18228722f; - if (std::isnan(x)) { - *value = x; - return NotANumber; - } - if (std::isnan(y)) { - *value = y; - return NotANumber; - } - if (y == 0.0f) { - *value = 0.0f; + constexpr float InverseLn2 = 1.4426950216f; + constexpr float Ln2High = 0.69287109375f; + constexpr float Ln2Low = 0.00027608682f; + constexpr float Q = 0.09997485578f; + constexpr float P0 = 0.5f; + constexpr float P1 = 0.0083208261f; + constexpr float E = 0x1.5bf0a8p+1f; + const short xKind = ClassifyValue(x); + const short yKind = ClassifyValue(y); + if (xKind == NotANumber) return NotANumber; + if (yKind == NotANumber) return NotANumber; + if (xKind == Infinite) { + if (x > 0.0f) { + if (yKind == Zero) { + *value = std::numeric_limits::quiet_NaN(); + RaiseLibcError(FE_INVALID); + return NotANumber; + } + if (yKind == Infinite) { + *value = y; + } else { + *value = std::copysign(std::numeric_limits::infinity(), y); + } + return Infinite; + } + if (yKind == Infinite) { + *value = std::numeric_limits::quiet_NaN(); + RaiseLibcError(FE_INVALID); + return NotANumber; + } + *value = std::copysign(0.0f, y); return Zero; } + if (yKind == Zero) { + *value = y; + return Zero; + } + if (yKind == Infinite) { + *value = y; + return Infinite; + } + if (xKind == Zero) { + if (offset == 0) { + *value = y; + return Finite; + } + const short result = ScaleProduct(*value, 1.0f, y, offset); + if (result == Finite && std::fpclassify(*value) == FP_SUBNORMAL) { + std::fesetexceptflag(&entryFlags, FE_ALL_EXCEPT); + errno = entryErrno; + } + return result; + } + if (offset == 0 && x == 1.0f && y == 1.0f) { + *value = E; + return Finite; + } if (x < -HugeExp) { *value = std::copysign(0.0f, y); - RaiseLibcError(FE_UNDERFLOW); return Zero; } if (x > HugeExp) { @@ -295,26 +368,26 @@ short APS5_VABI _FExp_nid_postfix(float* value, float y, short offset) { RaiseLibcError(FE_OVERFLOW); return Infinite; } - if (std::isinf(y)) { - *value = y; - return Infinite; - } float g = x * InverseLn2; - short exponent = static_cast(g + (g < 0.0f ? -0.5f : 0.5f)); + long exponent = static_cast(g + (g < 0.0f ? -0.5f : 0.5f)); g = static_cast(exponent); g = (x - g * Ln2High) - g * Ln2Low; - float result; + float factor; if (-Eps < g && g < Eps) { - result = y; + factor = 1.0f; } else { const float z = g * g; - const float w = Q0 * z + Q1; - g *= z + P1; - result = (w + g) / (w - g) * 2.0f * y; + const float w = Q * z + 1.0f; + g *= P0 + P1 * z; + factor = (w + g) / (w - g) * 2.0f; --exponent; } - *value = result; - return ScaleFloat(*value, static_cast(exponent) + offset); + const short result = ScaleProduct(*value, factor, y, exponent + offset); + if (result == Finite && std::fpclassify(*value) == FP_SUBNORMAL) { + std::fesetexceptflag(&entryFlags, FE_ALL_EXCEPT); + errno = entryErrno; + } + return result; } double APS5_VABI _Sin_nid_postfix(double x, unsigned int quadrant) { diff --git a/core/libs/tests/GuestDinkumwareInternals.cpp b/core/libs/tests/GuestDinkumwareInternals.cpp index 63d2e41aa6..c1be3d457e 100644 --- a/core/libs/tests/GuestDinkumwareInternals.cpp +++ b/core/libs/tests/GuestDinkumwareInternals.cpp @@ -8,11 +8,18 @@ #include #include #include +#include namespace { constexpr int RangeError = 34; constexpr int InvalidArgument = 22; +constexpr short Denormal = -2; +constexpr short Finite = -1; +constexpr short Zero = 0; +constexpr short Infinite = 1; +constexpr short NotANumber = 2; +constexpr int FloatingExceptions = FE_INVALID | FE_DIVBYZERO | FE_OVERFLOW | FE_UNDERFLOW | FE_INEXACT; void Require(bool condition, const char* message) { if (!condition) { @@ -29,6 +36,24 @@ void CheckStod(const char* text, std::int64_t pten, double expected, long consum Require(same && end - text == consumed && *__error_nid_postfix() == error, message); } +void CheckExp(double x, double y, short offset, double expected, short expectedKind, int expectedErrno, int expectedFlags, const char* message) { + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + double result = x; + const short kind = _Exp_nid_postfix(&result, y, offset); + const bool same = std::isnan(expected) ? std::isnan(result) : result == expected && std::signbit(result) == std::signbit(expected); + Require(kind == expectedKind && same && errno == expectedErrno && (std::fetestexcept(FE_ALL_EXCEPT) & FloatingExceptions) == expectedFlags, message); +} + +void CheckFExp(float x, float y, short offset, float expected, short expectedKind, int expectedErrno, int expectedFlags, const char* message) { + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + float result = x; + const short kind = _FExp_nid_postfix(&result, y, offset); + const bool same = std::isnan(expected) ? std::isnan(result) : result == expected && std::signbit(result) == std::signbit(expected); + Require(kind == expectedKind && same && errno == expectedErrno && (std::fetestexcept(FE_ALL_EXCEPT) & FloatingExceptions) == expectedFlags, message); +} + void TestStod() { CheckStod("1.5", 0, 1.5, 3, 0, "unscaled decimal"); CheckStod("1.5", 1, 15.0, 3, 0, "decimal scaled up"); @@ -185,157 +210,179 @@ void TestIntegerErrors() { } void TestDoubleHelpers() { - const double e = std::exp(1.0); + const double e = 0x1.5bf0a8b145769p+1; + CheckExp(1.0, 1.0, 0, e, Finite, 0, 0, "_Exp e"); + CheckExp(0.0, 1.0, 0, 1.0, Finite, 0, 0, "_Exp zero exponent"); + CheckExp(0.0, 5.0, 0, 5.0, Finite, 0, 0, "_Exp multiplier at zero exponent"); + CheckExp(1.0, -0.0, 0, -0.0, Zero, 0, 0, "_Exp keeps a negative zero multiplier"); + CheckExp(1.0, 0.0, 0, 0.0, Zero, 0, 0, "_Exp zero multiplier"); + CheckExp(-1.0, -0.0, 0, -0.0, Zero, 0, 0, "_Exp negative zero multiplier"); + CheckExp(INFINITY, -0.0, 0, std::numeric_limits::quiet_NaN(), NotANumber, EDOM, FE_INVALID, "_Exp positive infinity with zero multiplier is invalid"); + CheckExp(INFINITY, 0.0, 0, std::numeric_limits::quiet_NaN(), NotANumber, EDOM, FE_INVALID, "_Exp positive infinity with positive zero is invalid"); + CheckExp(INFINITY, 1.0, 0, INFINITY, Infinite, 0, 0, "_Exp positive infinity"); + CheckExp(INFINITY, -1.0, 0, -INFINITY, Infinite, 0, 0, "_Exp positive infinity preserves the multiplier sign"); + CheckExp(INFINITY, INFINITY, 0, INFINITY, Infinite, 0, 0, "_Exp positive infinity with infinite multiplier"); + CheckExp(INFINITY, -INFINITY, 0, -INFINITY, Infinite, 0, 0, "_Exp positive infinity with negative infinite multiplier"); + CheckExp(-INFINITY, 1.0, 0, 0.0, Zero, 0, 0, "_Exp negative infinity"); + CheckExp(-INFINITY, -1.0, 0, -0.0, Zero, 0, 0, "_Exp negative infinity preserves the multiplier sign"); + CheckExp(-INFINITY, 0.0, 0, 0.0, Zero, 0, 0, "_Exp negative infinity with positive zero"); + CheckExp(-INFINITY, -0.0, 0, -0.0, Zero, 0, 0, "_Exp negative infinity with negative zero"); + CheckExp(-INFINITY, INFINITY, 0, std::numeric_limits::quiet_NaN(), NotANumber, EDOM, FE_INVALID, "_Exp negative infinity with infinite multiplier is invalid"); + CheckExp(-INFINITY, -INFINITY, 0, std::numeric_limits::quiet_NaN(), NotANumber, EDOM, FE_INVALID, "_Exp negative infinity with negative infinite multiplier is invalid"); + CheckExp(0.0, INFINITY, 0, INFINITY, Infinite, 0, 0, "_Exp zero exponent with infinite multiplier"); + CheckExp(0.0, -INFINITY, 0, -INFINITY, Infinite, 0, 0, "_Exp zero exponent with negative infinite multiplier"); + CheckExp(1.0, INFINITY, 0, INFINITY, Infinite, 0, 0, "_Exp finite exponent with infinite multiplier"); + CheckExp(1.0, -INFINITY, 0, -INFINITY, Infinite, 0, 0, "_Exp finite exponent with negative infinite multiplier"); + CheckExp(1.0, std::numeric_limits::quiet_NaN(), 0, 1.0, NotANumber, 0, 0, "_Exp NaN multiplier leaves the exponent"); + CheckExp(INFINITY, std::numeric_limits::quiet_NaN(), 0, INFINITY, NotANumber, 0, 0, "_Exp NaN multiplier leaves an infinite exponent"); + CheckExp(std::numeric_limits::quiet_NaN(), 0.0, 0, std::numeric_limits::quiet_NaN(), NotANumber, 0, 0, "_Exp NaN exponent with zero multiplier"); + CheckExp(std::numeric_limits::quiet_NaN(), 1.0, 0, std::numeric_limits::quiet_NaN(), NotANumber, 0, 0, "_Exp NaN exponent"); + CheckExp(std::numeric_limits::quiet_NaN(), INFINITY, 0, std::numeric_limits::quiet_NaN(), NotANumber, 0, 0, "_Exp NaN exponent with infinite multiplier"); + CheckExp(1843.0, 1.0, 0, INFINITY, Infinite, ERANGE, FE_OVERFLOW | FE_INEXACT, "_Exp positive cutoff"); + CheckExp(1843.0, -1.0, 0, -INFINITY, Infinite, ERANGE, FE_OVERFLOW | FE_INEXACT, "_Exp positive cutoff preserves the multiplier sign"); + CheckExp(1843.0, 1.0, -2660, INFINITY, Infinite, ERANGE, FE_OVERFLOW | FE_INEXACT, "_Exp positive cutoff ignores a compensating offset"); + CheckExp(-1843.0, 1.0, 0, 0.0, Zero, 0, 0, "_Exp negative cutoff"); + CheckExp(-1843.0, -1.0, 0, -0.0, Zero, 0, 0, "_Exp negative cutoff preserves the multiplier sign"); + CheckExp(-1843.0, 1.0, 2660, 0.0, Zero, 0, 0, "_Exp negative cutoff ignores a compensating offset"); + CheckExp(1843.0, 0.0, 0, 0.0, Zero, 0, 0, "_Exp positive cutoff with zero multiplier"); + CheckExp(1843.0, std::numeric_limits::quiet_NaN(), 0, 1843.0, NotANumber, 0, 0, "_Exp positive cutoff with NaN multiplier"); + CheckExp(0.0, 1.0, -1074, std::numeric_limits::denorm_min(), Finite, 0, 0, "_Exp exact minimum subnormal"); + CheckExp(0.0, 1.0, -1075, 0.0, Zero, ERANGE, FE_UNDERFLOW | FE_INEXACT, "_Exp scaled underflow"); + CheckExp(0.0, -1.0, -1075, -0.0, Zero, ERANGE, FE_UNDERFLOW | FE_INEXACT, "_Exp scaled underflow preserves the multiplier sign"); + CheckExp(0.0, 1.0, 100, std::scalbn(1.0, 100), Finite, 0, 0, "_Exp scaled finite result"); + CheckExp(0.0, 1.0, 2000, INFINITY, Infinite, ERANGE, FE_OVERFLOW | FE_INEXACT, "_Exp scaled overflow"); + CheckExp(1842.0, 1.0, 0, INFINITY, Infinite, ERANGE, FE_OVERFLOW | FE_INEXACT, "_Exp inside the positive cutoff overflows"); + CheckExp(-1842.0, 1.0, 0, 0.0, Zero, ERANGE, FE_UNDERFLOW | FE_INEXACT, "_Exp inside the negative cutoff underflows"); + + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); double result = 1.0; - Require(_Exp_nid_postfix(&result, 1.0, 0) == -1 && std::fabs(result - e) <= e * 1e-14, "_Exp e"); - result = 0.0; - Require(_Exp_nid_postfix(&result, 1.0, 0) == -1 && result == 1.0, "_Exp zero exponent"); + Require(_Exp_nid_postfix(&result, 3.0, 0) == Finite && std::fabs(result - 3.0 * e) <= 3.0 * e * 1e-14, "_Exp multiplier"); result = 1.0; - Require(_Exp_nid_postfix(&result, 3.0, 0) == -1 && std::fabs(result - 3.0 * e) <= 3.0 * e * 1e-14, "_Exp multiplier"); + Require(_Exp_nid_postfix(&result, -2.0, 0) == Finite && std::fabs(result + 2.0 * e) <= 2.0 * e * 1e-14, "_Exp negative multiplier"); result = 1.0; - Require(_Exp_nid_postfix(&result, -2.0, 0) == -1 && std::fabs(result + 2.0 * e) <= 2.0 * e * 1e-14, "_Exp negative multiplier"); - result = 0.0; - Require(_Exp_nid_postfix(&result, 5.0, 0) == -1 && result == 5.0, "_Exp multiplier at zero exponent"); - result = 1.0; - Require(_Exp_nid_postfix(&result, 1.0, 10) == -1 && std::fabs(result - e * 1024.0) <= e * 1024.0 * 1e-14, "_Exp scale"); + Require(_Exp_nid_postfix(&result, 1.0, 10) == Finite && std::fabs(result - e * 1024.0) <= e * 1024.0 * 1e-14, "_Exp scale"); for (int i = -200; i <= 200; ++i) { const double x = static_cast(i) / 16.0; result = x; - Require(_Exp_nid_postfix(&result, 1.0, 0) == -1 && std::fabs(result - std::exp(x)) <= std::fabs(std::exp(x)) * 1e-14, "_Exp finite sweep"); + Require(_Exp_nid_postfix(&result, 1.0, 0) == Finite && std::fabs(result - std::exp(x)) <= std::fabs(std::exp(x)) * 1e-13, "_Exp finite sweep"); } - result = 1.0; - Require(_Exp_nid_postfix(&result, 0.0, 0) == 0 && result == 0.0, "_Exp zero multiplier"); - errno = 0; - std::feclearexcept(FE_ALL_EXCEPT); - result = NAN; - Require(_Exp_nid_postfix(&result, 0.0, 0) == 2 && std::isnan(result) && errno == 0 && std::fetestexcept(FE_ALL_EXCEPT) == 0, "_Exp NaN zero multiplier"); - result = NAN; - Require(_Exp_nid_postfix(&result, 0.0, 17) == 2 && std::isnan(result), "_Exp scaled NaN zero multiplier"); - result = 1843.0; - Require(_Exp_nid_postfix(&result, 0.0, 0) == 0 && result == 0.0, "_Exp overflow exponent with zero multiplier"); - result = INFINITY; - Require(_Exp_nid_postfix(&result, 0.0, -17) == 0 && result == 0.0 && errno == 0 && std::fetestexcept(FE_ALL_EXCEPT) == 0, "_Exp infinite exponent with zero multiplier"); - result = -INFINITY; - Require(_Exp_nid_postfix(&result, 1.0, 0) == 0 && result == 0.0, "_Exp negative infinity"); - errno = 0; - std::feclearexcept(FE_ALL_EXCEPT); - result = 1843.0; - Require(_Exp_nid_postfix(&result, -1.0, 0) == 1 && std::isinf(result) && std::signbit(result) && errno == ERANGE && (std::fetestexcept(FE_ALL_EXCEPT) & FE_OVERFLOW) != 0, "_Exp overflow preserves multiplier sign"); - errno = 0; - std::feclearexcept(FE_ALL_EXCEPT); - result = -1843.0; - Require(_Exp_nid_postfix(&result, -1.0, 0) == 0 && result == 0.0 && std::signbit(result) && errno == ERANGE && (std::fetestexcept(FE_ALL_EXCEPT) & (FE_UNDERFLOW | FE_INEXACT)) == (FE_UNDERFLOW | FE_INEXACT), "_Exp underflow preserves multiplier sign"); - errno = 0; - std::feclearexcept(FE_ALL_EXCEPT); - result = -1843.0; - Require(_Exp_nid_postfix(&result, 1.0, 0) == 0 && result == 0.0 && !std::signbit(result) && errno == ERANGE && (std::fetestexcept(FE_ALL_EXCEPT) & (FE_UNDERFLOW | FE_INEXACT)) == (FE_UNDERFLOW | FE_INEXACT), "_Exp negative cutoff reports underflow"); - errno = 0; - std::feclearexcept(FE_ALL_EXCEPT); - result = 1.0; - Require(_Exp_nid_postfix(&result, std::numeric_limits::max(), 0) == 1 && std::isinf(result) && errno == ERANGE && (std::fetestexcept(FE_ALL_EXCEPT) & FE_OVERFLOW) != 0, "_Exp finite multiplier overflow"); - errno = 0; - std::feclearexcept(FE_ALL_EXCEPT); - result = 1.0; - Require(_Exp_nid_postfix(&result, std::numeric_limits::denorm_min(), 0) == -2 && std::fpclassify(result) == FP_SUBNORMAL && errno == ERANGE && (std::fetestexcept(FE_ALL_EXCEPT) & (FE_UNDERFLOW | FE_INEXACT)) == (FE_UNDERFLOW | FE_INEXACT), "_Exp subnormal result classification"); - errno = 0; - std::feclearexcept(FE_ALL_EXCEPT); - result = INFINITY; - Require(_Exp_nid_postfix(&result, 1.0, 0) == 1 && std::isinf(result) && errno == ERANGE && (std::fetestexcept(FE_ALL_EXCEPT) & FE_OVERFLOW) != 0, "_Exp positive infinity"); - errno = 0; - std::feclearexcept(FE_ALL_EXCEPT); - result = NAN; - Require(_Exp_nid_postfix(&result, 1.0, 0) == 2 && std::isnan(result) && errno == 0 && std::fetestexcept(FE_ALL_EXCEPT) == 0, "_Exp NaN exponent"); - errno = 0; - std::feclearexcept(FE_ALL_EXCEPT); - result = 1.0; - Require(_Exp_nid_postfix(&result, NAN, 0) == 2 && std::isnan(result) && errno == 0 && std::fetestexcept(FE_ALL_EXCEPT) == 0, "_Exp NaN multiplier"); - errno = 0; - std::feclearexcept(FE_ALL_EXCEPT); - result = 1.0; - Require(_Exp_nid_postfix(&result, INFINITY, 0) == 1 && std::isinf(result) && errno == 0 && std::fetestexcept(FE_ALL_EXCEPT) == 0, "_Exp infinite multiplier"); - result = 1000.0; - Require(_Exp_nid_postfix(&result, 1.0, 0) == 1 && std::isinf(result), "_Exp overflow"); - result = -1000.0; - Require(_Exp_nid_postfix(&result, 1.0, 0) == 0 && result == 0.0, "_Exp underflow"); - result = 1.0; - Require(_Exp_nid_postfix(&result, 1.0, -2000) == 0 && result == 0.0, "_Exp scaled underflow"); - result = 1.0; - Require(_Exp_nid_postfix(&result, 1.0, 2000) == 1 && std::isinf(result), "_Exp scaled overflow"); - const float ef = std::exp(1.0f); + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + result = 1.0; + Require(_Exp_nid_postfix(&result, std::numeric_limits::denorm_min(), 0) == Finite && std::fpclassify(result) == FP_SUBNORMAL && errno == 0 && std::fetestexcept(FE_ALL_EXCEPT) == 0, "_Exp subnormal result classification"); + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + result = -740.0; + Require(_Exp_nid_postfix(&result, 1.0, 0) == Finite && std::fpclassify(result) == FP_SUBNORMAL && errno == 0 && std::fetestexcept(FE_ALL_EXCEPT) == 0, "_Exp negative exponent subnormal result"); + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + result = 1.0; + Require(_Exp_nid_postfix(&result, std::numeric_limits::max(), 0) == Infinite && std::isinf(result) && errno == ERANGE && (std::fetestexcept(FE_ALL_EXCEPT) & (FE_OVERFLOW | FE_INEXACT)) == (FE_OVERFLOW | FE_INEXACT), "_Exp finite multiplier overflow"); + + const double largeExpected = std::scalbn(std::exp(710.0 - 1024.0 * std::log(2.0)) * 1e-100, 1024); + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + result = 710.0; + Require(_Exp_nid_postfix(&result, 1e-100, 0) == Finite && std::isfinite(result) && std::fabs(result - largeExpected) <= largeExpected * 1e-13 && errno == 0 && (std::fetestexcept(FE_ALL_EXCEPT) & (FE_OVERFLOW | FE_UNDERFLOW)) == 0, "_Exp large exponent stays finite"); + const double smallExpected = std::scalbn(std::exp(-710.0 + 1024.0 * std::log(2.0)) * 1e300, -1024); + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + result = -710.0; + Require(_Exp_nid_postfix(&result, 1e300, 0) == Finite && std::isfinite(result) && std::fabs(result - smallExpected) <= smallExpected * 1e-13 && errno == 0 && (std::fetestexcept(FE_ALL_EXCEPT) & (FE_OVERFLOW | FE_UNDERFLOW)) == 0, "_Exp small exponent stays finite"); + const double multiplierExpected = std::exp(-0.5) * std::numeric_limits::max(); + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + result = -0.5; + Require(_Exp_nid_postfix(&result, std::numeric_limits::max(), 0) == Finite && std::isfinite(result) && std::fabs(result - multiplierExpected) <= multiplierExpected * 1e-13 && errno == 0 && (std::fetestexcept(FE_ALL_EXCEPT) & (FE_OVERFLOW | FE_UNDERFLOW)) == 0, "_Exp large multiplier stays finite"); + + const float ef = 0x1.5bf0a8p+1f; + CheckFExp(1.0f, 1.0f, 0, ef, Finite, 0, 0, "_FExp e"); + CheckFExp(0.0f, 1.0f, 0, 1.0f, Finite, 0, 0, "_FExp zero exponent"); + CheckFExp(0.0f, 5.0f, 0, 5.0f, Finite, 0, 0, "_FExp multiplier at zero exponent"); + CheckFExp(1.0f, -0.0f, 0, -0.0f, Zero, 0, 0, "_FExp keeps a negative zero multiplier"); + CheckFExp(1.0f, 0.0f, 0, 0.0f, Zero, 0, 0, "_FExp zero multiplier"); + CheckFExp(-1.0f, -0.0f, 0, -0.0f, Zero, 0, 0, "_FExp negative zero multiplier"); + CheckFExp(INFINITY, -0.0f, 0, std::numeric_limits::quiet_NaN(), NotANumber, EDOM, FE_INVALID, "_FExp positive infinity with zero multiplier is invalid"); + CheckFExp(INFINITY, 0.0f, 0, std::numeric_limits::quiet_NaN(), NotANumber, EDOM, FE_INVALID, "_FExp positive infinity with positive zero is invalid"); + CheckFExp(INFINITY, 1.0f, 0, INFINITY, Infinite, 0, 0, "_FExp positive infinity"); + CheckFExp(INFINITY, -1.0f, 0, -INFINITY, Infinite, 0, 0, "_FExp positive infinity preserves the multiplier sign"); + CheckFExp(INFINITY, INFINITY, 0, INFINITY, Infinite, 0, 0, "_FExp positive infinity with infinite multiplier"); + CheckFExp(INFINITY, -INFINITY, 0, -INFINITY, Infinite, 0, 0, "_FExp positive infinity with negative infinite multiplier"); + CheckFExp(-INFINITY, 1.0f, 0, 0.0f, Zero, 0, 0, "_FExp negative infinity"); + CheckFExp(-INFINITY, -1.0f, 0, -0.0f, Zero, 0, 0, "_FExp negative infinity preserves the multiplier sign"); + CheckFExp(-INFINITY, 0.0f, 0, 0.0f, Zero, 0, 0, "_FExp negative infinity with positive zero"); + CheckFExp(-INFINITY, -0.0f, 0, -0.0f, Zero, 0, 0, "_FExp negative infinity with negative zero"); + CheckFExp(-INFINITY, INFINITY, 0, std::numeric_limits::quiet_NaN(), NotANumber, EDOM, FE_INVALID, "_FExp negative infinity with infinite multiplier is invalid"); + CheckFExp(-INFINITY, -INFINITY, 0, std::numeric_limits::quiet_NaN(), NotANumber, EDOM, FE_INVALID, "_FExp negative infinity with negative infinite multiplier is invalid"); + CheckFExp(0.0f, INFINITY, 0, INFINITY, Infinite, 0, 0, "_FExp zero exponent with infinite multiplier"); + CheckFExp(0.0f, -INFINITY, 0, -INFINITY, Infinite, 0, 0, "_FExp zero exponent with negative infinite multiplier"); + CheckFExp(1.0f, INFINITY, 0, INFINITY, Infinite, 0, 0, "_FExp finite exponent with infinite multiplier"); + CheckFExp(1.0f, -INFINITY, 0, -INFINITY, Infinite, 0, 0, "_FExp finite exponent with negative infinite multiplier"); + CheckFExp(1.0f, std::numeric_limits::quiet_NaN(), 0, 1.0f, NotANumber, 0, 0, "_FExp NaN multiplier leaves the exponent"); + CheckFExp(INFINITY, std::numeric_limits::quiet_NaN(), 0, INFINITY, NotANumber, 0, 0, "_FExp NaN multiplier leaves an infinite exponent"); + CheckFExp(std::numeric_limits::quiet_NaN(), 0.0f, 0, std::numeric_limits::quiet_NaN(), NotANumber, 0, 0, "_FExp NaN exponent with zero multiplier"); + CheckFExp(std::numeric_limits::quiet_NaN(), 1.0f, 0, std::numeric_limits::quiet_NaN(), NotANumber, 0, 0, "_FExp NaN exponent"); + CheckFExp(std::numeric_limits::quiet_NaN(), INFINITY, 0, std::numeric_limits::quiet_NaN(), NotANumber, 0, 0, "_FExp NaN exponent with infinite multiplier"); + CheckFExp(230.0f, 1.0f, 0, INFINITY, Infinite, ERANGE, FE_OVERFLOW | FE_INEXACT, "_FExp positive cutoff"); + CheckFExp(230.0f, -1.0f, 0, -INFINITY, Infinite, ERANGE, FE_OVERFLOW | FE_INEXACT, "_FExp positive cutoff preserves the multiplier sign"); + CheckFExp(230.0f, 1.0f, -332, INFINITY, Infinite, ERANGE, FE_OVERFLOW | FE_INEXACT, "_FExp positive cutoff ignores the offset"); + CheckFExp(-230.0f, 1.0f, 0, 0.0f, Zero, 0, 0, "_FExp negative cutoff"); + CheckFExp(-230.0f, -1.0f, 0, -0.0f, Zero, 0, 0, "_FExp negative cutoff preserves the multiplier sign"); + CheckFExp(-230.0f, 1.0f, 332, 0.0f, Zero, 0, 0, "_FExp negative cutoff ignores the offset"); + CheckFExp(230.0f, 0.0f, 0, 0.0f, Zero, 0, 0, "_FExp positive cutoff with zero multiplier"); + CheckFExp(230.0f, std::numeric_limits::quiet_NaN(), 0, 230.0f, NotANumber, 0, 0, "_FExp positive cutoff with NaN multiplier"); + CheckFExp(0.0f, 1.0f, -149, std::numeric_limits::denorm_min(), Finite, 0, 0, "_FExp exact minimum subnormal"); + CheckFExp(0.0f, 1.0f, -150, 0.0f, Zero, ERANGE, FE_UNDERFLOW | FE_INEXACT, "_FExp scaled underflow"); + CheckFExp(0.0f, -1.0f, -150, -0.0f, Zero, ERANGE, FE_UNDERFLOW | FE_INEXACT, "_FExp scaled underflow preserves the multiplier sign"); + CheckFExp(0.0f, 1.0f, 100, std::scalbn(1.0f, 100), Finite, 0, 0, "_FExp scaled finite result"); + CheckFExp(0.0f, 1.0f, 200, INFINITY, Infinite, ERANGE, FE_OVERFLOW | FE_INEXACT, "_FExp scaled overflow"); + CheckFExp(229.0f, 1.0f, 0, INFINITY, Infinite, ERANGE, FE_OVERFLOW | FE_INEXACT, "_FExp inside the positive cutoff overflows"); + CheckFExp(-229.0f, 1.0f, 0, 0.0f, Zero, ERANGE, FE_UNDERFLOW | FE_INEXACT, "_FExp inside the negative cutoff underflows"); + + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); float resultf = 1.0f; - Require(_FExp_nid_postfix(&resultf, 1.0f, 0) == -1 && std::fabs(resultf - ef) <= ef * 1e-6f, "_FExp e"); - resultf = 0.0f; - Require(_FExp_nid_postfix(&resultf, 1.0f, 0) == -1 && resultf == 1.0f, "_FExp zero exponent"); + Require(_FExp_nid_postfix(&resultf, 3.0f, 0) == Finite && std::fabs(resultf - 3.0f * ef) <= 3.0f * ef * 1e-6f, "_FExp multiplier"); resultf = 1.0f; - Require(_FExp_nid_postfix(&resultf, 3.0f, 0) == -1 && std::fabs(resultf - 3.0f * ef) <= 3.0f * ef * 1e-6f, "_FExp multiplier"); + Require(_FExp_nid_postfix(&resultf, -2.0f, 0) == Finite && std::fabs(resultf + 2.0f * ef) <= 2.0f * ef * 1e-6f, "_FExp negative multiplier"); resultf = 1.0f; - Require(_FExp_nid_postfix(&resultf, -2.0f, 0) == -1 && std::fabs(resultf + 2.0f * ef) <= 2.0f * ef * 1e-6f, "_FExp negative multiplier"); - resultf = 1.0f; - Require(_FExp_nid_postfix(&resultf, 1.0f, 10) == -1 && std::fabs(resultf - ef * 1024.0f) <= ef * 1024.0f * 1e-6f, "_FExp scale"); + Require(_FExp_nid_postfix(&resultf, 1.0f, 10) == Finite && std::fabs(resultf - ef * 1024.0f) <= ef * 1024.0f * 1e-6f, "_FExp scale"); for (int i = -120; i <= 120; ++i) { const float x = static_cast(i) / 16.0f; resultf = x; - Require(_FExp_nid_postfix(&resultf, 1.0f, 0) == -1 && std::fabs(resultf - std::exp(x)) <= std::fabs(std::exp(x)) * 1e-6f, "_FExp finite sweep"); + Require(_FExp_nid_postfix(&resultf, 1.0f, 0) == Finite && std::fabs(resultf - std::exp(x)) <= std::fabs(std::exp(x)) * 1e-6f, "_FExp finite sweep"); } - resultf = 1.0f; - Require(_FExp_nid_postfix(&resultf, 0.0f, 0) == 0 && resultf == 0.0f, "_FExp zero multiplier"); - errno = 0; - std::feclearexcept(FE_ALL_EXCEPT); - resultf = NAN; - Require(_FExp_nid_postfix(&resultf, 0.0f, 0) == 2 && std::isnan(resultf) && errno == 0 && std::fetestexcept(FE_ALL_EXCEPT) == 0, "_FExp NaN zero multiplier"); - resultf = NAN; - Require(_FExp_nid_postfix(&resultf, 0.0f, 17) == 2 && std::isnan(resultf), "_FExp scaled NaN zero multiplier"); - resultf = 230.0f; - Require(_FExp_nid_postfix(&resultf, 0.0f, 0) == 0 && resultf == 0.0f, "_FExp overflow exponent with zero multiplier"); - resultf = INFINITY; - Require(_FExp_nid_postfix(&resultf, 0.0f, -17) == 0 && resultf == 0.0f && errno == 0 && std::fetestexcept(FE_ALL_EXCEPT) == 0, "_FExp infinite exponent with zero multiplier"); - resultf = -INFINITY; - Require(_FExp_nid_postfix(&resultf, 1.0f, 0) == 0 && resultf == 0.0f, "_FExp negative infinity"); - errno = 0; - std::feclearexcept(FE_ALL_EXCEPT); - resultf = 230.0f; - Require(_FExp_nid_postfix(&resultf, -1.0f, 0) == 1 && std::isinf(resultf) && std::signbit(resultf) && errno == ERANGE && (std::fetestexcept(FE_ALL_EXCEPT) & FE_OVERFLOW) != 0, "_FExp overflow preserves multiplier sign"); - errno = 0; - std::feclearexcept(FE_ALL_EXCEPT); - resultf = -230.0f; - Require(_FExp_nid_postfix(&resultf, -1.0f, 0) == 0 && resultf == 0.0f && std::signbit(resultf) && errno == ERANGE && (std::fetestexcept(FE_ALL_EXCEPT) & (FE_UNDERFLOW | FE_INEXACT)) == (FE_UNDERFLOW | FE_INEXACT), "_FExp underflow preserves multiplier sign"); - errno = 0; - std::feclearexcept(FE_ALL_EXCEPT); - resultf = -230.0f; - Require(_FExp_nid_postfix(&resultf, 1.0f, 0) == 0 && resultf == 0.0f && !std::signbit(resultf) && errno == ERANGE && (std::fetestexcept(FE_ALL_EXCEPT) & (FE_UNDERFLOW | FE_INEXACT)) == (FE_UNDERFLOW | FE_INEXACT), "_FExp negative cutoff reports underflow"); + errno = 0; std::feclearexcept(FE_ALL_EXCEPT); resultf = 1.0f; - Require(_FExp_nid_postfix(&resultf, std::numeric_limits::max(), 0) == 1 && std::isinf(resultf) && errno == ERANGE && (std::fetestexcept(FE_ALL_EXCEPT) & FE_OVERFLOW) != 0, "_FExp finite multiplier overflow"); + Require(_FExp_nid_postfix(&resultf, std::numeric_limits::denorm_min(), 0) == Finite && std::fpclassify(resultf) == FP_SUBNORMAL && errno == 0 && std::fetestexcept(FE_ALL_EXCEPT) == 0, "_FExp subnormal result classification"); + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + resultf = -100.0f; + Require(_FExp_nid_postfix(&resultf, 1.0f, 0) == Finite && std::fpclassify(resultf) == FP_SUBNORMAL && errno == 0 && std::fetestexcept(FE_ALL_EXCEPT) == 0, "_FExp negative exponent subnormal result"); errno = 0; std::feclearexcept(FE_ALL_EXCEPT); resultf = 1.0f; - Require(_FExp_nid_postfix(&resultf, std::numeric_limits::denorm_min(), 0) == -2 && std::fpclassify(resultf) == FP_SUBNORMAL && errno == ERANGE && (std::fetestexcept(FE_ALL_EXCEPT) & (FE_UNDERFLOW | FE_INEXACT)) == (FE_UNDERFLOW | FE_INEXACT), "_FExp subnormal result classification"); + Require(_FExp_nid_postfix(&resultf, std::numeric_limits::max(), 0) == Infinite && std::isinf(resultf) && errno == ERANGE && (std::fetestexcept(FE_ALL_EXCEPT) & (FE_OVERFLOW | FE_INEXACT)) == (FE_OVERFLOW | FE_INEXACT), "_FExp finite multiplier overflow"); + + const float largeExpectedf = static_cast(std::exp(100.0) * 1e-35); errno = 0; std::feclearexcept(FE_ALL_EXCEPT); - resultf = INFINITY; - Require(_FExp_nid_postfix(&resultf, 1.0f, 0) == 1 && std::isinf(resultf) && errno == ERANGE && (std::fetestexcept(FE_ALL_EXCEPT) & FE_OVERFLOW) != 0, "_FExp positive infinity"); - errno = 0; - std::feclearexcept(FE_ALL_EXCEPT); - resultf = NAN; - Require(_FExp_nid_postfix(&resultf, 1.0f, 0) == 2 && std::isnan(resultf) && errno == 0 && std::fetestexcept(FE_ALL_EXCEPT) == 0, "_FExp NaN exponent"); - errno = 0; - std::feclearexcept(FE_ALL_EXCEPT); - resultf = 1.0f; - Require(_FExp_nid_postfix(&resultf, NAN, 0) == 2 && std::isnan(resultf) && errno == 0 && std::fetestexcept(FE_ALL_EXCEPT) == 0, "_FExp NaN multiplier"); - errno = 0; - std::feclearexcept(FE_ALL_EXCEPT); - resultf = 1.0f; - Require(_FExp_nid_postfix(&resultf, INFINITY, 0) == 1 && std::isinf(resultf) && errno == 0 && std::fetestexcept(FE_ALL_EXCEPT) == 0, "_FExp infinite multiplier"); resultf = 100.0f; - Require(_FExp_nid_postfix(&resultf, 1.0f, 0) == 1 && std::isinf(resultf), "_FExp overflow"); - resultf = -120.0f; - Require(_FExp_nid_postfix(&resultf, 1.0f, 0) == 0 && resultf == 0.0f, "_FExp underflow"); - resultf = 1.0f; - Require(_FExp_nid_postfix(&resultf, 1.0f, -200) == 0 && resultf == 0.0f, "_FExp scaled underflow"); - resultf = 1.0f; - Require(_FExp_nid_postfix(&resultf, 1.0f, 200) == 1 && std::isinf(resultf), "_FExp scaled overflow"); + Require(_FExp_nid_postfix(&resultf, 1e-35f, 0) == Finite && std::isfinite(resultf) && std::fabs(resultf - largeExpectedf) <= largeExpectedf * 1e-6f && errno == 0 && (std::fetestexcept(FE_ALL_EXCEPT) & (FE_OVERFLOW | FE_UNDERFLOW)) == 0, "_FExp large exponent stays finite"); + const float smallExpectedf = static_cast(std::exp(-100.0) * 1e30); + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + resultf = -100.0f; + Require(_FExp_nid_postfix(&resultf, 1e30f, 0) == Finite && std::isfinite(resultf) && std::fabs(resultf - smallExpectedf) <= smallExpectedf * 1e-6f && errno == 0 && (std::fetestexcept(FE_ALL_EXCEPT) & (FE_OVERFLOW | FE_UNDERFLOW)) == 0, "_FExp small exponent stays finite"); + const float multiplierExpectedf = static_cast(std::exp(-0.5) * std::numeric_limits::max()); + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + resultf = -0.5f; + Require(_FExp_nid_postfix(&resultf, std::numeric_limits::max(), 0) == Finite && std::isfinite(resultf) && std::fabs(resultf - multiplierExpectedf) <= multiplierExpectedf * 1e-6f && errno == 0 && (std::fetestexcept(FE_ALL_EXCEPT) & (FE_OVERFLOW | FE_UNDERFLOW)) == 0, "_FExp large multiplier stays finite"); } void TestDispatch() { diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index cb7d5c983c..1baa7f55c4 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -162,7 +162,7 @@ - [std::_Throw_Cpp_error](../../../core/libs/prx/libc/src/exception/StandardExceptions.cpp) (libc) follows Dinkumware `thread0.cpp` ([VC12](https://github.com/ganboing/crt.vc12/blob/f1d47126/src/thread0.cpp), [STL](https://github.com/microsoft/STL/blob/da52dc0f/stl/src/thread0.cpp)), without console verification. Codes outside 0..6 throw `std::out_of_range`, the deleting destructor throws, and STREAMS errnos absent from FreeBSD 11.4 [`errno.h`](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/sys/errno.h) (`ENODATA`, `ENOSR`, `ENOSTR`, `ETIME`) are omitted. Codes 0..6 throw a 32-byte [VC12 `system_error`](https://github.com/ganboing/crt.vc12/blob/f1d47126/src/system_error) with FreeBSD errnos (16, 22, 3, 12, 1, 11, 35), a static `generic_category` (not `_Generic_object`), and unverified 40-byte [GuestLocale::String](../../../core/libs/prx/libc/include/GuestLocale.hpp) messages from [`syserror.cpp`](https://github.com/ganboing/crt.vc12/blob/f1d47126/src/syserror.cpp) - [Call tracing](../../../core/libs/prx/libc/src/specifics/x86_64/CallTrace.cpp) (`--trace-imports`, `APS5_TRACE_CALLS`) is missing on macOS and records only the entry of a call, not its arguments or result. One process-wide ring keeps the last 65536 calls, so a busy thread pushes the others out; the reporting thread's own calls are listed only while they are still in that ring. Calls are counted per traced target; an import bound to a target already traced under another name is counted under the first name. The thunk saves the integer argument registers and `xmm0` to `xmm7`; wider vector arguments survive only while the recording code avoids AVX, which is untested. Linux has no crash report, so it prints the trace only on `SIGABRT` (guest or host `abort()`), not on `SIGSEGV`; a caller in a module loaded after the last traced import prints without a module name, and a thread aborting while another writes the report waits for it at most 2 seconds - [_Dtest](../../../core/libs/prx/libc/src/Math.cpp) (libc) follows Dinkumware double classification matching _FDtest: finite values return -1, subnormals -2, zeros 0, infinities 1, and NaNs 2. -- [_Exp and _FExp](../../../core/libs/prx/libc/src/DinkumwareInternals.cpp) (libc) compute `y * exp(x) * 2^offset` following the public Dinkumware CRT contracts. The implementation classifies NaN operands first so they propagate, then applies the zero-multiplier shortcut before the exponent range checks: `y == 0` returns zero even for an infinite exponent. The multiplier sign is preserved on both range cutoffs; the negative cutoff raises underflow and returns a signed zero, and the overflow cutoff raises overflow. Scaling preserves range errors for an intermediate overflow and returns the denormal classification for a subnormal final result. The exact console results, the precedence for NaN and infinite special values, range-error thresholds, directed-rounding behavior and floating-point exception behavior are not verified. +- [_Exp and _FExp](../../../core/libs/prx/libc/src/DinkumwareInternals.cpp) (libc) follow PRAGMATA PPSA02530's bundled `libc.prx` (static disassembly): both operands are classified first and only finite/denormal pairs take the finite path. The strict x-only cutoffs at ±229/±1842 store `0.0*y` without an error below and `y*inf` with overflow above, so an offset does not compensate them. A zero multiplier keeps its sign, a NaN multiplier leaves `*px` unchanged, `+inf` with zero or `-inf` with infinity is invalid, and finite multipliers with `+inf`/`-inf` return signed infinity/zero without flags. Inside the cutoffs subnormals are `Finite` without flags, a zero from scaling raises underflow, and the negative cutoff itself raises nothing. `_FExp` uses the measured `c1`, `c2`, `invln2`, `Q` and `P`; `_Exp` finite constants, exact finite bits, signaling NaNs, directed rounding and flush-to-zero modes remain unverified. - [exp2f](../../../core/libs/prx/libc/src/Math.cpp) (libc) is FreeBSD msun's [`s_exp2f.c`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/s_exp2f.c), with `huge` and `twom100` read at run time and `x + redux` rounded to float, as msun's `volatile` reads and `STRICT_ASSIGN` do. As in the `libc.prx` that titles ship, `errno` is set to `ERANGE` only in msun's overflow (`x >= 128`) and underflow (`x <= -150`) branches, so a subnormal result leaves it unchanged. Floating-point exceptions are not compared. It gives the same bits and `errno` as the `exp2f` of the `libc.prx` bundled with PPSA07923, PPSA05916 and PPSA13934 for all 2^32 arguments, run on a Windows host. - [app0.zar](../../../core/libs/prx/libc/src/AppArchive.cpp) (libc) - a [ZArchive](https://github.com/Exzap/ZArchive) beside `app0/` is a read-only layer under it: a path missing from `app0/` is looked up in the archive, case-insensitively. It is not a console format; titles ship their files in a PFS image instead. Resolving an archive-only path through any call other than the archive-aware libkernel ones throws, so `fopen`, `stat`, `access`, `opendir` and the rest do not read the archive yet, and `opendir` of a directory that exists in both places, `app0/` itself included, and `fdopen` of an archived descriptor throw instead of missing its entries or reading the null device. A damaged compressed block throws instead of returning `EIO` - [fopen_s](../../../core/libs/prx/libc/src/FileAndHeap.cpp) (libc) - returns the errno of the failed open, as C11 K.3.5.2.1 leaves the nonzero value open and Microsoft's CRT returns errno; the console's Dinkumware `fopen_s` is not verified