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/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<double>(0x7fffffff))
else if (rounded >= static_cast<double>(0x80000000))
{
// Positive large operand or +inf
value = 0x7fffffff;
SetFPException(ppc_state, FPSCR_VXCVI);
exception_occurred = true;
}
else if (b < -static_cast<double>(0x80000000))
else if (rounded < -static_cast<double>(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<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;
s32 signed_value = static_cast<s32>(rounded);
value = static_cast<u32>(signed_value);
const double di = static_cast<double>(signed_value);
if (di == b)
{
ppc_state.fpscr.ClearFIFR();