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 <josjuice@gmail.com>
This commit is contained in:
Geotale 2024-08-07 14:46:51 -05:00
parent 9ae560d8a0
commit 2f45391ca5
1 changed files with 47 additions and 37 deletions

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();