diff options
Diffstat (limited to 'Client/ThirdParty/fpm/tests')
-rw-r--r-- | Client/ThirdParty/fpm/tests/arithmetic.cpp | 53 | ||||
-rw-r--r-- | Client/ThirdParty/fpm/tests/arithmetic_int.cpp | 45 | ||||
-rw-r--r-- | Client/ThirdParty/fpm/tests/basic_math.cpp | 106 | ||||
-rw-r--r-- | Client/ThirdParty/fpm/tests/classification.cpp | 165 | ||||
-rw-r--r-- | Client/ThirdParty/fpm/tests/common.hpp | 31 | ||||
-rw-r--r-- | Client/ThirdParty/fpm/tests/constants.cpp | 23 | ||||
-rw-r--r-- | Client/ThirdParty/fpm/tests/conversion.cpp | 105 | ||||
-rw-r--r-- | Client/ThirdParty/fpm/tests/customizations.cpp | 121 | ||||
-rw-r--r-- | Client/ThirdParty/fpm/tests/detail.cpp | 13 | ||||
-rw-r--r-- | Client/ThirdParty/fpm/tests/input.cpp | 312 | ||||
-rw-r--r-- | Client/ThirdParty/fpm/tests/manip.cpp | 64 | ||||
-rw-r--r-- | Client/ThirdParty/fpm/tests/nearest.cpp | 78 | ||||
-rw-r--r-- | Client/ThirdParty/fpm/tests/output.cpp | 335 | ||||
-rw-r--r-- | Client/ThirdParty/fpm/tests/power.cpp | 355 | ||||
-rw-r--r-- | Client/ThirdParty/fpm/tests/trigonometry.cpp | 160 |
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)); + } +} |