From 86018b503b51d32ea67b6e6978ba52fce36fc175 Mon Sep 17 00:00:00 2001 From: Lioncash Date: Mon, 7 May 2018 01:18:41 -0400 Subject: [PATCH] Common: Move floating-point utility functions to FloatUtils.h/.cpp Keeps all of the floating-point utility functions in their own file to keep them all together. This also provides a place for other general-purpose floating-point functions to be added in the future, which will be necessary when improving the flag-setting within the interpreter. --- Source/Core/Common/CMakeLists.txt | 1 + Source/Core/Common/Common.vcxproj | 2 + Source/Core/Common/Common.vcxproj.filters | 2 + Source/Core/Common/FloatUtils.cpp | 216 ++++++++++++++++++ Source/Core/Common/FloatUtils.h | 139 +++++++++++ Source/Core/Common/MathUtil.cpp | 209 ----------------- Source/Core/Common/MathUtil.h | 116 ---------- .../PowerPC/Interpreter/Interpreter_FPUtils.h | 14 +- .../Interpreter/Interpreter_FloatingPoint.cpp | 10 +- .../Interpreter/Interpreter_Paired.cpp | 10 +- .../Core/PowerPC/Jit64Common/EmuCodeBlock.cpp | 30 ++- .../PowerPC/Jit64Common/Jit64AsmCommon.cpp | 22 +- Source/Core/Core/PowerPC/PowerPC.cpp | 4 +- Source/UnitTests/Common/CMakeLists.txt | 1 + Source/UnitTests/Common/FloatUtilsTest.cpp | 68 ++++++ Source/UnitTests/Common/MathUtilTest.cpp | 60 ----- .../VideoCommon/VertexLoaderTest.cpp | 4 +- 17 files changed, 474 insertions(+), 434 deletions(-) create mode 100644 Source/Core/Common/FloatUtils.cpp create mode 100644 Source/Core/Common/FloatUtils.h create mode 100644 Source/UnitTests/Common/FloatUtilsTest.cpp diff --git a/Source/Core/Common/CMakeLists.txt b/Source/Core/Common/CMakeLists.txt index 59968e8c1c..cf87fd6e38 100644 --- a/Source/Core/Common/CMakeLists.txt +++ b/Source/Core/Common/CMakeLists.txt @@ -14,6 +14,7 @@ add_library(common File.cpp FileSearch.cpp FileUtil.cpp + FloatUtils.cpp GekkoDisassembler.cpp Hash.cpp HttpRequest.cpp diff --git a/Source/Core/Common/Common.vcxproj b/Source/Core/Common/Common.vcxproj index 6c66dc871e..db4031ed0a 100644 --- a/Source/Core/Common/Common.vcxproj +++ b/Source/Core/Common/Common.vcxproj @@ -122,6 +122,7 @@ + @@ -179,6 +180,7 @@ + diff --git a/Source/Core/Common/Common.vcxproj.filters b/Source/Core/Common/Common.vcxproj.filters index c5deca0fd8..8e0817faf1 100644 --- a/Source/Core/Common/Common.vcxproj.filters +++ b/Source/Core/Common/Common.vcxproj.filters @@ -47,6 +47,7 @@ + @@ -277,6 +278,7 @@ + diff --git a/Source/Core/Common/FloatUtils.cpp b/Source/Core/Common/FloatUtils.cpp new file mode 100644 index 0000000000..4bbb4266d6 --- /dev/null +++ b/Source/Core/Common/FloatUtils.cpp @@ -0,0 +1,216 @@ +// Copyright 2018 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "Common/FloatUtils.h" + +#include + +namespace Common +{ +u32 ClassifyDouble(double dvalue) +{ + // TODO: Optimize the below to be as fast as possible. + IntDouble value(dvalue); + u64 sign = value.i & DOUBLE_SIGN; + u64 exp = value.i & DOUBLE_EXP; + if (exp > DOUBLE_ZERO && exp < DOUBLE_EXP) + { + // Nice normalized number. + return sign ? PPC_FPCLASS_NN : PPC_FPCLASS_PN; + } + else + { + u64 mantissa = value.i & DOUBLE_FRAC; + if (mantissa) + { + if (exp) + { + return PPC_FPCLASS_QNAN; + } + else + { + // Denormalized number. + return sign ? PPC_FPCLASS_ND : PPC_FPCLASS_PD; + } + } + else if (exp) + { + // Infinite + return sign ? PPC_FPCLASS_NINF : PPC_FPCLASS_PINF; + } + else + { + // Zero + return sign ? PPC_FPCLASS_NZ : PPC_FPCLASS_PZ; + } + } +} + +u32 ClassifyFloat(float fvalue) +{ + // TODO: Optimize the below to be as fast as possible. + IntFloat value(fvalue); + u32 sign = value.i & FLOAT_SIGN; + u32 exp = value.i & FLOAT_EXP; + if (exp > FLOAT_ZERO && exp < FLOAT_EXP) + { + // Nice normalized number. + return sign ? PPC_FPCLASS_NN : PPC_FPCLASS_PN; + } + else + { + u32 mantissa = value.i & FLOAT_FRAC; + if (mantissa) + { + if (exp) + { + return PPC_FPCLASS_QNAN; // Quiet NAN + } + else + { + // Denormalized number. + return sign ? PPC_FPCLASS_ND : PPC_FPCLASS_PD; + } + } + else if (exp) + { + // Infinite + return sign ? PPC_FPCLASS_NINF : PPC_FPCLASS_PINF; + } + else + { + // Zero + return sign ? PPC_FPCLASS_NZ : PPC_FPCLASS_PZ; + } + } +} + +const std::array frsqrte_expected = {{ + {0x3ffa000, 0x7a4}, {0x3c29000, 0x700}, {0x38aa000, 0x670}, {0x3572000, 0x5f2}, + {0x3279000, 0x584}, {0x2fb7000, 0x524}, {0x2d26000, 0x4cc}, {0x2ac0000, 0x47e}, + {0x2881000, 0x43a}, {0x2665000, 0x3fa}, {0x2468000, 0x3c2}, {0x2287000, 0x38e}, + {0x20c1000, 0x35e}, {0x1f12000, 0x332}, {0x1d79000, 0x30a}, {0x1bf4000, 0x2e6}, + {0x1a7e800, 0x568}, {0x17cb800, 0x4f3}, {0x1552800, 0x48d}, {0x130c000, 0x435}, + {0x10f2000, 0x3e7}, {0x0eff000, 0x3a2}, {0x0d2e000, 0x365}, {0x0b7c000, 0x32e}, + {0x09e5000, 0x2fc}, {0x0867000, 0x2d0}, {0x06ff000, 0x2a8}, {0x05ab800, 0x283}, + {0x046a000, 0x261}, {0x0339800, 0x243}, {0x0218800, 0x226}, {0x0105800, 0x20b}, +}}; + +double ApproximateReciprocalSquareRoot(double val) +{ + union + { + double valf; + s64 vali; + }; + valf = val; + s64 mantissa = vali & ((1LL << 52) - 1); + s64 sign = vali & (1ULL << 63); + s64 exponent = vali & (0x7FFLL << 52); + + // Special case 0 + if (mantissa == 0 && exponent == 0) + return sign ? -std::numeric_limits::infinity() : + std::numeric_limits::infinity(); + // Special case NaN-ish numbers + if (exponent == (0x7FFLL << 52)) + { + if (mantissa == 0) + { + if (sign) + return std::numeric_limits::quiet_NaN(); + + return 0.0; + } + + return 0.0 + valf; + } + + // Negative numbers return NaN + if (sign) + return std::numeric_limits::quiet_NaN(); + + if (!exponent) + { + // "Normalize" denormal values + do + { + exponent -= 1LL << 52; + mantissa <<= 1; + } while (!(mantissa & (1LL << 52))); + mantissa &= (1LL << 52) - 1; + exponent += 1LL << 52; + } + + bool odd_exponent = !(exponent & (1LL << 52)); + exponent = ((0x3FFLL << 52) - ((exponent - (0x3FELL << 52)) / 2)) & (0x7FFLL << 52); + + int i = (int)(mantissa >> 37); + vali = sign | exponent; + int index = i / 2048 + (odd_exponent ? 16 : 0); + const auto& entry = frsqrte_expected[index]; + vali |= (s64)(entry.m_base - entry.m_dec * (i % 2048)) << 26; + return valf; +} + +const std::array fres_expected = {{ + {0x7ff800, 0x3e1}, {0x783800, 0x3a7}, {0x70ea00, 0x371}, {0x6a0800, 0x340}, {0x638800, 0x313}, + {0x5d6200, 0x2ea}, {0x579000, 0x2c4}, {0x520800, 0x2a0}, {0x4cc800, 0x27f}, {0x47ca00, 0x261}, + {0x430800, 0x245}, {0x3e8000, 0x22a}, {0x3a2c00, 0x212}, {0x360800, 0x1fb}, {0x321400, 0x1e5}, + {0x2e4a00, 0x1d1}, {0x2aa800, 0x1be}, {0x272c00, 0x1ac}, {0x23d600, 0x19b}, {0x209e00, 0x18b}, + {0x1d8800, 0x17c}, {0x1a9000, 0x16e}, {0x17ae00, 0x15b}, {0x14f800, 0x15b}, {0x124400, 0x143}, + {0x0fbe00, 0x143}, {0x0d3800, 0x12d}, {0x0ade00, 0x12d}, {0x088400, 0x11a}, {0x065000, 0x11a}, + {0x041c00, 0x108}, {0x020c00, 0x106}, +}}; + +// Used by fres and ps_res. +double ApproximateReciprocal(double val) +{ + // We are using namespace std scoped here because the Android NDK is complete trash as usual + // For 32bit targets(mips, ARMv7, x86) it doesn't provide an implementation of std::copysign + // but instead provides just global namespace copysign implementations. + // The workaround for this is to just use namespace std within this function's scope + // That way on real toolchains it will use the std:: variant like normal. + using namespace std; + union + { + double valf; + s64 vali; + }; + + valf = val; + s64 mantissa = vali & ((1LL << 52) - 1); + s64 sign = vali & (1ULL << 63); + s64 exponent = vali & (0x7FFLL << 52); + + // Special case 0 + if (mantissa == 0 && exponent == 0) + return copysign(std::numeric_limits::infinity(), valf); + + // Special case NaN-ish numbers + if (exponent == (0x7FFLL << 52)) + { + if (mantissa == 0) + return copysign(0.0, valf); + return 0.0 + valf; + } + + // Special case small inputs + if (exponent < (895LL << 52)) + return copysign(std::numeric_limits::max(), valf); + + // Special case large inputs + if (exponent >= (1149LL << 52)) + return copysign(0.0, valf); + + exponent = (0x7FDLL << 52) - exponent; + + int i = (int)(mantissa >> 37); + const auto& entry = fres_expected[i / 1024]; + vali = sign | exponent; + vali |= (s64)(entry.m_base - (entry.m_dec * (i % 1024) + 1) / 2) << 29; + return valf; +} + +} // namespace Common diff --git a/Source/Core/Common/FloatUtils.h b/Source/Core/Common/FloatUtils.h new file mode 100644 index 0000000000..959a20bb17 --- /dev/null +++ b/Source/Core/Common/FloatUtils.h @@ -0,0 +1,139 @@ +// Copyright 2018 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +#include "Common/CommonTypes.h" + +namespace Common +{ +template +constexpr T SNANConstant() +{ + return std::numeric_limits::signaling_NaN(); +} + +#ifdef _MSC_VER + +// MSVC needs a workaround, because its std::numeric_limits::signaling_NaN() +// will use __builtin_nans, which is improperly handled by the compiler and generates +// a bad constant. Here we go back to the version MSVC used before the builtin. +// TODO: Remove this and use numeric_limits directly whenever this bug is fixed. + +template <> +constexpr double SNANConstant() +{ + return (_CSTD _Snan._Double); +} +template <> +constexpr float SNANConstant() +{ + return (_CSTD _Snan._Float); +} + +#endif + +// The most significant bit of the fraction is an is-quiet bit on all architectures we care about. +enum : u64 +{ + DOUBLE_SIGN = 0x8000000000000000ULL, + DOUBLE_EXP = 0x7FF0000000000000ULL, + DOUBLE_FRAC = 0x000FFFFFFFFFFFFFULL, + DOUBLE_ZERO = 0x0000000000000000ULL, + DOUBLE_QBIT = 0x0008000000000000ULL +}; + +enum : u32 +{ + FLOAT_SIGN = 0x80000000, + FLOAT_EXP = 0x7F800000, + FLOAT_FRAC = 0x007FFFFF, + FLOAT_ZERO = 0x00000000 +}; + +union IntDouble +{ + double d; + u64 i; + + explicit IntDouble(u64 _i) : i(_i) {} + explicit IntDouble(double _d) : d(_d) {} +}; +union IntFloat +{ + float f; + u32 i; + + explicit IntFloat(u32 _i) : i(_i) {} + explicit IntFloat(float _f) : f(_f) {} +}; + +inline bool IsQNAN(double d) +{ + IntDouble x(d); + return ((x.i & DOUBLE_EXP) == DOUBLE_EXP) && ((x.i & DOUBLE_QBIT) == DOUBLE_QBIT); +} + +inline bool IsSNAN(double d) +{ + IntDouble x(d); + return ((x.i & DOUBLE_EXP) == DOUBLE_EXP) && ((x.i & DOUBLE_FRAC) != DOUBLE_ZERO) && + ((x.i & DOUBLE_QBIT) == DOUBLE_ZERO); +} + +inline float FlushToZero(float f) +{ + IntFloat x(f); + if ((x.i & FLOAT_EXP) == 0) + { + x.i &= FLOAT_SIGN; // turn into signed zero + } + return x.f; +} + +inline double FlushToZero(double d) +{ + IntDouble x(d); + if ((x.i & DOUBLE_EXP) == 0) + { + x.i &= DOUBLE_SIGN; // turn into signed zero + } + return x.d; +} + +enum PPCFpClass +{ + PPC_FPCLASS_QNAN = 0x11, + PPC_FPCLASS_NINF = 0x9, + PPC_FPCLASS_NN = 0x8, + PPC_FPCLASS_ND = 0x18, + PPC_FPCLASS_NZ = 0x12, + PPC_FPCLASS_PZ = 0x2, + PPC_FPCLASS_PD = 0x14, + PPC_FPCLASS_PN = 0x4, + PPC_FPCLASS_PINF = 0x5, +}; + +// Uses PowerPC conventions for the return value, so it can be easily +// used directly in CPU emulation. +u32 ClassifyDouble(double dvalue); +// More efficient float version. +u32 ClassifyFloat(float fvalue); + +struct BaseAndDec +{ + int m_base; + int m_dec; +}; +extern const std::array frsqrte_expected; +extern const std::array fres_expected; + +// PowerPC approximation algorithms +double ApproximateReciprocalSquareRoot(double val); +double ApproximateReciprocal(double val); + +} // namespace Common diff --git a/Source/Core/Common/MathUtil.cpp b/Source/Core/Common/MathUtil.cpp index 0557c838b3..c1be813cc1 100644 --- a/Source/Core/Common/MathUtil.cpp +++ b/Source/Core/Common/MathUtil.cpp @@ -10,215 +10,6 @@ #include "Common/CommonTypes.h" #include "Common/MathUtil.h" -namespace MathUtil -{ -u32 ClassifyDouble(double dvalue) -{ - // TODO: Optimize the below to be as fast as possible. - IntDouble value(dvalue); - u64 sign = value.i & DOUBLE_SIGN; - u64 exp = value.i & DOUBLE_EXP; - if (exp > DOUBLE_ZERO && exp < DOUBLE_EXP) - { - // Nice normalized number. - return sign ? PPC_FPCLASS_NN : PPC_FPCLASS_PN; - } - else - { - u64 mantissa = value.i & DOUBLE_FRAC; - if (mantissa) - { - if (exp) - { - return PPC_FPCLASS_QNAN; - } - else - { - // Denormalized number. - return sign ? PPC_FPCLASS_ND : PPC_FPCLASS_PD; - } - } - else if (exp) - { - // Infinite - return sign ? PPC_FPCLASS_NINF : PPC_FPCLASS_PINF; - } - else - { - // Zero - return sign ? PPC_FPCLASS_NZ : PPC_FPCLASS_PZ; - } - } -} - -u32 ClassifyFloat(float fvalue) -{ - // TODO: Optimize the below to be as fast as possible. - IntFloat value(fvalue); - u32 sign = value.i & FLOAT_SIGN; - u32 exp = value.i & FLOAT_EXP; - if (exp > FLOAT_ZERO && exp < FLOAT_EXP) - { - // Nice normalized number. - return sign ? PPC_FPCLASS_NN : PPC_FPCLASS_PN; - } - else - { - u32 mantissa = value.i & FLOAT_FRAC; - if (mantissa) - { - if (exp) - { - return PPC_FPCLASS_QNAN; // Quiet NAN - } - else - { - // Denormalized number. - return sign ? PPC_FPCLASS_ND : PPC_FPCLASS_PD; - } - } - else if (exp) - { - // Infinite - return sign ? PPC_FPCLASS_NINF : PPC_FPCLASS_PINF; - } - else - { - // Zero - return sign ? PPC_FPCLASS_NZ : PPC_FPCLASS_PZ; - } - } -} - -const std::array frsqrte_expected = {{ - {0x3ffa000, 0x7a4}, {0x3c29000, 0x700}, {0x38aa000, 0x670}, {0x3572000, 0x5f2}, - {0x3279000, 0x584}, {0x2fb7000, 0x524}, {0x2d26000, 0x4cc}, {0x2ac0000, 0x47e}, - {0x2881000, 0x43a}, {0x2665000, 0x3fa}, {0x2468000, 0x3c2}, {0x2287000, 0x38e}, - {0x20c1000, 0x35e}, {0x1f12000, 0x332}, {0x1d79000, 0x30a}, {0x1bf4000, 0x2e6}, - {0x1a7e800, 0x568}, {0x17cb800, 0x4f3}, {0x1552800, 0x48d}, {0x130c000, 0x435}, - {0x10f2000, 0x3e7}, {0x0eff000, 0x3a2}, {0x0d2e000, 0x365}, {0x0b7c000, 0x32e}, - {0x09e5000, 0x2fc}, {0x0867000, 0x2d0}, {0x06ff000, 0x2a8}, {0x05ab800, 0x283}, - {0x046a000, 0x261}, {0x0339800, 0x243}, {0x0218800, 0x226}, {0x0105800, 0x20b}, -}}; - -double ApproximateReciprocalSquareRoot(double val) -{ - union - { - double valf; - s64 vali; - }; - valf = val; - s64 mantissa = vali & ((1LL << 52) - 1); - s64 sign = vali & (1ULL << 63); - s64 exponent = vali & (0x7FFLL << 52); - - // Special case 0 - if (mantissa == 0 && exponent == 0) - return sign ? -std::numeric_limits::infinity() : - std::numeric_limits::infinity(); - // Special case NaN-ish numbers - if (exponent == (0x7FFLL << 52)) - { - if (mantissa == 0) - { - if (sign) - return std::numeric_limits::quiet_NaN(); - - return 0.0; - } - - return 0.0 + valf; - } - - // Negative numbers return NaN - if (sign) - return std::numeric_limits::quiet_NaN(); - - if (!exponent) - { - // "Normalize" denormal values - do - { - exponent -= 1LL << 52; - mantissa <<= 1; - } while (!(mantissa & (1LL << 52))); - mantissa &= (1LL << 52) - 1; - exponent += 1LL << 52; - } - - bool odd_exponent = !(exponent & (1LL << 52)); - exponent = ((0x3FFLL << 52) - ((exponent - (0x3FELL << 52)) / 2)) & (0x7FFLL << 52); - - int i = (int)(mantissa >> 37); - vali = sign | exponent; - int index = i / 2048 + (odd_exponent ? 16 : 0); - const auto& entry = frsqrte_expected[index]; - vali |= (s64)(entry.m_base - entry.m_dec * (i % 2048)) << 26; - return valf; -} - -const std::array fres_expected = {{ - {0x7ff800, 0x3e1}, {0x783800, 0x3a7}, {0x70ea00, 0x371}, {0x6a0800, 0x340}, {0x638800, 0x313}, - {0x5d6200, 0x2ea}, {0x579000, 0x2c4}, {0x520800, 0x2a0}, {0x4cc800, 0x27f}, {0x47ca00, 0x261}, - {0x430800, 0x245}, {0x3e8000, 0x22a}, {0x3a2c00, 0x212}, {0x360800, 0x1fb}, {0x321400, 0x1e5}, - {0x2e4a00, 0x1d1}, {0x2aa800, 0x1be}, {0x272c00, 0x1ac}, {0x23d600, 0x19b}, {0x209e00, 0x18b}, - {0x1d8800, 0x17c}, {0x1a9000, 0x16e}, {0x17ae00, 0x15b}, {0x14f800, 0x15b}, {0x124400, 0x143}, - {0x0fbe00, 0x143}, {0x0d3800, 0x12d}, {0x0ade00, 0x12d}, {0x088400, 0x11a}, {0x065000, 0x11a}, - {0x041c00, 0x108}, {0x020c00, 0x106}, -}}; - -// Used by fres and ps_res. -double ApproximateReciprocal(double val) -{ - // We are using namespace std scoped here because the Android NDK is complete trash as usual - // For 32bit targets(mips, ARMv7, x86) it doesn't provide an implementation of std::copysign - // but instead provides just global namespace copysign implementations. - // The workaround for this is to just use namespace std within this function's scope - // That way on real toolchains it will use the std:: variant like normal. - using namespace std; - union - { - double valf; - s64 vali; - }; - - valf = val; - s64 mantissa = vali & ((1LL << 52) - 1); - s64 sign = vali & (1ULL << 63); - s64 exponent = vali & (0x7FFLL << 52); - - // Special case 0 - if (mantissa == 0 && exponent == 0) - return copysign(std::numeric_limits::infinity(), valf); - - // Special case NaN-ish numbers - if (exponent == (0x7FFLL << 52)) - { - if (mantissa == 0) - return copysign(0.0, valf); - return 0.0 + valf; - } - - // Special case small inputs - if (exponent < (895LL << 52)) - return copysign(std::numeric_limits::max(), valf); - - // Special case large inputs - if (exponent >= (1149LL << 52)) - return copysign(0.0, valf); - - exponent = (0x7FDLL << 52) - exponent; - - int i = (int)(mantissa >> 37); - const auto& entry = fres_expected[i / 1024]; - vali = sign | exponent; - vali |= (s64)(entry.m_base - (entry.m_dec * (i % 1024) + 1) / 2) << 29; - return valf; -} - -} // namespace - inline void MatrixMul(int n, const float* a, const float* b, float* result) { for (int i = 0; i < n; ++i) diff --git a/Source/Core/Common/MathUtil.h b/Source/Core/Common/MathUtil.h index 58aad3522d..06ab1afd62 100644 --- a/Source/Core/Common/MathUtil.h +++ b/Source/Core/Common/MathUtil.h @@ -17,32 +17,6 @@ namespace MathUtil { -template -constexpr T SNANConstant() -{ - return std::numeric_limits::signaling_NaN(); -} - -#ifdef _MSC_VER - -// MSVC needs a workaround, because its std::numeric_limits::signaling_NaN() -// will use __builtin_nans, which is improperly handled by the compiler and generates -// a bad constant. Here we go back to the version MSVC used before the builtin. -// TODO: Remove this and use numeric_limits directly whenever this bug is fixed. - -template <> -constexpr double SNANConstant() -{ - return (_CSTD _Snan._Double); -} -template <> -constexpr float SNANConstant() -{ - return (_CSTD _Snan._Float); -} - -#endif - template constexpr T Clamp(const T val, const T& min, const T& max) { @@ -55,96 +29,6 @@ constexpr bool IsPow2(T imm) return imm > 0 && (imm & (imm - 1)) == 0; } -// The most significant bit of the fraction is an is-quiet bit on all architectures we care about. - -static const u64 DOUBLE_SIGN = 0x8000000000000000ULL, DOUBLE_EXP = 0x7FF0000000000000ULL, - DOUBLE_FRAC = 0x000FFFFFFFFFFFFFULL, DOUBLE_ZERO = 0x0000000000000000ULL, - DOUBLE_QBIT = 0x0008000000000000ULL; - -static const u32 FLOAT_SIGN = 0x80000000, FLOAT_EXP = 0x7F800000, FLOAT_FRAC = 0x007FFFFF, - FLOAT_ZERO = 0x00000000; - -union IntDouble -{ - double d; - u64 i; - - explicit IntDouble(u64 _i) : i(_i) {} - explicit IntDouble(double _d) : d(_d) {} -}; -union IntFloat -{ - float f; - u32 i; - - explicit IntFloat(u32 _i) : i(_i) {} - explicit IntFloat(float _f) : f(_f) {} -}; - -inline bool IsQNAN(double d) -{ - IntDouble x(d); - return ((x.i & DOUBLE_EXP) == DOUBLE_EXP) && ((x.i & DOUBLE_QBIT) == DOUBLE_QBIT); -} - -inline bool IsSNAN(double d) -{ - IntDouble x(d); - return ((x.i & DOUBLE_EXP) == DOUBLE_EXP) && ((x.i & DOUBLE_FRAC) != DOUBLE_ZERO) && - ((x.i & DOUBLE_QBIT) == DOUBLE_ZERO); -} - -inline float FlushToZero(float f) -{ - IntFloat x(f); - if ((x.i & FLOAT_EXP) == 0) - { - x.i &= FLOAT_SIGN; // turn into signed zero - } - return x.f; -} - -inline double FlushToZero(double d) -{ - IntDouble x(d); - if ((x.i & DOUBLE_EXP) == 0) - { - x.i &= DOUBLE_SIGN; // turn into signed zero - } - return x.d; -} - -enum PPCFpClass -{ - PPC_FPCLASS_QNAN = 0x11, - PPC_FPCLASS_NINF = 0x9, - PPC_FPCLASS_NN = 0x8, - PPC_FPCLASS_ND = 0x18, - PPC_FPCLASS_NZ = 0x12, - PPC_FPCLASS_PZ = 0x2, - PPC_FPCLASS_PD = 0x14, - PPC_FPCLASS_PN = 0x4, - PPC_FPCLASS_PINF = 0x5, -}; - -// Uses PowerPC conventions for the return value, so it can be easily -// used directly in CPU emulation. -u32 ClassifyDouble(double dvalue); -// More efficient float version. -u32 ClassifyFloat(float fvalue); - -struct BaseAndDec -{ - int m_base; - int m_dec; -}; -extern const std::array frsqrte_expected; -extern const std::array fres_expected; - -// PowerPC approximation algorithms -double ApproximateReciprocalSquareRoot(double val); -double ApproximateReciprocal(double val); - template struct Rectangle { diff --git a/Source/Core/Core/PowerPC/Interpreter/Interpreter_FPUtils.h b/Source/Core/Core/PowerPC/Interpreter/Interpreter_FPUtils.h index ce2c1485a1..593dc10bbb 100644 --- a/Source/Core/Core/PowerPC/Interpreter/Interpreter_FPUtils.h +++ b/Source/Core/Core/PowerPC/Interpreter/Interpreter_FPUtils.h @@ -10,7 +10,7 @@ #include "Common/CPUDetect.h" #include "Common/CommonTypes.h" -#include "Common/MathUtil.h" +#include "Common/FloatUtils.h" #include "Core/PowerPC/Gekko.h" #include "Core/PowerPC/PowerPC.h" @@ -56,7 +56,7 @@ inline double ForceSingle(double value) float x = (float)value; if (!cpu_info.bFlushToZero && FPSCR.NI) { - x = MathUtil::FlushToZero(x); + x = Common::FlushToZero(x); } // ...and back to double: return x; @@ -66,7 +66,7 @@ inline double ForceDouble(double d) { if (!cpu_info.bFlushToZero && FPSCR.NI) { - d = MathUtil::FlushToZero(d); + d = Common::FlushToZero(d); } return d; } @@ -89,7 +89,7 @@ inline double MakeQuiet(double d) u64 integral; std::memcpy(&integral, &d, sizeof(u64)); - integral |= MathUtil::DOUBLE_QBIT; + integral |= Common::DOUBLE_QBIT; double result; std::memcpy(&result, &integral, sizeof(double)); @@ -227,13 +227,13 @@ inline double NI_msub(double a, double c, double b) inline u32 ConvertToSingle(u64 x) { u32 exp = (x >> 52) & 0x7ff; - if (exp > 896 || (x & ~MathUtil::DOUBLE_SIGN) == 0) + if (exp > 896 || (x & ~Common::DOUBLE_SIGN) == 0) { return ((x >> 32) & 0xc0000000) | ((x >> 29) & 0x3fffffff); } else if (exp >= 874) { - u32 t = (u32)(0x80000000 | ((x & MathUtil::DOUBLE_FRAC) >> 21)); + u32 t = (u32)(0x80000000 | ((x & Common::DOUBLE_FRAC) >> 21)); t = t >> (905 - exp); t |= (x >> 32) & 0x80000000; return t; @@ -250,7 +250,7 @@ inline u32 ConvertToSingle(u64 x) inline u32 ConvertToSingleFTZ(u64 x) { u32 exp = (x >> 52) & 0x7ff; - if (exp > 896 || (x & ~MathUtil::DOUBLE_SIGN) == 0) + if (exp > 896 || (x & ~Common::DOUBLE_SIGN) == 0) { return ((x >> 32) & 0xc0000000) | ((x >> 29) & 0x3fffffff); } diff --git a/Source/Core/Core/PowerPC/Interpreter/Interpreter_FloatingPoint.cpp b/Source/Core/Core/PowerPC/Interpreter/Interpreter_FloatingPoint.cpp index 1adb50074d..5ef3ff035a 100644 --- a/Source/Core/Core/PowerPC/Interpreter/Interpreter_FloatingPoint.cpp +++ b/Source/Core/Core/PowerPC/Interpreter/Interpreter_FloatingPoint.cpp @@ -6,7 +6,7 @@ #include #include "Common/CommonTypes.h" -#include "Common/MathUtil.h" +#include "Common/FloatUtils.h" #include "Core/PowerPC/Interpreter/Interpreter.h" #include "Core/PowerPC/Interpreter/Interpreter_FPUtils.h" #include "Core/PowerPC/PowerPC.h" @@ -25,7 +25,7 @@ void Interpreter::Helper_FloatCompareOrdered(UGeckoInstruction inst, double fa, if (std::isnan(fa) || std::isnan(fb)) { compare_result = FPCC::FU; - if (MathUtil::IsSNAN(fa) || MathUtil::IsSNAN(fb)) + if (Common::IsSNAN(fa) || Common::IsSNAN(fb)) { SetFPException(FPSCR_VXSNAN); if (FPSCR.VE == 0) @@ -67,7 +67,7 @@ void Interpreter::Helper_FloatCompareUnordered(UGeckoInstruction inst, double fa { compare_result = FPCC::FU; - if (MathUtil::IsSNAN(fa) || MathUtil::IsSNAN(fb)) + if (Common::IsSNAN(fa) || Common::IsSNAN(fb)) { SetFPException(FPSCR_VXSNAN); } @@ -373,7 +373,7 @@ void Interpreter::fdivsx(UGeckoInstruction inst) void Interpreter::fresx(UGeckoInstruction inst) { double b = rPS0(inst.FB); - rPS0(inst.FD) = rPS1(inst.FD) = MathUtil::ApproximateReciprocal(b); + rPS0(inst.FD) = rPS1(inst.FD) = Common::ApproximateReciprocal(b); if (b == 0.0) { @@ -399,7 +399,7 @@ void Interpreter::frsqrtex(UGeckoInstruction inst) SetFPException(FPSCR_ZX); } - rPS0(inst.FD) = MathUtil::ApproximateReciprocalSquareRoot(b); + rPS0(inst.FD) = Common::ApproximateReciprocalSquareRoot(b); PowerPC::UpdateFPRF(rPS0(inst.FD)); if (inst.Rc) diff --git a/Source/Core/Core/PowerPC/Interpreter/Interpreter_Paired.cpp b/Source/Core/Core/PowerPC/Interpreter/Interpreter_Paired.cpp index 38df41895a..6a84132c96 100644 --- a/Source/Core/Core/PowerPC/Interpreter/Interpreter_Paired.cpp +++ b/Source/Core/Core/PowerPC/Interpreter/Interpreter_Paired.cpp @@ -5,7 +5,7 @@ #include #include "Common/CommonTypes.h" -#include "Common/MathUtil.h" +#include "Common/FloatUtils.h" #include "Core/PowerPC/Interpreter/Interpreter.h" #include "Core/PowerPC/Interpreter/Interpreter_FPUtils.h" #include "Core/PowerPC/PowerPC.h" @@ -123,8 +123,8 @@ void Interpreter::ps_res(UGeckoInstruction inst) SetFPException(FPSCR_ZX); } - rPS0(inst.FD) = MathUtil::ApproximateReciprocal(a); - rPS1(inst.FD) = MathUtil::ApproximateReciprocal(b); + rPS0(inst.FD) = Common::ApproximateReciprocal(a); + rPS1(inst.FD) = Common::ApproximateReciprocal(b); PowerPC::UpdateFPRF(rPS0(inst.FD)); if (inst.Rc) @@ -143,8 +143,8 @@ void Interpreter::ps_rsqrte(UGeckoInstruction inst) SetFPException(FPSCR_VXSQRT); } - rPS0(inst.FD) = ForceSingle(MathUtil::ApproximateReciprocalSquareRoot(rPS0(inst.FB))); - rPS1(inst.FD) = ForceSingle(MathUtil::ApproximateReciprocalSquareRoot(rPS1(inst.FB))); + rPS0(inst.FD) = ForceSingle(Common::ApproximateReciprocalSquareRoot(rPS0(inst.FB))); + rPS1(inst.FD) = ForceSingle(Common::ApproximateReciprocalSquareRoot(rPS1(inst.FB))); PowerPC::UpdateFPRF(rPS0(inst.FD)); diff --git a/Source/Core/Core/PowerPC/Jit64Common/EmuCodeBlock.cpp b/Source/Core/Core/PowerPC/Jit64Common/EmuCodeBlock.cpp index ab4d496a98..6f92d605a7 100644 --- a/Source/Core/Core/PowerPC/Jit64Common/EmuCodeBlock.cpp +++ b/Source/Core/Core/PowerPC/Jit64Common/EmuCodeBlock.cpp @@ -9,8 +9,8 @@ #include "Common/Assert.h" #include "Common/CPUDetect.h" +#include "Common/FloatUtils.h" #include "Common/Intrinsics.h" -#include "Common/MathUtil.h" #include "Core/HW/MMIO.h" #include "Core/HW/Memmap.h" #include "Core/PowerPC/Gekko.h" @@ -1060,8 +1060,7 @@ void EmuCodeBlock::SetFPRF(Gen::X64Reg xmm) // Nice normalized number: sign ? PPC_FPCLASS_NN : PPC_FPCLASS_PN; LEA(32, RSCRATCH, - MScaled(RSCRATCH, MathUtil::PPC_FPCLASS_NN - MathUtil::PPC_FPCLASS_PN, - MathUtil::PPC_FPCLASS_PN)); + MScaled(RSCRATCH, Common::PPC_FPCLASS_NN - Common::PPC_FPCLASS_PN, Common::PPC_FPCLASS_PN)); continue1 = J(); SetJumpTarget(maxExponent); @@ -1069,14 +1068,14 @@ void EmuCodeBlock::SetFPRF(Gen::X64Reg xmm) FixupBranch notNAN = J_CC(CC_Z); // Max exponent + mantissa: PPC_FPCLASS_QNAN - MOV(32, R(RSCRATCH), Imm32(MathUtil::PPC_FPCLASS_QNAN)); + MOV(32, R(RSCRATCH), Imm32(Common::PPC_FPCLASS_QNAN)); continue2 = J(); // Max exponent + no mantissa: sign ? PPC_FPCLASS_NINF : PPC_FPCLASS_PINF; SetJumpTarget(notNAN); LEA(32, RSCRATCH, - MScaled(RSCRATCH, MathUtil::PPC_FPCLASS_NINF - MathUtil::PPC_FPCLASS_PINF, - MathUtil::PPC_FPCLASS_PINF)); + MScaled(RSCRATCH, Common::PPC_FPCLASS_NINF - Common::PPC_FPCLASS_PINF, + Common::PPC_FPCLASS_PINF)); continue3 = J(); SetJumpTarget(zeroExponent); @@ -1085,14 +1084,13 @@ void EmuCodeBlock::SetFPRF(Gen::X64Reg xmm) // No exponent + mantissa: sign ? PPC_FPCLASS_ND : PPC_FPCLASS_PD; LEA(32, RSCRATCH, - MScaled(RSCRATCH, MathUtil::PPC_FPCLASS_ND - MathUtil::PPC_FPCLASS_PD, - MathUtil::PPC_FPCLASS_PD)); + MScaled(RSCRATCH, Common::PPC_FPCLASS_ND - Common::PPC_FPCLASS_PD, Common::PPC_FPCLASS_PD)); continue4 = J(); // Zero: sign ? PPC_FPCLASS_NZ : PPC_FPCLASS_PZ; SetJumpTarget(zero); SHL(32, R(RSCRATCH), Imm8(4)); - ADD(32, R(RSCRATCH), Imm8(MathUtil::PPC_FPCLASS_PZ)); + ADD(32, R(RSCRATCH), Imm8(Common::PPC_FPCLASS_PZ)); } else { @@ -1107,33 +1105,31 @@ void EmuCodeBlock::SetFPRF(Gen::X64Reg xmm) MOVQ_xmm(R(RSCRATCH), xmm); SHR(64, R(RSCRATCH), Imm8(63)); LEA(32, RSCRATCH, - MScaled(RSCRATCH, MathUtil::PPC_FPCLASS_NN - MathUtil::PPC_FPCLASS_PN, - MathUtil::PPC_FPCLASS_PN)); + MScaled(RSCRATCH, Common::PPC_FPCLASS_NN - Common::PPC_FPCLASS_PN, Common::PPC_FPCLASS_PN)); continue1 = J(); SetJumpTarget(nan); MOVQ_xmm(R(RSCRATCH), xmm); SHR(64, R(RSCRATCH), Imm8(63)); - MOV(32, R(RSCRATCH), Imm32(MathUtil::PPC_FPCLASS_QNAN)); + MOV(32, R(RSCRATCH), Imm32(Common::PPC_FPCLASS_QNAN)); continue2 = J(); SetJumpTarget(infinity); MOVQ_xmm(R(RSCRATCH), xmm); SHR(64, R(RSCRATCH), Imm8(63)); LEA(32, RSCRATCH, - MScaled(RSCRATCH, MathUtil::PPC_FPCLASS_NINF - MathUtil::PPC_FPCLASS_PINF, - MathUtil::PPC_FPCLASS_PINF)); + MScaled(RSCRATCH, Common::PPC_FPCLASS_NINF - Common::PPC_FPCLASS_PINF, + Common::PPC_FPCLASS_PINF)); continue3 = J(); SetJumpTarget(zeroExponent); TEST(64, R(RSCRATCH), R(RSCRATCH)); FixupBranch zero = J_CC(CC_Z); SHR(64, R(RSCRATCH), Imm8(63)); LEA(32, RSCRATCH, - MScaled(RSCRATCH, MathUtil::PPC_FPCLASS_ND - MathUtil::PPC_FPCLASS_PD, - MathUtil::PPC_FPCLASS_PD)); + MScaled(RSCRATCH, Common::PPC_FPCLASS_ND - Common::PPC_FPCLASS_PD, Common::PPC_FPCLASS_PD)); continue4 = J(); SetJumpTarget(zero); SHR(64, R(RSCRATCH), Imm8(63)); SHL(32, R(RSCRATCH), Imm8(4)); - ADD(32, R(RSCRATCH), Imm8(MathUtil::PPC_FPCLASS_PZ)); + ADD(32, R(RSCRATCH), Imm8(Common::PPC_FPCLASS_PZ)); } SetJumpTarget(continue1); diff --git a/Source/Core/Core/PowerPC/Jit64Common/Jit64AsmCommon.cpp b/Source/Core/Core/PowerPC/Jit64Common/Jit64AsmCommon.cpp index 86f29611e7..68e1251dcb 100644 --- a/Source/Core/Core/PowerPC/Jit64Common/Jit64AsmCommon.cpp +++ b/Source/Core/Core/PowerPC/Jit64Common/Jit64AsmCommon.cpp @@ -8,8 +8,8 @@ #include "Common/CPUDetect.h" #include "Common/CommonTypes.h" +#include "Common/FloatUtils.h" #include "Common/JitRegister.h" -#include "Common/MathUtil.h" #include "Common/x64ABI.h" #include "Common/x64Emitter.h" #include "Core/PowerPC/Gekko.h" @@ -57,15 +57,15 @@ void CommonAsmRoutines::GenFrsqrte() XOR(32, R(RSCRATCH_EXTRA), Imm8(0x10)); // int index = i / 2048 + (odd_exponent ? 16 : 0); PUSH(RSCRATCH2); - MOV(64, R(RSCRATCH2), ImmPtr(GetConstantFromPool(MathUtil::frsqrte_expected))); - static_assert(sizeof(MathUtil::BaseAndDec) == 8, "Unable to use SCALE_8; incorrect size"); + MOV(64, R(RSCRATCH2), ImmPtr(GetConstantFromPool(Common::frsqrte_expected))); + static_assert(sizeof(Common::BaseAndDec) == 8, "Unable to use SCALE_8; incorrect size"); SHR(64, R(RSCRATCH), Imm8(37)); AND(32, R(RSCRATCH), Imm32(0x7FF)); IMUL(32, RSCRATCH, - MComplex(RSCRATCH2, RSCRATCH_EXTRA, SCALE_8, offsetof(MathUtil::BaseAndDec, m_dec))); + MComplex(RSCRATCH2, RSCRATCH_EXTRA, SCALE_8, offsetof(Common::BaseAndDec, m_dec))); MOV(32, R(RSCRATCH_EXTRA), - MComplex(RSCRATCH2, RSCRATCH_EXTRA, SCALE_8, offsetof(MathUtil::BaseAndDec, m_base))); + MComplex(RSCRATCH2, RSCRATCH_EXTRA, SCALE_8, offsetof(Common::BaseAndDec, m_base))); SUB(32, R(RSCRATCH_EXTRA), R(RSCRATCH)); SHL(64, R(RSCRATCH_EXTRA), Imm8(26)); @@ -94,7 +94,7 @@ void CommonAsmRoutines::GenFrsqrte() SetJumpTarget(complex2); SetJumpTarget(complex3); ABI_PushRegistersAndAdjustStack(QUANTIZED_REGS_TO_SAVE, 8); - ABI_CallFunction(MathUtil::ApproximateReciprocalSquareRoot); + ABI_CallFunction(Common::ApproximateReciprocalSquareRoot); ABI_PopRegistersAndAdjustStack(QUANTIZED_REGS_TO_SAVE, 8); RET(); @@ -135,16 +135,16 @@ void CommonAsmRoutines::GenFres() AND(32, R(RSCRATCH2), Imm8(0x1F)); // i / 1024 PUSH(RSCRATCH_EXTRA); - MOV(64, R(RSCRATCH_EXTRA), ImmPtr(GetConstantFromPool(MathUtil::fres_expected))); - static_assert(sizeof(MathUtil::BaseAndDec) == 8, "Unable to use SCALE_8; incorrect size"); + MOV(64, R(RSCRATCH_EXTRA), ImmPtr(GetConstantFromPool(Common::fres_expected))); + static_assert(sizeof(Common::BaseAndDec) == 8, "Unable to use SCALE_8; incorrect size"); IMUL(32, RSCRATCH, - MComplex(RSCRATCH_EXTRA, RSCRATCH2, SCALE_8, offsetof(MathUtil::BaseAndDec, m_dec))); + MComplex(RSCRATCH_EXTRA, RSCRATCH2, SCALE_8, offsetof(Common::BaseAndDec, m_dec))); ADD(32, R(RSCRATCH), Imm8(1)); SHR(32, R(RSCRATCH), Imm8(1)); MOV(32, R(RSCRATCH2), - MComplex(RSCRATCH_EXTRA, RSCRATCH2, SCALE_8, offsetof(MathUtil::BaseAndDec, m_base))); + MComplex(RSCRATCH_EXTRA, RSCRATCH2, SCALE_8, offsetof(Common::BaseAndDec, m_base))); SUB(32, R(RSCRATCH2), R(RSCRATCH)); SHL(64, R(RSCRATCH2), Imm8(29)); @@ -165,7 +165,7 @@ void CommonAsmRoutines::GenFres() SetJumpTarget(complex); ABI_PushRegistersAndAdjustStack(QUANTIZED_REGS_TO_SAVE, 8); - ABI_CallFunction(MathUtil::ApproximateReciprocal); + ABI_CallFunction(Common::ApproximateReciprocal); ABI_PopRegistersAndAdjustStack(QUANTIZED_REGS_TO_SAVE, 8); RET(); diff --git a/Source/Core/Core/PowerPC/PowerPC.cpp b/Source/Core/Core/PowerPC/PowerPC.cpp index dd35ae801c..bd92ae893b 100644 --- a/Source/Core/Core/PowerPC/PowerPC.cpp +++ b/Source/Core/Core/PowerPC/PowerPC.cpp @@ -11,8 +11,8 @@ #include "Common/ChunkFile.h" #include "Common/CommonTypes.h" #include "Common/FPURoundMode.h" +#include "Common/FloatUtils.h" #include "Common/Logging/Log.h" -#include "Common/MathUtil.h" #include "Core/ConfigManager.h" #include "Core/CoreTiming.h" @@ -563,7 +563,7 @@ void CheckBreakPoints() void UpdateFPRF(double dvalue) { - FPSCR.FPRF = MathUtil::ClassifyDouble(dvalue); + FPSCR.FPRF = Common::ClassifyDouble(dvalue); } } // namespace PowerPC diff --git a/Source/UnitTests/Common/CMakeLists.txt b/Source/UnitTests/Common/CMakeLists.txt index 7154e86131..8b94438c30 100644 --- a/Source/UnitTests/Common/CMakeLists.txt +++ b/Source/UnitTests/Common/CMakeLists.txt @@ -7,6 +7,7 @@ add_dolphin_test(CommonFuncsTest CommonFuncsTest.cpp) add_dolphin_test(EventTest EventTest.cpp) add_dolphin_test(FixedSizeQueueTest FixedSizeQueueTest.cpp) add_dolphin_test(FlagTest FlagTest.cpp) +add_dolphin_test(FloatUtilsTest FloatUtilsTest.cpp) add_dolphin_test(MathUtilTest MathUtilTest.cpp) add_dolphin_test(NandPathsTest NandPathsTest.cpp) add_dolphin_test(SPSCQueueTest SPSCQueueTest.cpp) diff --git a/Source/UnitTests/Common/FloatUtilsTest.cpp b/Source/UnitTests/Common/FloatUtilsTest.cpp new file mode 100644 index 0000000000..c1f7117162 --- /dev/null +++ b/Source/UnitTests/Common/FloatUtilsTest.cpp @@ -0,0 +1,68 @@ +// Copyright 2018 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include +#include + +#include + +#include "Common/FloatUtils.h" + +TEST(FloatUtils, IsQNAN) +{ + EXPECT_TRUE(Common::IsQNAN(std::numeric_limits::quiet_NaN())); + EXPECT_FALSE(Common::IsQNAN(Common::SNANConstant())); +} + +TEST(FloatUtils, IsSNAN) +{ + EXPECT_FALSE(Common::IsSNAN(std::numeric_limits::quiet_NaN())); + EXPECT_TRUE(Common::IsSNAN(Common::SNANConstant())); +} + +TEST(FloatUtils, FlushToZero) +{ + // To test the software implementation we need to make sure FTZ and DAZ are disabled. + // Using volatile here to ensure the compiler doesn't constant-fold it, + // we want the multiplication to occur at test runtime. + volatile float s = std::numeric_limits::denorm_min(); + volatile double d = std::numeric_limits::denorm_min(); + // Casting away the volatile attribute is required in order for msvc to resolve this to the + // correct instance of the comparison function. + EXPECT_LT(0.f, (float)(s * 2)); + EXPECT_LT(0.0, (double)(d * 2)); + + EXPECT_EQ(+0.0, Common::FlushToZero(+std::numeric_limits::denorm_min())); + EXPECT_EQ(-0.0, Common::FlushToZero(-std::numeric_limits::denorm_min())); + EXPECT_EQ(+0.0, Common::FlushToZero(+std::numeric_limits::min() / 2)); + EXPECT_EQ(-0.0, Common::FlushToZero(-std::numeric_limits::min() / 2)); + EXPECT_EQ(std::numeric_limits::min(), + Common::FlushToZero(std::numeric_limits::min())); + EXPECT_EQ(std::numeric_limits::max(), + Common::FlushToZero(std::numeric_limits::max())); + EXPECT_EQ(+std::numeric_limits::infinity(), + Common::FlushToZero(+std::numeric_limits::infinity())); + EXPECT_EQ(-std::numeric_limits::infinity(), + Common::FlushToZero(-std::numeric_limits::infinity())); + + // Test all subnormals as well as an equally large set of random normal floats. + std::default_random_engine engine(0); + std::uniform_int_distribution dist(0x00800000u, 0x7fffffffu); + for (u32 i = 0; i <= 0x007fffffu; ++i) + { + Common::IntFloat x(i); + EXPECT_EQ(+0.f, Common::FlushToZero(x.f)); + + x.i = i | 0x80000000u; + EXPECT_EQ(-0.f, Common::FlushToZero(x.f)); + + x.i = dist(engine); + Common::IntFloat y(Common::FlushToZero(x.f)); + EXPECT_EQ(x.i, y.i); + + x.i |= 0x80000000u; + y.f = Common::FlushToZero(x.f); + EXPECT_EQ(x.i, y.i); + } +} diff --git a/Source/UnitTests/Common/MathUtilTest.cpp b/Source/UnitTests/Common/MathUtilTest.cpp index 9c84ad6f0a..4b21ace1c4 100644 --- a/Source/UnitTests/Common/MathUtilTest.cpp +++ b/Source/UnitTests/Common/MathUtilTest.cpp @@ -3,8 +3,6 @@ // Refer to the license.txt file included. #include -#include -#include #include "Common/MathUtil.h" @@ -20,18 +18,6 @@ TEST(MathUtil, Clamp) EXPECT_EQ(0.0, MathUtil::Clamp(-1.0, 0.0, 2.0)); } -TEST(MathUtil, IsQNAN) -{ - EXPECT_TRUE(MathUtil::IsQNAN(std::numeric_limits::quiet_NaN())); - EXPECT_FALSE(MathUtil::IsQNAN(MathUtil::SNANConstant())); -} - -TEST(MathUtil, IsSNAN) -{ - EXPECT_FALSE(MathUtil::IsSNAN(std::numeric_limits::quiet_NaN())); - EXPECT_TRUE(MathUtil::IsSNAN(MathUtil::SNANConstant())); -} - TEST(MathUtil, IntLog2) { EXPECT_EQ(0, IntLog2(1)); @@ -44,49 +30,3 @@ TEST(MathUtil, IntLog2) EXPECT_EQ(3, IntLog2(15)); EXPECT_EQ(63, IntLog2(0xFFFFFFFFFFFFFFFFull)); } - -TEST(MathUtil, FlushToZero) -{ - // To test the software implementation we need to make sure FTZ and DAZ are disabled. - // Using volatile here to ensure the compiler doesn't constant-fold it, - // we want the multiplication to occur at test runtime. - volatile float s = std::numeric_limits::denorm_min(); - volatile double d = std::numeric_limits::denorm_min(); - // Casting away the volatile attribute is required in order for msvc to resolve this to the - // correct instance of the comparison function. - EXPECT_LT(0.f, (float)(s * 2)); - EXPECT_LT(0.0, (double)(d * 2)); - - EXPECT_EQ(+0.0, MathUtil::FlushToZero(+std::numeric_limits::denorm_min())); - EXPECT_EQ(-0.0, MathUtil::FlushToZero(-std::numeric_limits::denorm_min())); - EXPECT_EQ(+0.0, MathUtil::FlushToZero(+std::numeric_limits::min() / 2)); - EXPECT_EQ(-0.0, MathUtil::FlushToZero(-std::numeric_limits::min() / 2)); - EXPECT_EQ(std::numeric_limits::min(), - MathUtil::FlushToZero(std::numeric_limits::min())); - EXPECT_EQ(std::numeric_limits::max(), - MathUtil::FlushToZero(std::numeric_limits::max())); - EXPECT_EQ(+std::numeric_limits::infinity(), - MathUtil::FlushToZero(+std::numeric_limits::infinity())); - EXPECT_EQ(-std::numeric_limits::infinity(), - MathUtil::FlushToZero(-std::numeric_limits::infinity())); - - // Test all subnormals as well as an equally large set of random normal floats. - std::default_random_engine engine(0); - std::uniform_int_distribution dist(0x00800000u, 0x7fffffffu); - for (u32 i = 0; i <= 0x007fffffu; ++i) - { - MathUtil::IntFloat x(i); - EXPECT_EQ(+0.f, MathUtil::FlushToZero(x.f)); - - x.i = i | 0x80000000u; - EXPECT_EQ(-0.f, MathUtil::FlushToZero(x.f)); - - x.i = dist(engine); - MathUtil::IntFloat y(MathUtil::FlushToZero(x.f)); - EXPECT_EQ(x.i, y.i); - - x.i |= 0x80000000u; - y.f = MathUtil::FlushToZero(x.f); - EXPECT_EQ(x.i, y.i); - } -} diff --git a/Source/UnitTests/VideoCommon/VertexLoaderTest.cpp b/Source/UnitTests/VideoCommon/VertexLoaderTest.cpp index 54df13351a..6ba3c4c464 100644 --- a/Source/UnitTests/VideoCommon/VertexLoaderTest.cpp +++ b/Source/UnitTests/VideoCommon/VertexLoaderTest.cpp @@ -11,7 +11,7 @@ #include // NOLINT #include "Common/Common.h" -#include "Common/MathUtil.h" +#include "Common/FloatUtils.h" #include "VideoCommon/CPMemory.h" #include "VideoCommon/DataReader.h" #include "VideoCommon/OpcodeDecoding.h" @@ -75,7 +75,7 @@ protected: void ExpectOut(float val) { // Read unswapped. - MathUtil::IntFloat expected(val), actual(m_dst.Read()); + Common::IntFloat expected(val), actual(m_dst.Read()); if (!actual.f || actual.f != actual.f) EXPECT_EQ(expected.i, actual.i); else