Merge pull request #12981 from Geotale/proper-integer-rounding
Improve Integer Rounding Accuracy
This commit is contained in:
commit
0c1cd13b23
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue