aboutsummaryrefslogtreecommitdiff
path: root/Client/ThirdParty/fpm/tests
diff options
context:
space:
mode:
Diffstat (limited to 'Client/ThirdParty/fpm/tests')
-rw-r--r--Client/ThirdParty/fpm/tests/arithmetic.cpp53
-rw-r--r--Client/ThirdParty/fpm/tests/arithmetic_int.cpp45
-rw-r--r--Client/ThirdParty/fpm/tests/basic_math.cpp106
-rw-r--r--Client/ThirdParty/fpm/tests/classification.cpp165
-rw-r--r--Client/ThirdParty/fpm/tests/common.hpp31
-rw-r--r--Client/ThirdParty/fpm/tests/constants.cpp23
-rw-r--r--Client/ThirdParty/fpm/tests/conversion.cpp105
-rw-r--r--Client/ThirdParty/fpm/tests/customizations.cpp121
-rw-r--r--Client/ThirdParty/fpm/tests/detail.cpp13
-rw-r--r--Client/ThirdParty/fpm/tests/input.cpp312
-rw-r--r--Client/ThirdParty/fpm/tests/manip.cpp64
-rw-r--r--Client/ThirdParty/fpm/tests/nearest.cpp78
-rw-r--r--Client/ThirdParty/fpm/tests/output.cpp335
-rw-r--r--Client/ThirdParty/fpm/tests/power.cpp355
-rw-r--r--Client/ThirdParty/fpm/tests/trigonometry.cpp160
15 files changed, 1966 insertions, 0 deletions
diff --git a/Client/ThirdParty/fpm/tests/arithmetic.cpp b/Client/ThirdParty/fpm/tests/arithmetic.cpp
new file mode 100644
index 0000000..c49222a
--- /dev/null
+++ b/Client/ThirdParty/fpm/tests/arithmetic.cpp
@@ -0,0 +1,53 @@
+#include "common.hpp"
+
+TEST(arithmethic, negation)
+{
+ using P = fpm::fixed_24_8;
+
+ EXPECT_EQ(P(-13.125), -P( 13.125));
+ EXPECT_EQ(P( 13.125), -P(-13.125));
+}
+
+TEST(arithmethic, addition)
+{
+ using P = fpm::fixed_24_8;
+
+ EXPECT_EQ(P(10.75), P(3.5) + P(7.25));
+}
+
+TEST(arithmethic, subtraction)
+{
+ using P = fpm::fixed_24_8;
+
+ EXPECT_EQ(P(-3.75), P(3.5) - P(7.25));
+}
+
+TEST(arithmethic, multiplication)
+{
+ using P = fpm::fixed_24_8;
+
+ EXPECT_EQ(P(-25.375), P(3.5) * P(-7.25));
+}
+
+TEST(arithmethic, division)
+{
+ using P = fpm::fixed_24_8;
+
+ EXPECT_EQ(P(3.5 / 7.25), P(3.5) / P(7.25));
+ EXPECT_EQ(P(-3.5 / 7.25), P(-3.5) / P(7.25));
+ EXPECT_EQ(P(3.5 / -7.25), P(3.5) / P(-7.25));
+ EXPECT_EQ(P(-3.5 / -7.25), P(-3.5) / P(-7.25));
+
+#ifndef NDEBUG
+ EXPECT_DEATH(P(1) / P(0), "");
+#endif
+}
+
+TEST(arithmethic, division_range)
+{
+ using P = fpm::fixed<std::int32_t, std::int64_t, 12>;
+
+ // These calculation will overflow and produce
+ // wrong results without the intermediate type.
+ EXPECT_EQ(P(32), P(256) / P(8));
+} \ No newline at end of file
diff --git a/Client/ThirdParty/fpm/tests/arithmetic_int.cpp b/Client/ThirdParty/fpm/tests/arithmetic_int.cpp
new file mode 100644
index 0000000..be9c176
--- /dev/null
+++ b/Client/ThirdParty/fpm/tests/arithmetic_int.cpp
@@ -0,0 +1,45 @@
+#include "common.hpp"
+
+TEST(arithmethic_int, addition)
+{
+ using P = fpm::fixed_24_8;
+
+ EXPECT_EQ(P(10.5), P(3.5) + 7);
+}
+
+TEST(arithmethic_int, subtraction)
+{
+ using P = fpm::fixed_24_8;
+
+ EXPECT_EQ(P(-3.5), P(3.5) - 7);
+}
+
+TEST(arithmethic_int, multiplication)
+{
+ using P = fpm::fixed_24_8;
+
+ EXPECT_EQ(P(-24.5), P(3.5) * -7);
+}
+
+TEST(arithmethic_int, division)
+{
+ using P = fpm::fixed_24_8;
+
+ EXPECT_EQ(P(3.5 / 7), P(3.5) / 7);
+ EXPECT_EQ(P(-3.5 / 7), P(-3.5) / 7);
+ EXPECT_EQ(P(3.5 / -7), P(3.5) / -7);
+ EXPECT_EQ(P(-3.5 / -7), P(-3.5) / -7);
+
+#ifndef NDEBUG
+ EXPECT_DEATH(P(1) / 0, "");
+#endif
+}
+
+TEST(arithmethic_int, division_range)
+{
+ using P = fpm::fixed<std::int32_t, std::int64_t, 12>;
+
+ // These calculation will overflow and produce
+ // wrong results without the intermediate type.
+ EXPECT_EQ(P(32), P(256) / 8);
+} \ No newline at end of file
diff --git a/Client/ThirdParty/fpm/tests/basic_math.cpp b/Client/ThirdParty/fpm/tests/basic_math.cpp
new file mode 100644
index 0000000..3064b53
--- /dev/null
+++ b/Client/ThirdParty/fpm/tests/basic_math.cpp
@@ -0,0 +1,106 @@
+#include "common.hpp"
+#include <fpm/math.hpp>
+
+TEST(basic_math, abs)
+{
+ using P = fpm::fixed_24_8;
+
+ EXPECT_EQ(P(13.125), abs(P(-13.125)));
+ EXPECT_EQ(P(13.125), abs(P(13.125)));
+ EXPECT_EQ(P(1), abs(P(-1)));
+ EXPECT_EQ(P(1), abs(P(1)));
+}
+
+TEST(basic_math, fmod)
+{
+ using P = fpm::fixed_24_8;
+
+ EXPECT_EQ(P( 1.5), fmod(P( 9.5), P( 2)));
+ EXPECT_EQ(P(-1.5), fmod(P(-9.5), P( 2)));
+ EXPECT_EQ(P( 1.5), fmod(P( 9.5), P(-2)));
+ EXPECT_EQ(P(-1.5), fmod(P(-9.5), P(-2)));
+}
+
+TEST(basic_math, remainder)
+{
+ using P = fpm::fixed_24_8;
+
+ EXPECT_EQ(P(-0.5), remainder(P( 9.5), P( 2)));
+ EXPECT_EQ(P( 0.5), remainder(P(-9.5), P( 2)));
+ EXPECT_EQ(P(-0.5), remainder(P( 9.5), P(-2)));
+ EXPECT_EQ(P( 0.5), remainder(P(-9.5), P(-2)));
+
+ EXPECT_EQ(P( 1), remainder(P( 9), P( 2)));
+ EXPECT_EQ(P(-1), remainder(P(-9), P( 2)));
+ EXPECT_EQ(P( 1), remainder(P( 9), P(-2)));
+ EXPECT_EQ(P(-1), remainder(P(-9), P(-2)));
+
+ EXPECT_EQ(P(-1), remainder(P( 11), P( 2)));
+ EXPECT_EQ(P( 1), remainder(P(-11), P( 2)));
+ EXPECT_EQ(P(-1), remainder(P( 11), P(-2)));
+ EXPECT_EQ(P( 1), remainder(P(-11), P(-2)));
+
+ EXPECT_EQ(P(-0.9), remainder(P( 5.1), P( 3)));
+ EXPECT_EQ(P( 0.9), remainder(P(-5.1), P( 3)));
+ EXPECT_EQ(P(-0.9), remainder(P( 5.1), P(-3)));
+ EXPECT_EQ(P( 0.9), remainder(P(-5.1), P(-3)));
+
+ EXPECT_EQ(P(0), remainder(P(0), P(1)));
+}
+
+TEST(basic_math, remquo)
+{
+ // remquo must return at least 3 bits of quotient
+ constexpr int QUO_MIN_SIZE = 1 << 3;
+
+ using P = fpm::fixed_16_16;
+
+ int quo = 999999;
+ EXPECT_EQ(P( 1.5), remquo(P( 9.5), P( 2), &quo));
+ EXPECT_EQ( 4, quo % QUO_MIN_SIZE);
+ EXPECT_EQ(P(-1.5), remquo(P(-9.5), P( 2), &quo));
+ EXPECT_EQ(-4, quo % QUO_MIN_SIZE);
+ EXPECT_EQ(P( 1.5), remquo(P( 9.5), P(-2), &quo));
+ EXPECT_EQ(-4, quo % QUO_MIN_SIZE);
+ EXPECT_EQ(P(-1.5), remquo(P(-9.5), P(-2), &quo));
+ EXPECT_EQ( 4, quo % QUO_MIN_SIZE);
+
+ EXPECT_EQ(P( 1), remquo(P( 9), P( 2), &quo));
+ EXPECT_EQ( 4, quo % QUO_MIN_SIZE);
+ EXPECT_EQ(P(-1), remquo(P(-9), P( 2), &quo));
+ EXPECT_EQ(-4, quo % QUO_MIN_SIZE);
+ EXPECT_EQ(P( 1), remquo(P( 9), P(-2), &quo));
+ EXPECT_EQ(-4, quo % QUO_MIN_SIZE);
+ EXPECT_EQ(P(-1), remquo(P(-9), P(-2), &quo));
+ EXPECT_EQ( 4, quo % QUO_MIN_SIZE);
+
+ EXPECT_EQ(P( 1), remquo(P( 11), P( 2), &quo));
+ EXPECT_EQ( 5, quo % QUO_MIN_SIZE);
+ EXPECT_EQ(P(-1), remquo(P(-11), P( 2), &quo));
+ EXPECT_EQ(-5, quo % QUO_MIN_SIZE);
+ EXPECT_EQ(P( 1), remquo(P( 11), P(-2), &quo));
+ EXPECT_EQ(-5, quo % QUO_MIN_SIZE);
+ EXPECT_EQ(P(-1), remquo(P(-11), P(-2), &quo));
+ EXPECT_EQ( 5, quo % QUO_MIN_SIZE);
+
+ EXPECT_EQ(P( 2.1), remquo(P( 5.1), P( 3), &quo));
+ EXPECT_EQ( 1, quo % QUO_MIN_SIZE);
+ EXPECT_EQ(P(-2.1), remquo(P(-5.1), P( 3), &quo));
+ EXPECT_EQ(-1, quo % QUO_MIN_SIZE);
+ EXPECT_EQ(P( 2.1), remquo(P( 5.1), P(-3), &quo));
+ EXPECT_EQ(-1, quo % QUO_MIN_SIZE);
+ EXPECT_EQ(P(-2.1), remquo(P(-5.1), P(-3), &quo));
+ EXPECT_EQ( 1, quo % QUO_MIN_SIZE);
+
+ EXPECT_EQ(P( 3.375), remquo(P( 97.125), P( 3.75), &quo));
+ EXPECT_EQ( 1, quo % QUO_MIN_SIZE);
+ EXPECT_EQ(P(-3.375), remquo(P(-97.125), P( 3.75), &quo));
+ EXPECT_EQ(-1, quo % QUO_MIN_SIZE);
+ EXPECT_EQ(P( 3.375), remquo(P( 97.125), P(-3.75), &quo));
+ EXPECT_EQ(-1, quo % QUO_MIN_SIZE);
+ EXPECT_EQ(P(-3.375), remquo(P(-97.125), P(-3.75), &quo));
+ EXPECT_EQ( 1, quo % QUO_MIN_SIZE);
+
+ EXPECT_EQ(P(0), remquo(P(0), P(1), &quo));
+ EXPECT_EQ(0, quo % QUO_MIN_SIZE);
+}
diff --git a/Client/ThirdParty/fpm/tests/classification.cpp b/Client/ThirdParty/fpm/tests/classification.cpp
new file mode 100644
index 0000000..832b140
--- /dev/null
+++ b/Client/ThirdParty/fpm/tests/classification.cpp
@@ -0,0 +1,165 @@
+#include "common.hpp"
+#include <fpm/math.hpp>
+
+TEST(classification, fpclassify)
+{
+ using P = fpm::fixed<std::int32_t, std::int64_t, 12>;
+ EXPECT_EQ(FP_NORMAL, fpclassify(P(1.0)));
+ EXPECT_EQ(FP_NORMAL, fpclassify(P(-1.0)));
+ EXPECT_EQ(FP_NORMAL, fpclassify(P(0.5)));
+ EXPECT_EQ(FP_ZERO, fpclassify(P(0)));
+}
+
+TEST(classification, isfinite)
+{
+ using P = fpm::fixed<std::int32_t, std::int64_t, 12>;
+ EXPECT_TRUE(isfinite(P(1.0)));
+ EXPECT_TRUE(isfinite(P(-1.0)));
+ EXPECT_TRUE(isfinite(P(0.5)));
+ EXPECT_TRUE(isfinite(P(0)));
+}
+
+TEST(classification, isinf)
+{
+ using P = fpm::fixed<std::int32_t, std::int64_t, 12>;
+ EXPECT_FALSE(isinf(P(1.0)));
+ EXPECT_FALSE(isinf(P(-1.0)));
+ EXPECT_FALSE(isinf(P(0.5)));
+ EXPECT_FALSE(isinf(P(0)));
+}
+
+TEST(classification, isnan)
+{
+ using P = fpm::fixed<std::int32_t, std::int64_t, 12>;
+ EXPECT_FALSE(isnan(P(1.0)));
+ EXPECT_FALSE(isnan(P(-1.0)));
+ EXPECT_FALSE(isnan(P(0.5)));
+ EXPECT_FALSE(isnan(P(0)));
+}
+
+TEST(classification, isnormal)
+{
+ using P = fpm::fixed<std::int32_t, std::int64_t, 12>;
+ EXPECT_TRUE(isnormal(P(1.0)));
+ EXPECT_TRUE(isnormal(P(-1.0)));
+ EXPECT_TRUE(isnormal(P(0.5)));
+ EXPECT_FALSE(isnormal(P(0)));
+}
+
+TEST(classification, signbit)
+{
+ using P = fpm::fixed<std::int32_t, std::int64_t, 12>;
+ EXPECT_FALSE(signbit(P(1.0)));
+ EXPECT_TRUE(signbit(P(-1.0)));
+ EXPECT_FALSE(signbit(P(0.5)));
+ EXPECT_FALSE(signbit(P(0)));
+}
+
+TEST(classification, isgreater)
+{
+ using P = fpm::fixed<std::int32_t, std::int64_t, 12>;
+ EXPECT_FALSE(isgreater(P(1.0), P(2.0)));
+ EXPECT_FALSE(isgreater(P(1.0), P(1.0)));
+ EXPECT_TRUE(isgreater(P(2.0), P(1.0)));
+ EXPECT_FALSE(isgreater(P(-2.0), P(-1.0)));
+ EXPECT_FALSE(isgreater(P(-2.0), P(-2.0)));
+ EXPECT_TRUE(isgreater(P(-1.0), P(-2.0)));
+ EXPECT_FALSE(isgreater(P(0.25), P(0.5)));
+ EXPECT_FALSE(isgreater(P(0.25), P(0.25)));
+ EXPECT_TRUE(isgreater(P(0.5), P(0.25)));
+ EXPECT_FALSE(isgreater(P(-1), P(0)));
+ EXPECT_FALSE(isgreater(P(-1), P(-1)));
+ EXPECT_TRUE(isgreater(P(0), P(-1)));
+ EXPECT_FALSE(isgreater(P(0), P(0)));
+}
+
+TEST(classification, isgreaterequal)
+{
+ using P = fpm::fixed<std::int32_t, std::int64_t, 12>;
+ EXPECT_FALSE(isgreaterequal(P(1.0), P(2.0)));
+ EXPECT_TRUE(isgreaterequal(P(1.0), P(1.0)));
+ EXPECT_TRUE(isgreaterequal(P(2.0), P(1.0)));
+ EXPECT_FALSE(isgreaterequal(P(-2.0), P(-1.0)));
+ EXPECT_TRUE(isgreaterequal(P(-2.0), P(-2.0)));
+ EXPECT_TRUE(isgreaterequal(P(-1.0), P(-2.0)));
+ EXPECT_FALSE(isgreaterequal(P(0.25), P(0.5)));
+ EXPECT_TRUE(isgreaterequal(P(0.25), P(0.25)));
+ EXPECT_TRUE(isgreaterequal(P(0.5), P(0.25)));
+ EXPECT_FALSE(isgreaterequal(P(-1), P(0)));
+ EXPECT_TRUE(isgreaterequal(P(-1), P(-1)));
+ EXPECT_TRUE(isgreaterequal(P(0), P(-1)));
+ EXPECT_TRUE(isgreaterequal(P(0), P(0)));
+}
+
+TEST(classification, isless)
+{
+ using P = fpm::fixed<std::int32_t, std::int64_t, 12>;
+ EXPECT_TRUE(isless(P(1.0), P(2.0)));
+ EXPECT_FALSE(isless(P(1.0), P(1.0)));
+ EXPECT_FALSE(isless(P(2.0), P(1.0)));
+ EXPECT_TRUE(isless(P(-2.0), P(-1.0)));
+ EXPECT_FALSE(isless(P(-2.0), P(-2.0)));
+ EXPECT_FALSE(isless(P(-1.0), P(-2.0)));
+ EXPECT_TRUE(isless(P(0.25), P(0.5)));
+ EXPECT_FALSE(isless(P(0.25), P(0.25)));
+ EXPECT_FALSE(isless(P(0.5), P(0.25)));
+ EXPECT_TRUE(isless(P(-1), P(0)));
+ EXPECT_FALSE(isless(P(-1), P(-1)));
+ EXPECT_FALSE(isless(P(0), P(-1)));
+ EXPECT_FALSE(isless(P(0), P(0)));
+}
+
+TEST(classification, islessequal)
+{
+ using P = fpm::fixed<std::int32_t, std::int64_t, 12>;
+ EXPECT_TRUE(islessequal(P(1.0), P(2.0)));
+ EXPECT_TRUE(islessequal(P(1.0), P(1.0)));
+ EXPECT_FALSE(islessequal(P(2.0), P(1.0)));
+ EXPECT_TRUE(islessequal(P(-2.0), P(-1.0)));
+ EXPECT_TRUE(islessequal(P(-2.0), P(-2.0)));
+ EXPECT_FALSE(islessequal(P(-1.0), P(-2.0)));
+ EXPECT_TRUE(islessequal(P(0.25), P(0.5)));
+ EXPECT_TRUE(islessequal(P(0.25), P(0.25)));
+ EXPECT_FALSE(islessequal(P(0.5), P(0.25)));
+ EXPECT_TRUE(islessequal(P(-1), P(0)));
+ EXPECT_TRUE(islessequal(P(-1), P(-1)));
+ EXPECT_FALSE(islessequal(P(0), P(-1)));
+ EXPECT_TRUE(islessequal(P(0), P(0)));
+}
+
+TEST(classification, islessgreater)
+{
+ using P = fpm::fixed<std::int32_t, std::int64_t, 12>;
+ EXPECT_TRUE(islessgreater(P(1.0), P(2.0)));
+ EXPECT_FALSE(islessgreater(P(1.0), P(1.0)));
+ EXPECT_TRUE(islessgreater(P(2.0), P(1.0)));
+ EXPECT_TRUE(islessgreater(P(-2.0), P(-1.0)));
+ EXPECT_FALSE(islessgreater(P(-2.0), P(-2.0)));
+ EXPECT_TRUE(islessgreater(P(-1.0), P(-2.0)));
+ EXPECT_TRUE(islessgreater(P(0.25), P(0.5)));
+ EXPECT_FALSE(islessgreater(P(0.25), P(0.25)));
+ EXPECT_TRUE(islessgreater(P(0.5), P(0.25)));
+ EXPECT_TRUE(islessgreater(P(-1), P(0)));
+ EXPECT_FALSE(islessgreater(P(-1), P(-1)));
+ EXPECT_TRUE(islessgreater(P(0), P(-1)));
+ EXPECT_FALSE(islessgreater(P(0), P(0)));
+}
+
+TEST(classification, isunordered)
+{
+ using P = fpm::fixed<std::int32_t, std::int64_t, 12>;
+ EXPECT_FALSE(isunordered(P(1.0), P(2.0)));
+ EXPECT_FALSE(isunordered(P(1.0), P(1.0)));
+ EXPECT_FALSE(isunordered(P(2.0), P(1.0)));
+ EXPECT_FALSE(isunordered(P(-2.0), P(-1.0)));
+ EXPECT_FALSE(isunordered(P(-2.0), P(-2.0)));
+ EXPECT_FALSE(isunordered(P(-1.0), P(-2.0)));
+ EXPECT_FALSE(isunordered(P(0.25), P(0.5)));
+ EXPECT_FALSE(isunordered(P(0.25), P(0.25)));
+ EXPECT_FALSE(isunordered(P(0.5), P(0.25)));
+ EXPECT_FALSE(isunordered(P(-1), P(0)));
+ EXPECT_FALSE(isunordered(P(-1), P(-1)));
+ EXPECT_FALSE(isunordered(P(0), P(-1)));
+ EXPECT_FALSE(isunordered(P(0), P(0)));
+}
+
diff --git a/Client/ThirdParty/fpm/tests/common.hpp b/Client/ThirdParty/fpm/tests/common.hpp
new file mode 100644
index 0000000..d4dfe89
--- /dev/null
+++ b/Client/ThirdParty/fpm/tests/common.hpp
@@ -0,0 +1,31 @@
+#ifndef FPM_TESTS_COMMON_HPP
+#define FPM_TESTS_COMMON_HPP
+
+#include <fpm/fixed.hpp>
+#include <gtest/gtest.h>
+#include <iomanip>
+#include <ostream>
+
+namespace fpm
+{
+template <typename B, typename I, unsigned int F>
+void PrintTo(const fpm::fixed<B, I, F>& val, ::std::ostream* os)
+{
+ auto f = os->flags();
+ *os << static_cast<double>(val)
+ << " (0x" << std::hex << std::setw(sizeof(B) * 2) << std::setfill('0') << val.raw_value() << ")";
+ os->flags(f);
+}
+}
+
+inline ::testing::AssertionResult HasMaximumError(double value, double reference, double max_error)
+{
+ auto diff = std::abs(value - reference);
+ if (reference < 1e-10 && diff <= max_error)
+ return ::testing::AssertionSuccess();
+ if (std::abs(diff / reference) <= max_error)
+ return ::testing::AssertionSuccess();
+ return ::testing::AssertionFailure() << value << " is not within " << (max_error * 100) << "% of " << reference;
+}
+
+#endif \ No newline at end of file
diff --git a/Client/ThirdParty/fpm/tests/constants.cpp b/Client/ThirdParty/fpm/tests/constants.cpp
new file mode 100644
index 0000000..7db8d43
--- /dev/null
+++ b/Client/ThirdParty/fpm/tests/constants.cpp
@@ -0,0 +1,23 @@
+#include "common.hpp"
+
+using P = fpm::fixed_16_16;
+
+TEST(constants, e)
+{
+ EXPECT_EQ(P(2.7182818284590452353602874713527), P::e());
+}
+
+TEST(constants, pi)
+{
+ EXPECT_EQ(P(3.1415926535897932384626433832795), P::pi());
+}
+
+TEST(constants, half_pi)
+{
+ EXPECT_EQ(P(1.5707963267948966192313216916398), P::half_pi());
+}
+
+TEST(constants, two_pi)
+{
+ EXPECT_EQ(P(6.283185307179586476925286766559), P::two_pi());
+}
diff --git a/Client/ThirdParty/fpm/tests/conversion.cpp b/Client/ThirdParty/fpm/tests/conversion.cpp
new file mode 100644
index 0000000..0b70da5
--- /dev/null
+++ b/Client/ThirdParty/fpm/tests/conversion.cpp
@@ -0,0 +1,105 @@
+#include "common.hpp"
+#include <utility>
+
+using P = fpm::fixed_16_16;
+using Q = fpm::fixed_24_8;
+
+TEST(conversion, construction)
+{
+ P x;
+}
+
+TEST(conversion, copy)
+{
+ const P x(12);
+
+ // Copy ctor
+ P y(x);
+ EXPECT_EQ(P(12), y);
+
+ // Copy assignment
+ P z = x;
+ EXPECT_EQ(P(12), y);
+}
+
+TEST(conversion, move)
+{
+ const P x(12);
+
+ // Move ctor
+ P y(std::move(x));
+ EXPECT_EQ(P(12), y);
+
+ // Move assignment
+ P z = std::move(x);
+ EXPECT_EQ(P(12), y);
+}
+
+TEST(conversion, floats)
+{
+ EXPECT_EQ(1.125, static_cast<double>(P{1.125f}));
+ EXPECT_EQ(1.125, static_cast<double>(P{1.125}));
+}
+
+TEST(conversion, float_rounding)
+{
+ // Small number of fraction bits to test rounding
+ using Q = fpm::fixed<std::int32_t, std::int64_t, 2>;
+
+ EXPECT_EQ(1.25, static_cast<double>(Q{1.125}));
+ EXPECT_EQ(1.5, static_cast<double>(Q{1.375}));
+ EXPECT_EQ(-1.25, static_cast<double>(Q{-1.125}));
+ EXPECT_EQ(-1.5, static_cast<double>(Q{-1.375}));
+}
+
+TEST(conversion, ints)
+{
+ EXPECT_EQ(-125, static_cast<int>(P{-125}));
+ EXPECT_EQ(-125l, static_cast<long>(P{-125l}));
+ EXPECT_EQ(-125ll, static_cast<long long>(P{-125ll}));
+
+ EXPECT_EQ(125u, static_cast<unsigned int>(P{125u}));
+ EXPECT_EQ(125lu, static_cast<unsigned long>(P{125lu}));
+ EXPECT_EQ(125llu, static_cast<unsigned long long>(P{125llu}));
+}
+
+TEST(conversion, fixed_point)
+{
+ EXPECT_EQ(P(-1), P::from_fixed_point<0>(-1));
+ EXPECT_EQ(P(1), P::from_fixed_point<0>(1));
+
+ EXPECT_EQ(P(-1.125), P::from_fixed_point<4>(-18));
+ EXPECT_EQ(P(1.125), P::from_fixed_point<4>(18));
+
+ // This should round up to 1
+ EXPECT_EQ(P(-1), P::from_fixed_point<20>(-1048575));
+ EXPECT_EQ(P(1), P::from_fixed_point<20>(1048575));
+}
+
+TEST(conversion, fixed_to_fixed)
+{
+ EXPECT_EQ(Q(1), Q(P(1)));
+ EXPECT_EQ(Q(1), Q(P(1)));
+
+ // Conversion to fewer fraction bits should round
+ EXPECT_EQ(Q::from_raw_value(0x13), Q(P::from_raw_value(0x12ff)));
+ EXPECT_EQ(Q::from_raw_value(0x12), Q(P::from_raw_value(0x127f)));
+ EXPECT_EQ(Q::from_raw_value(-0x13), Q(P::from_raw_value(-0x12ff)));
+ EXPECT_EQ(Q::from_raw_value(-0x12), Q(P::from_raw_value(-0x127f)));
+
+ // Conversion to more fraction bits should zero-extend
+ EXPECT_EQ(P::from_raw_value(0x1200), P(Q::from_raw_value(0x12)));
+ EXPECT_EQ(P::from_raw_value(-0x1200), P(Q::from_raw_value(-0x12)));
+
+ {
+ // Assignment requires explicit conversion via construction
+ P p(1);
+ Q q = Q(p);
+ EXPECT_EQ(Q(1), q);
+ }
+
+ // Conversion to a smaller base type should truncate the upper bits
+ using S1 = fpm::fixed<std::int8_t, std::int16_t, 1>;
+ EXPECT_EQ(0x56, S1(P::from_raw_value(0x79AB1000)).raw_value());
+ EXPECT_EQ(-0x56, S1(P::from_raw_value(-0x79AB1000)).raw_value());
+}
diff --git a/Client/ThirdParty/fpm/tests/customizations.cpp b/Client/ThirdParty/fpm/tests/customizations.cpp
new file mode 100644
index 0000000..5a75d6a
--- /dev/null
+++ b/Client/ThirdParty/fpm/tests/customizations.cpp
@@ -0,0 +1,121 @@
+#include "common.hpp"
+
+template <typename T>
+class customizations : public ::testing::Test
+{
+};
+
+using FixedTypes = ::testing::Types<fpm::fixed_16_16, fpm::fixed_24_8, fpm::fixed_8_24>;
+
+TYPED_TEST_SUITE(customizations, FixedTypes);
+
+TYPED_TEST(customizations, swap)
+{
+ using std::swap;
+
+ using P = TypeParam;
+ P x{1}, y{2};
+ swap(x, y);
+ EXPECT_EQ(P{2}, x);
+ EXPECT_EQ(P{1}, y);
+}
+
+TYPED_TEST(customizations, hash)
+{
+ using P = TypeParam;
+
+ const std::hash<P> hash{};
+ for (int y = -50; y < 50; ++y)
+ {
+ EXPECT_EQ(hash(P{y}/10), hash(P{y}/10));
+ for (int x = -50; x < 50; ++x)
+ {
+ if (x != y)
+ {
+ EXPECT_NE(hash(P{x}/10), hash(P{y}/10));
+ }
+ }
+ }
+}
+
+template <typename T>
+struct Limits {};
+
+template <>
+struct Limits<fpm::fixed_16_16>
+{
+ static constexpr bool is_signed() noexcept { return true; }
+ static constexpr int digits() noexcept { return 31; }
+ static constexpr int max_digits10() noexcept { return 5+5; }
+ static constexpr int min_exponent() noexcept { return -15; }
+ static constexpr int max_exponent() noexcept { return 15; }
+ static constexpr int min_exponent10() noexcept { return -4; }
+ static constexpr int max_exponent10() noexcept { return 4; }
+ static constexpr fpm::fixed_16_16 min() noexcept { return fpm::fixed_16_16::from_raw_value(-2147483647 - 1); }
+ static constexpr fpm::fixed_16_16 max() noexcept { return fpm::fixed_16_16::from_raw_value( 2147483647); }
+};
+
+template <>
+struct Limits<fpm::fixed_24_8>
+{
+ static constexpr bool is_signed() noexcept { return true; }
+ static constexpr int digits() noexcept { return 31; }
+ static constexpr int max_digits10() noexcept { return 7+3; }
+ static constexpr int min_exponent() noexcept { return -7; }
+ static constexpr int max_exponent() noexcept { return 23; }
+ static constexpr int min_exponent10() noexcept { return -2; }
+ static constexpr int max_exponent10() noexcept { return 6; }
+ static constexpr fpm::fixed_24_8 min() noexcept { return fpm::fixed_24_8::from_raw_value(-2147483647 - 1); }
+ static constexpr fpm::fixed_24_8 max() noexcept { return fpm::fixed_24_8::from_raw_value( 2147483647); }
+};
+
+template <>
+struct Limits<fpm::fixed_8_24>
+{
+ static constexpr bool is_signed() noexcept { return true; }
+ static constexpr int digits() noexcept { return 31; }
+ static constexpr int max_digits10() noexcept { return 3+8; }
+ static constexpr int min_exponent() noexcept { return -23; }
+ static constexpr int max_exponent() noexcept { return 7; }
+ static constexpr int min_exponent10() noexcept { return -7; }
+ static constexpr int max_exponent10() noexcept { return 2; }
+ static constexpr fpm::fixed_8_24 min() noexcept { return fpm::fixed_8_24::from_raw_value(-2147483647 - 1); }
+ static constexpr fpm::fixed_8_24 max() noexcept { return fpm::fixed_8_24::from_raw_value( 2147483647); }
+};
+
+TYPED_TEST(customizations, numeric_limits)
+{
+ using L = std::numeric_limits<TypeParam>;
+ using TL = Limits<TypeParam>;
+
+ EXPECT_EQ(L::is_specialized, true);
+ EXPECT_EQ(L::is_signed, TL::is_signed());
+ EXPECT_EQ(L::is_integer, false);
+ EXPECT_EQ(L::is_exact, true);
+ EXPECT_EQ(L::has_infinity, false);
+ EXPECT_EQ(L::has_quiet_NaN, false);
+ EXPECT_EQ(L::has_signaling_NaN, false);
+ EXPECT_EQ(L::has_denorm, std::denorm_absent);
+ EXPECT_EQ(L::has_denorm_loss, false);
+ EXPECT_EQ(L::round_style, std::round_to_nearest);
+ EXPECT_EQ(L::is_iec_559, false);
+ EXPECT_EQ(L::is_bounded, true);
+ EXPECT_EQ(L::is_modulo, false);
+ EXPECT_EQ(L::digits, TL::digits());
+ EXPECT_EQ(L::digits10, 1);
+ EXPECT_EQ(L::max_digits10, TL::max_digits10());
+ EXPECT_EQ(L::radix, 2);
+ EXPECT_EQ(L::min_exponent, TL::min_exponent());
+ EXPECT_EQ(L::min_exponent10, TL::min_exponent10());
+ EXPECT_EQ(L::max_exponent, TL::max_exponent());
+ EXPECT_EQ(L::max_exponent10, TL::max_exponent10());
+ EXPECT_EQ(L::traps, true);
+ EXPECT_EQ(L::tinyness_before, false);
+
+ EXPECT_EQ(L::min(), TL::min());
+ EXPECT_EQ(L::lowest(), TL::min());
+ EXPECT_EQ(L::max(), TL::max());
+ EXPECT_EQ(L::epsilon(), TypeParam::from_raw_value(1));
+ EXPECT_EQ(L::round_error(), TypeParam(0.5));
+ EXPECT_EQ(L::denorm_min(), TL::min());
+}
diff --git a/Client/ThirdParty/fpm/tests/detail.cpp b/Client/ThirdParty/fpm/tests/detail.cpp
new file mode 100644
index 0000000..7045b04
--- /dev/null
+++ b/Client/ThirdParty/fpm/tests/detail.cpp
@@ -0,0 +1,13 @@
+#include "common.hpp"
+#include <fpm/math.hpp>
+
+TEST(detail, find_highest_bit)
+{
+ EXPECT_EQ(0, fpm::detail::find_highest_bit(1));
+ EXPECT_EQ(1, fpm::detail::find_highest_bit(2));
+ EXPECT_EQ(12, fpm::detail::find_highest_bit(0x1000));
+ EXPECT_EQ(23, fpm::detail::find_highest_bit(0x00FFFFFF));
+ EXPECT_EQ(23, fpm::detail::find_highest_bit(0x0087BD54));
+ EXPECT_EQ(28, fpm::detail::find_highest_bit(0x10000000));
+ EXPECT_EQ(31, fpm::detail::find_highest_bit(0x80000000));
+}
diff --git a/Client/ThirdParty/fpm/tests/input.cpp b/Client/ThirdParty/fpm/tests/input.cpp
new file mode 100644
index 0000000..16445db
--- /dev/null
+++ b/Client/ThirdParty/fpm/tests/input.cpp
@@ -0,0 +1,312 @@
+#include "common.hpp"
+#include <fpm/ios.hpp>
+#include <sstream>
+
+using ::testing::Combine;
+using ::testing::Values;
+
+namespace
+{
+ class fake_numpunct : public std::numpunct<char>
+ {
+ public:
+ fake_numpunct(char decimal_point, char thousands_sep, std::string grouping)
+ : m_decimal_point(decimal_point)
+ , m_thousands_sep(thousands_sep)
+ , m_grouping(grouping)
+ {}
+
+ protected:
+ char do_decimal_point() const override { return m_decimal_point; }
+ char do_thousands_sep() const override { return m_thousands_sep; }
+ std::string do_grouping() const override { return m_grouping; }
+ std::string do_truename() const override { return "unused"; }
+ std::string do_falsename() const override { return "unused"; }
+
+ private:
+ char m_decimal_point;
+ char m_thousands_sep;
+ std::string m_grouping;
+ };
+
+ class input : public ::testing::Test
+ {
+ protected:
+ input() : m_locale("C")
+ {
+ }
+
+ void setlocale(std::locale locale)
+ {
+ m_locale = locale;
+ }
+
+ template <typename B, typename I, unsigned int F>
+ void test_conversion(const std::string& text, fpm::fixed<B, I, F> expected, const std::string& expected_remaining = "")
+ {
+ std::istringstream ss(text);
+ ss.imbue(m_locale);
+
+ fpm::fixed<B, I, F> value;
+ ss >> value;
+
+ // Values should match
+ EXPECT_EQ(value, expected) << "for text: \"" << text << "\"";
+ EXPECT_TRUE(ss);
+
+ EXPECT_EQ(get_remainder(ss), expected_remaining) << "for text: \"" << text << "\"";
+ }
+
+ void test_invalid_conversion(const std::string& text, const std::string& expected_remaining = "")
+ {
+ std::istringstream ss(text);
+ ss.imbue(m_locale);
+
+ fpm::fixed_16_16 value;
+ ss >> value;
+
+ // Stream should have a parse error
+ EXPECT_TRUE(ss.fail()) << "for text: \"" << text << "\"";;
+ EXPECT_FALSE(ss.bad()) << "for text: \"" << text << "\"";;
+ EXPECT_FALSE(ss.good()) << "for text: \"" << text << "\"";;
+
+ EXPECT_EQ(get_remainder(ss), expected_remaining) << "for text: \"" << text << "\"";
+ }
+
+ private:
+ static std::string get_remainder(std::istream& is)
+ {
+ std::string remaining;
+ is.clear();
+ is >> std::noskipws >> remaining;
+ return remaining;
+ }
+
+ std::locale m_locale;
+ };
+}
+
+TEST_F(input, integers)
+{
+ using P = fpm::fixed_16_16;
+ test_conversion("0", P(0));
+ test_conversion("+0", P(0));
+ test_conversion("-0", P(0));
+
+ test_conversion("1", P(1));
+ test_conversion("+1", P(1));
+ test_conversion("-1", P(-1));
+
+ test_conversion("12795", P(12795));
+ test_conversion("+12795", P(12795));
+ test_conversion("-12795", P(-12795));
+}
+
+TEST_F(input, fixed_notation)
+{
+ using P = fpm::fixed_16_16;
+ test_conversion("0.0", P(0));
+ test_conversion("+0.0", P(0));
+ test_conversion("-0.0", P(0));
+
+ test_conversion("0.5", P(0.5));
+ test_conversion("+0.5", P(0.5));
+ test_conversion("-0.5", P(-0.5));
+
+ test_conversion(".5", P(0.5));
+ test_conversion("+.5", P(0.5));
+ test_conversion("-.5", P(-0.5));
+
+ test_conversion("3.", P(3));
+ test_conversion("+3.", P(3));
+ test_conversion("-3.", P(-3));
+
+ test_conversion("1467.0125", P(1467.0125));
+ test_conversion("+1467.0125", P(1467.0125));
+ test_conversion("-1467.0125", P(-1467.0125));
+}
+
+TEST_F(input, scientific_notation)
+{
+ using P = fpm::fixed_16_16;
+ test_conversion("0.e0", P(0));
+ test_conversion("+0.e0", P(0));
+ test_conversion("-0.e0", P(0));
+
+ test_conversion(".5e1", P(5));
+ test_conversion("+.5e1", P(5));
+ test_conversion("-.5e1", P(-5));
+
+ test_conversion("1.125e2", P(112.5));
+ test_conversion("+1.125e2", P(112.5));
+ test_conversion("-1.125e2", P(-112.5));
+
+ test_conversion("1.125E+2", P(112.5));
+ test_conversion("+1.125E+2", P(112.5));
+ test_conversion("-1.125E+2", P(-112.5));
+
+ test_conversion("9.765625e-4", P(0.0009765625));
+ test_conversion("-9.765625e-4", P(-0.0009765625));
+}
+
+TEST_F(input, hexfloat_notation)
+{
+ using P = fpm::fixed_16_16;
+ test_conversion("0x0", P(0));
+ test_conversion("+0x0", P(0));
+ test_conversion("-0x0", P(0));
+
+ test_conversion("0x1a", P(26));
+ test_conversion("+0x1a", P(26));
+ test_conversion("-0x1a", P(-26));
+
+ test_conversion("0xbcdP3", P(24168));
+ test_conversion("+0xbcdP3", P(24168));
+ test_conversion("-0xbcdP3", P(-24168));
+
+ test_conversion("0x1.bcdP-3", P(0.217193603515625));
+ test_conversion("+0x1.bcdP-3", P(0.217193603515625));
+ test_conversion("-0x1.bcdP-3", P(-0.217193603515625));
+}
+
+TEST_F(input, decimal_point)
+{
+ using P = fpm::fixed_16_16;
+
+ // Multiple decimal points stop parsing after the first one
+ test_conversion("1..5", P(1), ".5");
+
+ // Switch to a fake locale with a specific decimal separator
+ setlocale(std::locale(std::locale(""), new fake_numpunct('\'', ',', "\001\002")));
+ test_conversion("1\'234", P(1.234));
+}
+
+TEST_F(input, thousands_separator)
+{
+ using P = fpm::fixed_16_16;
+
+ // Switch to a fake locale with an english thousands separator
+ setlocale(std::locale(std::locale(""), new fake_numpunct('.', ',', "\003")));
+
+ // Thousands groupings before decimal point are ignored
+ test_conversion("12,345.67", P(12345.67));
+
+ // Thousands groupings after decimal point stop parsing
+ test_conversion("1.234,567", P(1.234), ",567");
+ test_conversion("1.25,56", P(1.25), ",56");
+ test_conversion("1.,125", P(1), ",125");
+
+ // Switch to a fake locale with a specific thousands separator
+ setlocale(std::locale(std::locale(""), new fake_numpunct('.', '\'', "\001\002")));
+ test_conversion("12\'345.67", P(12345.67));
+}
+
+TEST_F(input, fails_on_badbit)
+{
+ using P = fpm::fixed_16_16;
+
+ // operator>> is a FormattedInputFunction which is supposed to set failbit if badbit is set.
+ std::istringstream ss("12345");
+ ss.exceptions(std::ios::failbit);
+ ss.setstate(std::ios::badbit);
+ P x;
+ EXPECT_THROW(ss >> x, std::ios::failure);
+}
+
+TEST_F(input, handles_eof)
+{
+ using P = fpm::fixed_16_16;
+
+ // operator>> is a FormattedInputFunction which is supposed to handle EOF correctly
+ // (in this case, by throwing an exception)
+ std::istringstream ss("1");
+ ss.exceptions(std::ios::eofbit);
+ P x;
+ EXPECT_THROW(ss >> x, std::ios::failure);
+}
+
+TEST_F(input, skips_whitespace)
+{
+ using P = fpm::fixed_16_16;
+ test_conversion(" \t\r\n\v\f 1.125E+2", P(112.5));
+}
+
+TEST_F(input, ignored_remainder)
+{
+ using P = fpm::fixed_16_16;
+
+ // Hexadecimal exponent in decimal number
+ test_conversion("1.5p1", P(1.5), "p1");
+
+ // Hexadecimal char in decimal exponent
+ test_conversion("1e1a", P(10), "a");
+ test_conversion("1e1f", P(10), "f");
+
+ // Hexadecimal char in decimal number
+ test_conversion("1a", P(1), "a");
+ test_conversion("1f", P(1), "f");
+}
+
+TEST_F(input, incorrect_inputs)
+{
+ // Empty sequence
+ test_invalid_conversion("");
+ test_invalid_conversion("u1", "u1");
+
+ // Decimal point by itself
+ test_invalid_conversion(".");
+
+ // Missing exponent value
+ test_invalid_conversion("1e");
+ test_invalid_conversion("1ee1", "e1");
+ test_invalid_conversion("1ea", "a");
+ test_invalid_conversion("1ef", "f");
+ test_invalid_conversion("1e-e1", "e1");
+ test_invalid_conversion("1e--1", "-1");
+ test_invalid_conversion("1e++1", "+1");
+ test_invalid_conversion("1e+-1", "-1");
+ test_invalid_conversion("1e-+1", "+1");
+
+ // Missing significand
+ test_invalid_conversion("--1", "-1");
+ test_invalid_conversion("++1", "+1");
+ test_invalid_conversion("+-1", "-1");
+ test_invalid_conversion("-+1", "+1");
+}
+
+TEST_F(input, overflow)
+{
+ using P = fpm::fixed_16_16;
+
+ test_conversion("1e100", std::numeric_limits<P>::max());
+ test_conversion("-1e100", std::numeric_limits<P>::min());
+
+ test_conversion("100000", std::numeric_limits<P>::max());
+ test_conversion("-100000", std::numeric_limits<P>::min());
+
+ test_conversion("1000000000000000000000000000000000000000000000", std::numeric_limits<P>::max());
+ test_conversion("-1000000000000000000000000000000000000000000000", std::numeric_limits<P>::min());
+}
+
+TEST_F(input, infinity)
+{
+ using P = fpm::fixed_16_16;
+
+ test_conversion("inf", std::numeric_limits<P>::max());
+ test_conversion("infinity", std::numeric_limits<P>::max());
+
+ test_conversion("-inf", std::numeric_limits<P>::min());
+ test_conversion("-infinity", std::numeric_limits<P>::min());
+
+ test_invalid_conversion("infinit", "");
+ test_invalid_conversion("infini", "");
+ test_invalid_conversion("infin", "");
+ test_invalid_conversion("infi", "");
+ test_invalid_conversion("in", "");
+ test_invalid_conversion("i", "");
+
+ test_conversion("infa", std::numeric_limits<P>::max(), "a");
+ test_invalid_conversion("infinix", "x");
+ test_invalid_conversion("ib", "b");
+ test_invalid_conversion("-ic", "c");
+} \ No newline at end of file
diff --git a/Client/ThirdParty/fpm/tests/manip.cpp b/Client/ThirdParty/fpm/tests/manip.cpp
new file mode 100644
index 0000000..3e47640
--- /dev/null
+++ b/Client/ThirdParty/fpm/tests/manip.cpp
@@ -0,0 +1,64 @@
+#include "common.hpp"
+#include <fpm/math.hpp>
+
+TEST(manipulation, copysign)
+{
+ using P = fpm::fixed_24_8;
+
+ EXPECT_EQ(P(-13.125), copysign(P(-13.125), P(-7.25)));
+ EXPECT_EQ(P(-13.125), copysign(P( 13.125), P(-7.25)));
+ EXPECT_EQ(P( 13.125), copysign(P(-13.125), P( 7.25)));
+ EXPECT_EQ(P( 13.125), copysign(P( 13.125), P( 7.25)));
+
+ EXPECT_EQ(P(-13), copysign(P(-13), P(-7)));
+ EXPECT_EQ(P(-13), copysign(P( 13), P(-7)));
+ EXPECT_EQ(P( 13), copysign(P(-13), P( 7)));
+ EXPECT_EQ(P( 13), copysign(P( 13), P( 7)));
+}
+
+TEST(manipulation, nextafter)
+{
+ using P = fpm::fixed_16_16;
+
+ EXPECT_EQ(P(2.5), nextafter(P(2.5), P(2.5)));
+ EXPECT_EQ(P(-2.5), nextafter(P(-2.5), P(-2.5)));
+
+ EXPECT_EQ(P::from_raw_value(1), nextafter(P(0), std::numeric_limits<P>::max()));
+ EXPECT_EQ(P::from_raw_value(0x10001), nextafter(P(1), P(10)));
+ EXPECT_EQ(P::from_raw_value(-0x0ffff), nextafter(P(-1), P(10)));
+
+ EXPECT_EQ(P::from_raw_value(-1), nextafter(P(0), std::numeric_limits<P>::min()));
+ EXPECT_EQ(P::from_raw_value(0x0ffff), nextafter(P(1), P(-10)));
+ EXPECT_EQ(P::from_raw_value(-0x10001), nextafter(P(-1), P(-10)));
+}
+
+TEST(manipulation, nexttoward)
+{
+ using P = fpm::fixed_16_16;
+
+ EXPECT_EQ(P(2.5), nexttoward(P(2.5), P(2.5)));
+ EXPECT_EQ(P(-2.5), nexttoward(P(-2.5), P(-2.5)));
+
+ EXPECT_EQ(P::from_raw_value(1), nexttoward(P(0), std::numeric_limits<P>::max()));
+ EXPECT_EQ(P::from_raw_value(0x10001), nexttoward(P(1), P(10)));
+ EXPECT_EQ(P::from_raw_value(-0x0ffff), nexttoward(P(-1), P(10)));
+
+ EXPECT_EQ(P::from_raw_value(-1), nexttoward(P(0), std::numeric_limits<P>::min()));
+ EXPECT_EQ(P::from_raw_value(0x0ffff), nexttoward(P(1), P(-10)));
+ EXPECT_EQ(P::from_raw_value(-0x10001), nexttoward(P(-1), P(-10)));
+}
+
+TEST(manipulation, modf)
+{
+ using P = fpm::fixed_16_16;
+
+ P integral;
+ EXPECT_EQ(P(0), modf(P(0), &integral));
+ EXPECT_EQ(P(0), integral);
+
+ EXPECT_EQ(P(0.25), modf(P(12.25), &integral));
+ EXPECT_EQ(P(12), integral);
+
+ EXPECT_EQ(P(-0.25), modf(P(-12.25), &integral));
+ EXPECT_EQ(P(-12), integral);
+}
diff --git a/Client/ThirdParty/fpm/tests/nearest.cpp b/Client/ThirdParty/fpm/tests/nearest.cpp
new file mode 100644
index 0000000..342e0b1
--- /dev/null
+++ b/Client/ThirdParty/fpm/tests/nearest.cpp
@@ -0,0 +1,78 @@
+#include "common.hpp"
+#include <fpm/math.hpp>
+
+TEST(nearest, round)
+{
+ using P = fpm::fixed_24_8;
+
+ EXPECT_EQ(P( 2), round(P( 2.3)));
+ EXPECT_EQ(P( 3), round(P( 2.5)));
+ EXPECT_EQ(P( 3), round(P( 2.7)));
+ EXPECT_EQ(P(-2), round(P(-2.3)));
+ EXPECT_EQ(P(-3), round(P(-2.5)));
+ EXPECT_EQ(P(-3), round(P(-2.7)));
+ EXPECT_EQ(P( 0), round(P( 0)));
+}
+
+TEST(nearest, ceil)
+{
+ using P = fpm::fixed_24_8;
+
+ EXPECT_EQ(P( 1), ceil(P( 1)));
+ EXPECT_EQ(P(-1), ceil(P(-1)));
+
+ EXPECT_EQ(P( 3), ceil(P( 2.4)));
+ EXPECT_EQ(P(-2), ceil(P(-2.4)));
+ EXPECT_EQ(P( 0), ceil(P( 0)));
+}
+
+TEST(nearest, floor)
+{
+ using P = fpm::fixed_24_8;
+
+ EXPECT_EQ(P( 1), floor(P( 1)));
+ EXPECT_EQ(P(-1), floor(P(-1)));
+
+ EXPECT_EQ(P( 2), floor(P( 2.7)));
+ EXPECT_EQ(P(-3), floor(P(-2.7)));
+ EXPECT_EQ(P( 0), floor(P( 0)));
+}
+
+TEST(nearest, trunc)
+{
+ using P = fpm::fixed_24_8;
+
+ EXPECT_EQ(P( 1), trunc(P( 1)));
+ EXPECT_EQ(P(-1), trunc(P(-1)));
+
+ EXPECT_EQ(P( 2), trunc(P( 2.7)));
+ EXPECT_EQ(P(-2), trunc(P(-2.9)));
+ EXPECT_EQ(P( 0), trunc(P( 0)));
+}
+
+TEST(nearest, nearbyint)
+{
+ using P = fpm::fixed_24_8;
+
+ EXPECT_EQ(P( 2), nearbyint(P( 2.3)));
+ EXPECT_EQ(P( 2), nearbyint(P( 2.5)));
+ EXPECT_EQ(P( 4), nearbyint(P( 3.5)));
+ EXPECT_EQ(P(-2), nearbyint(P(-2.3)));
+ EXPECT_EQ(P(-2), nearbyint(P(-2.5)));
+ EXPECT_EQ(P(-4), nearbyint(P(-3.5)));
+ EXPECT_EQ(P( 0), nearbyint(P( 0)));
+}
+
+TEST(nearest, rint)
+{
+ using P = fpm::fixed_24_8;
+
+ EXPECT_EQ(P( 2), rint(P( 2.3)));
+ EXPECT_EQ(P( 2), rint(P( 2.5)));
+ EXPECT_EQ(P( 4), rint(P( 3.5)));
+ EXPECT_EQ(P(-2), rint(P(-2.3)));
+ EXPECT_EQ(P(-2), rint(P(-2.5)));
+ EXPECT_EQ(P(-4), rint(P(-3.5)));
+ EXPECT_EQ(P( 0), rint(P( 0)));
+}
+
diff --git a/Client/ThirdParty/fpm/tests/output.cpp b/Client/ThirdParty/fpm/tests/output.cpp
new file mode 100644
index 0000000..6fb54a0
--- /dev/null
+++ b/Client/ThirdParty/fpm/tests/output.cpp
@@ -0,0 +1,335 @@
+#include "common.hpp"
+#include <fpm/ios.hpp>
+#include <sstream>
+#include <tuple>
+#include <utility>
+#include <cfenv>
+
+using ::testing::Combine;
+using ::testing::Values;
+using fmtflags = ::std::ios::fmtflags;
+
+using Flags = ::testing::tuple<fmtflags, fmtflags, fmtflags, fmtflags, std::streamsize, int, char, std::locale>;
+
+namespace
+{
+ class fake_numpunct : public std::numpunct<char>
+ {
+ public:
+ fake_numpunct(char decimal_point, char thousands_sep, std::string grouping)
+ : m_decimal_point(decimal_point)
+ , m_thousands_sep(thousands_sep)
+ , m_grouping(grouping)
+ {}
+
+ protected:
+ char do_decimal_point() const override { return m_decimal_point; }
+ char do_thousands_sep() const override { return m_thousands_sep; }
+ std::string do_grouping() const override { return m_grouping; }
+ std::string do_truename() const override { return "unused"; }
+ std::string do_falsename() const override { return "unused"; }
+
+ private:
+ char m_decimal_point;
+ char m_thousands_sep;
+ std::string m_grouping;
+ };
+}
+
+class output : public ::testing::TestWithParam<Flags>
+{
+protected:
+ bool is_test_valid(const std::ios_base& stream, double value) const
+ {
+ const auto floatfield = stream.flags() & std::ios::floatfield;
+
+#if defined(__GLIBCXX__ )
+ // stdlibc++ seems to have a bug where it applies thousands grouping in hexadecimal mode,
+ // and---even worse---applies the grouping through the "0x" prefix. This produces
+ // interesting results such as "0,x1.8p+3" instead of "0x1.8p+3" with a grouping
+ // of "\002", or "0,x,1.8p+3" with a grouping of "\001".
+ const auto& numpunct = std::use_facet<std::numpunct<char>>(stream.getloc());
+ if (floatfield == (std::ios::fixed | std::ios::scientific) && !numpunct.grouping().empty())
+ {
+ return false;
+ }
+#elif defined(_MSC_VER)
+ // Microsoft Visual C++ has a few problems:
+ // It doesn't properly ignore the specified precision for hexfloat
+ if (floatfield == (std::ios::fixed | std::ios::scientific))
+ {
+ return false;
+ }
+ // A precision of zero isn't properly handled with a floatfield of "auto" or scientific
+ if (stream.precision() == 0 && (floatfield == 0 || floatfield == std::ios::scientific))
+ {
+ return false;
+ }
+ // Specifying "showpoint" adds a spurious "0" when the value is 0.
+ if (value == 0 && (stream.flags() & std::ios::showpoint) != 0)
+ {
+ return false;
+ }
+#endif
+
+ return true;
+ }
+
+ void test(double value) const
+ {
+ using P = fpm::fixed_16_16;
+
+ std::stringstream ss_fixed = create_stream();
+ std::stringstream ss_float = create_stream();
+
+ if (!is_test_valid(ss_float, value))
+ {
+ GTEST_SKIP() << "Skipping test due to invalid test combination";
+ return;
+ }
+
+ ss_fixed << P(value);
+ ss_float << value;
+
+ // Stream contents should match
+ EXPECT_EQ(ss_float.str(), ss_fixed.str()) << "for value: " << value;
+
+ // Stream properties should match afterwards
+ EXPECT_EQ(ss_float.flags(), ss_fixed.flags());
+ EXPECT_EQ(ss_float.precision(), ss_fixed.precision());
+ EXPECT_EQ(ss_float.width(), ss_fixed.width());
+ EXPECT_EQ(ss_float.fill(), ss_fixed.fill());
+ EXPECT_EQ(ss_float.getloc(), ss_fixed.getloc());
+ }
+
+private:
+ std::stringstream create_stream() const
+ {
+ std::stringstream ss;
+ ss.setf(std::get<0>(GetParam()) | std::get<1>(GetParam()) | std::get<2>(GetParam()) | std::get<3>(GetParam()));
+ ss.precision(std::get<4>(GetParam()));
+ ss.width(std::get<5>(GetParam()));
+ ss.fill(std::get<6>(GetParam()));
+ ss.imbue(std::get<7>(GetParam()));
+ return ss;
+ }
+};
+
+TEST_P(output, small_numbers)
+{
+ test(0.0);
+
+#if !defined(_MSC_VER)
+ // Microsoft Visual C++ isn't rounding these correctly. See
+ // the rounding tests for more information.
+ test(1.125);
+ test(-1.125);
+
+ test(0.125);
+ test(-0.125);
+
+ test(1.0 / 1024.0);
+ test(-1.0 / 1024.0);
+#endif
+}
+
+TEST_P(output, large_numbers)
+{
+ test(16782.0 + 1.0 / 1024.0);
+ test(-16782.0 - 1.0 / 1024.0);
+
+ test(100);
+ test(1000);
+ test(10000);
+}
+
+TEST_P(output, integers)
+{
+ test(0);
+ test(1);
+ test(-1);
+ test(2);
+ test(-2);
+ test(8);
+ test(-8);
+ test(10);
+ test(-10);
+ test(16);
+ test(-16);
+ test(1024);
+ test(-1024);
+}
+
+static const std::locale s_fake_locale(std::locale(""), new fake_numpunct(',', '.', "\001\002"));
+
+INSTANTIATE_TEST_SUITE_P(output_flags, output,
+ Combine(
+ Values(0, std::ios::left, std::ios::right, std::ios::internal),
+ Values(0, std::ios::scientific, std::ios::fixed, std::ios::scientific | std::ios::fixed),
+ Values(0, std::ios::showpoint),
+ Values(0, std::ios::uppercase),
+ Values(0, 1, 5, 29, 128), // Precision
+ Values(0, 1, 10, 2000), // Width
+ Values(' ', '*', '0'), // Fill
+ Values(std::locale("C"), std::locale(""), s_fake_locale) // Locale
+ )
+);
+
+using GroupingFlags = ::testing::tuple<fmtflags, std::string>;
+
+class output_grouping : public ::testing::TestWithParam<GroupingFlags>
+{
+protected:
+ void test(double value)
+ {
+ using P = fpm::fixed_24_8;
+ using std::get;
+
+ auto flags = get<0>(GetParam());
+ auto grouping = get<1>(GetParam());
+
+#if defined(_MSC_VER)
+ // Microsoft Visual C++ does not respect a grouping size < 0 (e.g. "\x200").
+ // It it supposed to represent "infinite group" but instead it reuses the last group size
+ if (grouping.find('\200') != std::string::npos) {
+ GTEST_SKIP();
+ return;
+ }
+#endif
+
+ std::locale locale(std::locale("C"), new fake_numpunct('.', ',', grouping));
+
+ std::stringstream ss_fixed, ss_float;
+ ss_fixed.imbue(locale);
+ ss_fixed.setf(flags);
+ ss_float.imbue(locale);
+ ss_float.setf(flags);
+ ss_fixed << P(value);
+ ss_float << value;
+
+ EXPECT_EQ(ss_float.str(), ss_fixed.str()) << "for value: " << value;
+ }
+};
+
+TEST_P(output_grouping, grouping)
+{
+ test(1);
+ test(12);
+ test(123);
+ test(1234);
+ test(12345);
+ test(123456);
+ test(1234567);
+}
+
+// Do not test hexfloat (fixed | scientific) due to stdlibc++ bug (see above)
+INSTANTIATE_TEST_SUITE_P(output_grouping, output_grouping,
+ Combine(
+ Values(0, std::ios::scientific, std::ios::fixed),
+ Values("\003", "\001\001", "\002\001\000", "\002\001\177", "\002\001\200")
+ )
+);
+
+class output_rounding : public ::testing::Test
+{
+protected:
+ void test(double value, int precision, std::ios::fmtflags flags = std::ios::fixed)
+ {
+ using P = fpm::fixed_16_16;
+
+#if defined(_MSC_VER)
+ // Microsoft Visual C++ has a bug where the floating point rounding mode (fesetround) of FE_TONEAREST
+ // is not respected in printing functions, instead rounding "away from zero".
+ GTEST_SKIP();
+ return;
+#endif
+
+ std::stringstream ss_fixed, ss_float;
+ ss_fixed << std::setiosflags(flags) << std::setprecision(precision) << P(value);
+ ss_float << std::setiosflags(flags) << std::setprecision(precision) << value;
+
+ EXPECT_EQ(ss_float.str(), ss_fixed.str()) << "for value: " << value;
+ }
+};
+
+TEST_F(output_rounding, increment_rounding)
+{
+ test(9.9, 0);
+ test(-9.9, 0);
+
+ test(9.99, 1);
+ test(-9.99, 1);
+
+ test(9.9995, 3);
+ test(-9.9995, 3);
+}
+
+TEST_F(output_rounding, tie_rounding)
+{
+ test(0.5, 0);
+ test(-0.5, 0);
+
+ test(0.5, 1);
+ test(-0.5, 1);
+
+ test(0.25, 1);
+ test(-0.25, 1);
+
+ test(0.125, 2);
+ test(-0.125, 2);
+
+ test(0.0009765625, 6);
+ test(-0.0009765625, 6);
+
+ test(0.0009765625, 7);
+ test(-0.0009765625, 7);
+}
+
+TEST_F(output_rounding, scientific_rounding)
+{
+ test(0.25, 0, std::ios::scientific);
+ test(-0.25, 0, std::ios::scientific);
+
+ test(0.5, 0, std::ios::scientific);
+ test(-0.5, 0, std::ios::scientific);
+
+ test(0.75, 0, std::ios::scientific);
+ test(-0.75, 0, std::ios::scientific);
+
+ test(0.015625, 0, std::ios::scientific);
+ test(0.015625, 1, std::ios::scientific);
+
+ test(0.0625, 0, std::ios::scientific);
+ test(0.0625, 1, std::ios::scientific);
+
+ test(0.09375, 0, std::ios::scientific);
+ test(0.09375, 1, std::ios::scientific);
+}
+
+class output_specific : public ::testing::Test
+{
+protected:
+ template <typename Fixed>
+ void test(const char* expected, Fixed value, int precision, std::ios::fmtflags flags = std::ios::fixed)
+ {
+ std::stringstream ss;
+ ss << std::setiosflags(flags) << std::setprecision(precision) << value;
+ EXPECT_EQ(expected, ss.str());
+ }
+};
+
+TEST_F(output_specific, type_limit)
+{
+ using F4 = fpm::fixed<std::int8_t, std::int16_t, 4>;
+ using F16 = fpm::fixed_16_16;
+
+ test("-32768.000", F16::from_raw_value(-2147483647 - 1), 3, std::ios::fixed);
+ test("-3.277e+04", F16::from_raw_value(-2147483647 - 1), 3, std::ios::scientific);
+ test("32768.000", F16::from_raw_value(2147483647), 3, std::ios::fixed);
+ test("3.277e+04", F16::from_raw_value(2147483647), 3, std::ios::scientific);
+
+ test("-8.000", F4::from_raw_value(-127 - 1), 3, std::ios::fixed);
+ test("-8.000e+00", F4::from_raw_value(-127 - 1), 3, std::ios::scientific);
+ test("7.938", F4::from_raw_value(127), 3, std::ios::fixed);
+ test("7.938e+00", F4::from_raw_value(127), 3, std::ios::scientific);
+}
diff --git a/Client/ThirdParty/fpm/tests/power.cpp b/Client/ThirdParty/fpm/tests/power.cpp
new file mode 100644
index 0000000..a38a838
--- /dev/null
+++ b/Client/ThirdParty/fpm/tests/power.cpp
@@ -0,0 +1,355 @@
+#include "common.hpp"
+#include <fpm/math.hpp>
+
+TEST(power, exp)
+{
+ // For several values, verify that fpm::exp is close to std::exp.
+ using P = fpm::fixed<std::int32_t, std::int64_t, 12>;
+
+ // Maximum relative error (percentage) we allow
+ constexpr auto MAX_ERROR_PERC = 0.02;
+
+ for (double value = -5; value <= 5; value += 0.1)
+ {
+ auto exp_real = std::exp(value);
+ auto exp_fixed = static_cast<double>(exp(P(value)));
+ EXPECT_TRUE(HasMaximumError(exp_fixed, exp_real, MAX_ERROR_PERC));
+ }
+}
+
+TEST(power, exp2)
+{
+ // For several values, verify that fpm::exp2 is close to std::exp2.
+ using P = fpm::fixed<std::int32_t, std::int64_t, 12>;
+
+ // Maximum relative error (percentage) we allow
+ constexpr auto MAX_ERROR_PERC = 0.01;
+
+ for (double value = -5; value <= 5; value += 0.1)
+ {
+ auto exp_real = std::exp2(value);
+ auto exp_fixed = static_cast<double>(exp2(P(value)));
+ EXPECT_TRUE(HasMaximumError(exp_fixed, exp_real, MAX_ERROR_PERC));
+ }
+}
+
+TEST(power, expm1)
+{
+ // For several values, verify that fpm::expm1 is close to std::expm1.
+ using P = fpm::fixed<std::int32_t, std::int64_t, 12>;
+
+ // Maximum relative error (percentage) we allow
+ constexpr auto MAX_ERROR_PERC = 0.02;
+
+ for (double value = -5; value <= 5; value += 0.1)
+ {
+ auto exp_real = std::expm1(value);
+ auto exp_fixed = static_cast<double>(expm1(P(value)));
+ EXPECT_TRUE(HasMaximumError(exp_fixed, exp_real, MAX_ERROR_PERC));
+ }
+}
+
+TEST(power, log)
+{
+ // For several values, verify that fpm::log is close to std::log.
+ using P = fpm::fixed<std::int32_t, std::int64_t, 12>;
+
+ // Maximum relative error (percentage) we allow
+ constexpr auto MAX_ERROR_PERC = 0.01;
+
+ // Small numbers
+ for (double value = 0.1; value <= 10; value += 0.1)
+ {
+ auto log_real = std::log(value);
+ auto log_fixed = static_cast<double>(log(P(value)));
+ EXPECT_TRUE(HasMaximumError(log_fixed, log_real, MAX_ERROR_PERC));
+ }
+
+ // Larger numbers, step by PI to get an irregular pattern
+ for (double value = 1; value <= 1000; value += 3.141593)
+ {
+ auto log_real = std::log(value);
+ auto log_fixed = static_cast<double>(log(P(value)));
+ EXPECT_TRUE(HasMaximumError(log_fixed, log_real, MAX_ERROR_PERC));
+ }
+
+#ifndef NDEBUG
+ EXPECT_DEATH(log(P(0)), "");
+ EXPECT_DEATH(log(P(-1)), "");
+#endif
+}
+
+TEST(power, log2)
+{
+ // For several values, verify that fpm::log2 is close to std::log2.
+ using P = fpm::fixed<std::int32_t, std::int64_t, 12>;
+
+ // Maximum relative error (percentage) we allow
+ constexpr auto MAX_ERROR_PERC = 0.01;
+
+ // Small numbers
+ for (double value = 0.1; value <= 10; value += 0.1)
+ {
+ auto log_real = std::log2(value);
+ auto log_fixed = static_cast<double>(log2(P(value)));
+ EXPECT_TRUE(HasMaximumError(log_fixed, log_real, MAX_ERROR_PERC));
+ }
+
+ // Larger numbers, step by PI to get an irregular pattern
+ for (double value = 1; value <= 1000; value += 3.141593)
+ {
+ auto log_real = std::log2(value);
+ auto log_fixed = static_cast<double>(log2(P(value)));
+ EXPECT_TRUE(HasMaximumError(log_fixed, log_real, MAX_ERROR_PERC));
+ }
+
+#ifndef NDEBUG
+ EXPECT_DEATH(log2(P(0)), "");
+ EXPECT_DEATH(log2(P(-1)), "");
+#endif
+}
+
+TEST(power, log10)
+{
+ // For several values, verify that fpm::log10 is close to std::log10exp.
+ using P = fpm::fixed<std::int32_t, std::int64_t, 12>;
+
+ // Maximum relative error (percentage) we allow
+ constexpr auto MAX_ERROR_PERC = 0.01;
+
+ // Small numbers
+ for (double value = 0.1; value <= 10; value += 0.1)
+ {
+ auto log_real = std::log10(value);
+ auto log_fixed = static_cast<double>(log10(P(value)));
+ EXPECT_TRUE(HasMaximumError(log_fixed, log_real, MAX_ERROR_PERC));
+ }
+
+ // Larger numbers, step by PI to get an irregular pattern
+ for (double value = 1; value <= 1000; value += 3.141593)
+ {
+ auto log_real = std::log10(value);
+ auto log_fixed = static_cast<double>(log10(P(value)));
+ EXPECT_TRUE(HasMaximumError(log_fixed, log_real, MAX_ERROR_PERC));
+ }
+
+#ifndef NDEBUG
+ EXPECT_DEATH(log10(P(0)), "");
+ EXPECT_DEATH(log10(P(-1)), "");
+#endif
+}
+
+TEST(power, log1p)
+{
+ // For several values, verify that fpm::log1p is close to std::log1p.
+ using P = fpm::fixed<std::int32_t, std::int64_t, 12>;
+
+ // Maximum relative error (percentage) we allow
+ constexpr auto MAX_ERROR_PERC = 0.01;
+
+ // Small numbers
+ for (double value = -0.9; value <= 10; value += 0.1)
+ {
+ auto log_real = std::log1p(value);
+ auto log_fixed = static_cast<double>(log1p(P(value)));
+ EXPECT_TRUE(HasMaximumError(log_fixed, log_real, MAX_ERROR_PERC));
+ }
+
+ // Larger numbers, step by PI to get an irregular pattern
+ for (double value = 0; value <= 1000; value += 3.141593)
+ {
+ auto log_real = std::log1p(value);
+ auto log_fixed = static_cast<double>(log1p(P(value)));
+ EXPECT_TRUE(HasMaximumError(log_fixed, log_real, MAX_ERROR_PERC));
+ }
+
+#ifndef NDEBUG
+ EXPECT_DEATH(log1p(P(-1)), "");
+ EXPECT_DEATH(log1p(P(-2)), "");
+#endif
+}
+
+TEST(power, pow)
+{
+ // For several combinations of x and y, verify that fpm::pow is close to std::pow.
+ using P = fpm::fixed<std::int32_t, std::int64_t, 12>;
+
+ // Maximum relative error (percentage) we allow
+ constexpr auto MAX_ERROR_PERC = 0.11;
+
+ // Small numbers
+ for (double base = 0.125; base <= 1; base += 0.125)
+ {
+ for (double exp = -1; exp <= 1; exp += 0.01)
+ {
+ auto pow_real = std::pow(base, exp);
+ auto pow_fixed = static_cast<double>(pow(P(base), P(exp)));
+ EXPECT_TRUE(HasMaximumError(pow_fixed, pow_real, MAX_ERROR_PERC));
+ }
+ }
+
+ // Larger numbers, step by PI to get an irregular pattern
+ for (double base = 1; base <= 40; base += 3.141593)
+ {
+ assert(P(base) != P(0));
+ for (double exp = -2; exp <= 2; exp += 0.1)
+ {
+ auto pow_real = std::pow(base, exp);
+ auto pow_fixed = static_cast<double>(pow(P(base), P(exp)));
+ EXPECT_TRUE(HasMaximumError(pow_fixed, pow_real, MAX_ERROR_PERC));
+ }
+ }
+
+ // Negative numbers
+ for (double base = -20; base < 0; base += 1.0/8)
+ {
+ // Whole exponents only
+ for (double exp = -3; exp < 0; exp++)
+ {
+ auto pow_real = std::pow(base, exp);
+ auto pow_fixed = static_cast<double>(pow(P(base), P(exp)));
+ EXPECT_TRUE(HasMaximumError(pow_fixed, pow_real, MAX_ERROR_PERC));
+ }
+ }
+
+ // Edge cases
+ EXPECT_EQ(P(1), pow(P(1), P(0)));
+ EXPECT_EQ(P(0), pow(P(0), P(1)));
+ EXPECT_EQ(P(1), pow(P(1), P(1)));
+#ifndef NDEBUG
+ EXPECT_DEATH(pow(P(0), P(0)), "");
+#endif
+}
+
+TEST(power, pow_int)
+{
+ // For several combinations of x and y, verify that fpm::pow is close to std::pow.
+ using P = fpm::fixed_16_16;
+
+ // Maximum relative error (percentage) we allow
+ constexpr auto MAX_ERROR_PERC = 0.01;
+
+ // Small numbers
+ for (double base = -1; base <= 1; base += 1.0/8)
+ {
+ if (base != 0)
+ {
+ for (int exp = -4; exp <= 4; exp++)
+ {
+ auto pow_real = std::pow(base, exp);
+ auto pow_fixed = static_cast<double>(pow(P(base), exp));
+ EXPECT_TRUE(HasMaximumError(pow_fixed, pow_real, MAX_ERROR_PERC));
+ }
+ }
+ }
+
+ // Larger numbers, step by PI to get an irregular pattern
+ for (double base = -40; base <= 40; base += 3.141593)
+ {
+ assert(P(base) != P(0));
+ for (int exp = -2; exp <= 2; exp++)
+ {
+ auto pow_real = std::pow(base, exp);
+ auto pow_fixed = static_cast<double>(pow(P(base), exp));
+ EXPECT_TRUE(HasMaximumError(pow_fixed, pow_real, MAX_ERROR_PERC));
+ }
+ }
+
+ // Edge cases
+ EXPECT_EQ(P(1), pow(P(1), 0));
+ EXPECT_EQ(P(0), pow(P(0), 1));
+ EXPECT_EQ(P(1), pow(P(1), 1));
+#ifndef NDEBUG
+ EXPECT_DEATH(pow(P(0), 0), "");
+#endif
+}
+
+TEST(power, sqrt)
+{
+ // For several values, verify that fpm::sqrt is close to std::sqrt.
+ using P = fpm::fixed_16_16;
+
+ // Maximum relative error (percentage) we allow
+ constexpr auto MAX_ERROR_PERC = 0.0003;
+
+ // Small numbers
+ for (double value = 0; value <= 100; value += 0.01)
+ {
+ auto sqrt_real = std::sqrt(value);
+ auto sqrt_fixed = static_cast<double>(sqrt(P(value)));
+ EXPECT_TRUE(HasMaximumError(sqrt_fixed, sqrt_real, MAX_ERROR_PERC));
+ }
+
+ // Larger numbers, step by PI/10 to get an irregular pattern
+ for (double value = 0; value <= 10000; value += 0.3141593)
+ {
+ auto sqrt_real = std::sqrt(value);
+ auto sqrt_fixed = static_cast<double>(sqrt(P(value)));
+ EXPECT_TRUE(HasMaximumError(sqrt_fixed, sqrt_real, MAX_ERROR_PERC));
+ }
+
+#ifndef NDEBUG
+ EXPECT_DEATH(sqrt(P(-1)), "");
+#endif
+}
+
+TEST(power, sqrt_24)
+{
+ // High-precision test of sqrt
+ using P = fpm::fixed_8_24;
+
+ // Maximum relative error (percentage) we allow
+ constexpr auto MAX_ERROR_PERC = 0.0000005;
+
+ // Small numbers
+ for (double value = 0; value <= 100; value += 0.01)
+ {
+ auto sqrt_real = std::sqrt(value);
+ auto sqrt_fixed = static_cast<double>(sqrt(P(value)));
+ EXPECT_TRUE(HasMaximumError(sqrt_fixed, sqrt_real, MAX_ERROR_PERC));
+ }
+
+}
+
+TEST(power, cbrt)
+{
+ // For several values, verify that fpm::cbrt is close to std::cbrt.
+ using P = fpm::fixed_16_16;
+
+ // Maximum relative error (percentage) we allow
+ constexpr auto MAX_ERROR_PERC = 0.00005;
+
+ // Small numbers
+ for (double value = -100; value <= 100; value += 0.125)
+ {
+ auto cbrt_real = std::cbrt(value);
+ auto cbrt_fixed = static_cast<double>(cbrt(P(value)));
+ EXPECT_TRUE(HasMaximumError(cbrt_fixed, cbrt_real, MAX_ERROR_PERC));
+ }
+
+ // Larger numbers, step by PI*10 to get an irregular pattern
+ for (double value = -10000; value <= 10000; value += 31.41593)
+ {
+ auto cbrt_real = std::cbrt(value);
+ auto cbrt_fixed = static_cast<double>(cbrt(P(value)));
+ EXPECT_TRUE(HasMaximumError(cbrt_fixed, cbrt_real, MAX_ERROR_PERC));
+ break;
+ }
+}
+
+TEST(power, cbrt_24)
+{
+ // High-precision test of cbrt
+ using P = fpm::fixed_8_24;
+
+ // Maximum relative error (percentage) we allow
+ constexpr auto MAX_ERROR_PERC = 0.0000005;
+
+ // Small numbers
+ for (double value = -100; value <= 100; value += 0.125)
+ {
+ auto cbrt_real = std::cbrt(value);
+ auto cbrt_fixed = static_cast<double>(cbrt(P(value)));
+ EXPECT_TRUE(HasMaximumError(cbrt_fixed, cbrt_real, MAX_ERROR_PERC));
+ }
+}
diff --git a/Client/ThirdParty/fpm/tests/trigonometry.cpp b/Client/ThirdParty/fpm/tests/trigonometry.cpp
new file mode 100644
index 0000000..19d8607
--- /dev/null
+++ b/Client/ThirdParty/fpm/tests/trigonometry.cpp
@@ -0,0 +1,160 @@
+#include "common.hpp"
+#include <fpm/math.hpp>
+
+TEST(trigonometry, sin)
+{
+ using P = fpm::fixed<std::int32_t, std::int64_t, 16>;
+ const double PI = std::acos(-1);
+
+ constexpr auto MAX_ERROR_PERC = 0.002;
+
+ for (int angle = -1799; angle <= 1800; ++angle)
+ {
+ auto flt_angle = angle * PI / 180;
+ auto sin_real = std::sin(flt_angle);
+ auto sin_fixed = static_cast<double>(sin(P(flt_angle)));
+ EXPECT_TRUE(HasMaximumError(sin_fixed, sin_real, MAX_ERROR_PERC));
+ }
+}
+
+TEST(trigonometry, cos)
+{
+ using P = fpm::fixed<std::int32_t, std::int64_t, 16>;
+ const double PI = std::acos(-1);
+
+ constexpr auto MAX_ERROR_PERC = 0.002;
+
+ for (int angle = -1799; angle <= 1800; ++angle)
+ {
+ auto flt_angle = angle * PI / 180;
+ auto cos_real = std::cos(flt_angle);
+ auto cos_fixed = static_cast<double>(cos(P(flt_angle)));
+ EXPECT_TRUE(HasMaximumError(cos_fixed, cos_real, MAX_ERROR_PERC));
+ }
+}
+
+TEST(trigonometry, tan)
+{
+ using P = fpm::fixed<std::int32_t, std::int64_t, 16>;
+ const double PI = std::acos(-1);
+
+ constexpr auto MAX_ERROR_PERC = 0.002;
+
+ for (int angle = -1799; angle <= 1800; ++angle)
+ {
+ // Tangent goes to infinite at 90 and -90 degrees.
+ // We can't represent that with fixed-point maths, so don't test for it.
+ if ((angle + 90) % 180 != 0)
+ {
+ auto flt_angle = angle * PI / 180;
+ auto tan_real = std::tan(flt_angle);
+ auto tan_fixed = static_cast<double>(tan(P(flt_angle)));
+ EXPECT_TRUE(HasMaximumError(tan_fixed, tan_real, MAX_ERROR_PERC));
+ }
+ }
+
+#ifndef NDEBUG
+ EXPECT_DEATH(tan(P::pi()/2), "");
+ EXPECT_DEATH(tan(-P::pi()/2), "");
+#endif
+}
+
+TEST(trigonometry, atan)
+{
+ using P = fpm::fixed<std::int32_t, std::int64_t, 12>;
+
+ constexpr auto MAX_ERROR_PERC = 0.025;
+
+ for (int x = -1000; x <= 1000; ++x)
+ {
+ auto value = x / 10.0;
+ auto atan_real = std::atan(value);
+ auto atan_fixed = static_cast<double>(atan(P(value)));
+ EXPECT_TRUE(HasMaximumError(atan_fixed, atan_real, MAX_ERROR_PERC));
+ }
+
+ for (int x = -1000; x <= 1000; ++x)
+ {
+ auto value = x / 1000.0;
+ auto atan_real = std::atan(value);
+ auto atan_fixed = static_cast<double>(atan(P(value)));
+ EXPECT_TRUE(HasMaximumError(atan_fixed, atan_real, MAX_ERROR_PERC));
+ }
+}
+
+TEST(trigonometry, asin)
+{
+ using P = fpm::fixed<std::int32_t, std::int64_t, 12>;
+
+ constexpr auto MAX_ERROR_PERC = 0.025;
+
+ for (int x = -1000; x <= 1000; ++x)
+ {
+ auto value = x / 1000.0;
+ auto asin_real = std::asin(value);
+ auto asin_fixed = static_cast<double>(asin(P(value)));
+ EXPECT_TRUE(HasMaximumError(asin_fixed, asin_real, MAX_ERROR_PERC));
+ }
+}
+
+TEST(trigonometry, acos)
+{
+ using P = fpm::fixed<std::int32_t, std::int64_t, 12>;
+
+ constexpr auto MAX_ERROR_PERC = 0.025;
+
+ for (int x = -1000; x <= 1000; ++x)
+ {
+ auto value = x / 1000.0;
+ auto acos_real = std::acos(value);
+ auto acos_fixed = static_cast<double>(acos(P(value)));
+ EXPECT_TRUE(HasMaximumError(acos_fixed, acos_real, MAX_ERROR_PERC));
+ }
+}
+
+TEST(trigonometry, atan2)
+{
+ using P = fpm::fixed<std::int32_t, std::int64_t, 12>;
+ const double PI = std::acos(-1);
+
+ constexpr auto MAX_ERROR_PERC = 0.025;
+
+ for (int angle = -1799; angle <= 1800; ++angle)
+ {
+ const auto y = std::sin(angle * PI / 1800);
+ const auto x = std::cos(angle * PI / 1800);
+
+ auto atan2_real = std::atan2(y, x);
+ auto atan2_fixed = static_cast<double>(atan2(P(y), P(x)));
+ EXPECT_TRUE(HasMaximumError(atan2_fixed, atan2_real, MAX_ERROR_PERC));
+ }
+
+#ifndef NDEBUG
+ EXPECT_DEATH(atan2(P(0), P(0)), "");
+#endif
+}
+
+// Naively, atan2(y, x) does y / x which would overflow for near-zero x with Q16.16.
+// Test that we've got protections in place for this.
+TEST(trigonometry, atan2_near_zero)
+{
+ constexpr auto MAX_ERROR_PERC = 0.025;
+ using P = fpm::fixed_16_16;
+
+ const auto x = P::from_raw_value(1);
+ const auto y = P(100);
+
+ // Positive x
+ {
+ auto atan2_real = std::atan2(static_cast<double>(y), static_cast<double>(x));
+ auto atan2_fixed = static_cast<double>(atan2(y, x));
+ EXPECT_TRUE(HasMaximumError(atan2_fixed, atan2_real, MAX_ERROR_PERC));
+ }
+
+ // Negative x
+ {
+ auto atan2_real = std::atan2(static_cast<double>(y), static_cast<double>(-x));
+ auto atan2_fixed = static_cast<double>(atan2(y, -x));
+ EXPECT_TRUE(HasMaximumError(atan2_fixed, atan2_real, MAX_ERROR_PERC));
+ }
+}