Merge pull request #12981 from Geotale/proper-integer-rounding

Improve Integer Rounding Accuracy
This commit is contained in:
JosJuice 2024-09-08 12:09:58 +02:00 committed by GitHub
commit 0c1cd13b23
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -8,6 +8,7 @@
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/FloatUtils.h" #include "Common/FloatUtils.h"
#include "Common/Unreachable.h"
#include "Core/PowerPC/Gekko.h" #include "Core/PowerPC/Gekko.h"
#include "Core/PowerPC/Interpreter/Interpreter_FPUtils.h" #include "Core/PowerPC/Interpreter/Interpreter_FPUtils.h"
#include "Core/PowerPC/PowerPC.h" #include "Core/PowerPC/PowerPC.h"
@ -32,6 +33,22 @@ void SetFI(PowerPC::PowerPCState& ppc_state, u32 FI)
ppc_state.fpscr.FI = 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 // Note that the convert to integer operation is defined
// in Appendix C.4.2 in PowerPC Microprocessor Family: // in Appendix C.4.2 in PowerPC Microprocessor Family:
// The Programming Environments Manual for 32 and 64-bit Microprocessors // 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) RoundingMode rounding_mode)
{ {
const double b = ppc_state.ps[inst.FB].PS0AsDouble(); const double b = ppc_state.ps[inst.FB].PS0AsDouble();
double rounded;
u32 value; u32 value;
bool exception_occurred = false; 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 (std::isnan(b))
{ {
if (Common::IsSNAN(b)) if (Common::IsSNAN(b))
@ -51,14 +93,14 @@ void ConvertToInteger(PowerPC::PowerPCState& ppc_state, UGeckoInstruction inst,
SetFPException(ppc_state, FPSCR_VXCVI); SetFPException(ppc_state, FPSCR_VXCVI);
exception_occurred = true; exception_occurred = true;
} }
else if (b > static_cast<double>(0x7fffffff)) else if (rounded >= static_cast<double>(0x80000000))
{ {
// Positive large operand or +inf // Positive large operand or +inf
value = 0x7fffffff; value = 0x7fffffff;
SetFPException(ppc_state, FPSCR_VXCVI); SetFPException(ppc_state, FPSCR_VXCVI);
exception_occurred = true; exception_occurred = true;
} }
else if (b < -static_cast<double>(0x80000000)) else if (rounded < -static_cast<double>(0x80000000))
{ {
// Negative large operand or -inf // Negative large operand or -inf
value = 0x80000000; value = 0x80000000;
@ -67,41 +109,9 @@ void ConvertToInteger(PowerPC::PowerPCState& ppc_state, UGeckoInstruction inst,
} }
else else
{ {
s32 i = 0; s32 signed_value = static_cast<s32>(rounded);
switch (rounding_mode) value = static_cast<u32>(signed_value);
{ const double di = static_cast<double>(signed_value);
case RoundingMode::Nearest:
{
const double t = b + 0.5;
i = static_cast<s32>(t);
// Ties to even
if (t - i < 0 || (t - i == 0 && (i & 1)))
{
i--;
}
break;
}
case RoundingMode::TowardsZero:
i = static_cast<s32>(b);
break;
case RoundingMode::TowardsPositiveInfinity:
i = static_cast<s32>(b);
if (b - i > 0)
{
i++;
}
break;
case RoundingMode::TowardsNegativeInfinity:
i = static_cast<s32>(b);
if (b - i < 0)
{
i--;
}
break;
}
value = static_cast<u32>(i);
const double di = i;
if (di == b) if (di == b)
{ {
ppc_state.fpscr.ClearFIFR(); ppc_state.fpscr.ClearFIFR();