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/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();
|
||||||
|
|
Loading…
Reference in New Issue