From 2f45391ca54bc66dd598edb4dd958a689d2ba806 Mon Sep 17 00:00:00 2001 From: Geotale Date: Wed, 7 Aug 2024 14:46:51 -0500 Subject: [PATCH] Improve Integer Rounding Accuracy Changes integer rounding to more closely meet the documentation The documentation explains to round before doing any bounds checks All this really does is make sure some exception bits won't be set wrong This depends on the rounding mode, fixing cases such as: - Round to even, (0x7fffffff, 0x7fffffff.8) - Round to down, (0x7fffffff, 0x80000000) This change also uses some standard functions for rounding Previously using them was casting to an s32 directly, now keeps the f64 RoundToIntegerMode introduced due to roundeven not being part of C++17 Finally, it can change a >0x7fffffff to >=0x80000000, done because: - It looks nicer now with integers (I liked 0s) - It gives ever so slightly better codegen on Aarch64 Co-Authored-By: JosJuice --- .../Interpreter/Interpreter_FloatingPoint.cpp | 84 +++++++++++-------- 1 file changed, 47 insertions(+), 37 deletions(-) diff --git a/Source/Core/Core/PowerPC/Interpreter/Interpreter_FloatingPoint.cpp b/Source/Core/Core/PowerPC/Interpreter/Interpreter_FloatingPoint.cpp index 8036a079f6..b1dbbf0cc1 100644 --- a/Source/Core/Core/PowerPC/Interpreter/Interpreter_FloatingPoint.cpp +++ b/Source/Core/Core/PowerPC/Interpreter/Interpreter_FloatingPoint.cpp @@ -8,6 +8,7 @@ #include "Common/CommonTypes.h" #include "Common/FloatUtils.h" +#include "Common/Unreachable.h" #include "Core/PowerPC/Gekko.h" #include "Core/PowerPC/Interpreter/Interpreter_FPUtils.h" #include "Core/PowerPC/PowerPC.h" @@ -32,6 +33,22 @@ void SetFI(PowerPC::PowerPCState& ppc_state, u32 FI) ppc_state.fpscr.FI = FI; } +// Round a number to an integer in the same direction as the CPU rounding mode, +// without setting any CPU flags or being careful about NaNs +double RoundToIntegerMode(double number) +{ + // This value is 2^52 -- The first number in which double precision floating point + // numbers can only store subsequent integers, and no longer any decimals + // This keeps the sign of the unrounded value because it needs to scale it + // upwards when added + const double int_precision = std::copysign(4503599627370496.0, number); + + // By adding this value to the original number, + // it will be forced to decide a integer to round to + // This rounding will be the same as the CPU rounding mode + return (number + int_precision) - int_precision; +} + // Note that the convert to integer operation is defined // in Appendix C.4.2 in PowerPC Microprocessor Family: // The Programming Environments Manual for 32 and 64-bit Microprocessors @@ -39,9 +56,34 @@ void ConvertToInteger(PowerPC::PowerPCState& ppc_state, UGeckoInstruction inst, RoundingMode rounding_mode) { const double b = ppc_state.ps[inst.FB].PS0AsDouble(); + double rounded; u32 value; bool exception_occurred = false; + // To reduce complexity, this takes in a rounding mode in a switch case, + // rather than always judging based on the emulated CPU rounding mode + switch (rounding_mode) + { + case RoundingMode::Nearest: + // On generic platforms, the rounding should be assumed to be ties to even + // For targeted platforms this would work for any rounding mode, + // but it's mainly just kept in to replace roundeven, + // due to its lack in the C++17 (and possible lack for future versions) + rounded = RoundToIntegerMode(b); + break; + case RoundingMode::TowardsZero: + rounded = std::trunc(b); + break; + case RoundingMode::TowardsPositiveInfinity: + rounded = std::ceil(b); + break; + case RoundingMode::TowardsNegativeInfinity: + rounded = std::floor(b); + break; + default: + Common::Unreachable(); + } + if (std::isnan(b)) { if (Common::IsSNAN(b)) @@ -51,14 +93,14 @@ void ConvertToInteger(PowerPC::PowerPCState& ppc_state, UGeckoInstruction inst, SetFPException(ppc_state, FPSCR_VXCVI); exception_occurred = true; } - else if (b > static_cast(0x7fffffff)) + else if (rounded >= static_cast(0x80000000)) { // Positive large operand or +inf value = 0x7fffffff; SetFPException(ppc_state, FPSCR_VXCVI); exception_occurred = true; } - else if (b < -static_cast(0x80000000)) + else if (rounded < -static_cast(0x80000000)) { // Negative large operand or -inf value = 0x80000000; @@ -67,41 +109,9 @@ void ConvertToInteger(PowerPC::PowerPCState& ppc_state, UGeckoInstruction inst, } else { - s32 i = 0; - switch (rounding_mode) - { - case RoundingMode::Nearest: - { - const double t = b + 0.5; - i = static_cast(t); - - // Ties to even - if (t - i < 0 || (t - i == 0 && (i & 1))) - { - i--; - } - break; - } - case RoundingMode::TowardsZero: - i = static_cast(b); - break; - case RoundingMode::TowardsPositiveInfinity: - i = static_cast(b); - if (b - i > 0) - { - i++; - } - break; - case RoundingMode::TowardsNegativeInfinity: - i = static_cast(b); - if (b - i < 0) - { - i--; - } - break; - } - value = static_cast(i); - const double di = i; + s32 signed_value = static_cast(rounded); + value = static_cast(signed_value); + const double di = static_cast(signed_value); if (di == b) { ppc_state.fpscr.ClearFIFR();