diff --git a/include_cpp17/fea/numerics/fixed.hpp b/include_cpp17/fea/numerics/fixed.hpp new file mode 100644 index 00000000..7383333c --- /dev/null +++ b/include_cpp17/fea/numerics/fixed.hpp @@ -0,0 +1,216 @@ +/* +BSD 3-Clause License + +Copyright (c) 2024, Philippe Groarke +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#pragma once +#include "fea/utils/platform.hpp" + +#include +#include +#include +#include +#include + +/* +A basic_fixed precision Real number type. + +Minimizes precision issues and accelerates performance by using integer +instructions. + +The default fea::basic_fixed type uses 23 decimal bits, which has the same +precision (epsilon) as float32. + +You may provide 2^n bits Scaling values, or more appropriate scaling values. For +example, use a scaling of 100 when processing currency (2 decimal places). + +Tip : https://en.wikipedia.org/wiki/Fixed-point_arithmetic +Fixed-point formats with scaling factors of the form 2n-1 (namely 1, 3, 7, 15, +31, etc.) have been said to be appropriate for image processing and other +digital signal processing tasks. +*/ + +namespace fea { +namespace detail { +// Returns true if integer value is a power of 2. +// https://graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2 +// +// TODO : Put where? Add unit tests. +template +constexpr bool mis_pow2(T v) noexcept { + static_assert(std::is_integral_v, "fea::mis_pow2 : T must be integer."); + return v && !(v & (v - 1)); +} + +template +constexpr T msqrt_base2(T v) noexcept { + static_assert( + std::is_integral_v, "fea::msqrt_base2 : T must be integer."); + + T mask = T(1); + for (size_t i = 0; i < sizeof(T) * 8; ++i) { + if (v & (mask << i)) { + return T(i); + } + } + return T(0); +} +} // namespace detail + +template +struct basic_fixed { + static_assert(std::is_integral_v, + "fea::basic_fixed : IntT must be integral type."); + + using value_t = IntT; + static constexpr value_t scaling_v = value_t(Scaling); + + // Enable some bit-shifting optimizations if scaling is 2^n. + static constexpr bool scaling_is_pow2_v = detail::mis_pow2(scaling_v); + static constexpr value_t scaling_sqrt_v + = scaling_is_pow2_v ? detail::msqrt_base2(scaling_v) : value_t(0); + +private: + static constexpr float _float_to_int_v = float(scaling_v); + static constexpr float _int_to_float_v = 1.f / float(scaling_v); + static constexpr double _double_to_int_v = double(scaling_v); + static constexpr double _int_to_double_v = 1.0 / double(scaling_v); + +public: + // Ctors + constexpr basic_fixed() noexcept = default; + ~basic_fixed() noexcept = default; + constexpr basic_fixed(const basic_fixed&) noexcept = default; + constexpr basic_fixed(basic_fixed&&) noexcept = default; + constexpr basic_fixed& operator=(const basic_fixed&) noexcept = default; + constexpr basic_fixed& operator=(basic_fixed&&) noexcept = default; + + constexpr basic_fixed(float f) noexcept; + constexpr basic_fixed(double d) noexcept; + explicit constexpr basic_fixed(value_t v) noexcept; + + // Conversions + explicit constexpr operator float() const noexcept; + explicit constexpr operator double() const noexcept; + explicit constexpr operator value_t() const noexcept; + + // Comparison Operators + friend constexpr bool operator==( + basic_fixed lhs, basic_fixed rhs) noexcept { + return lhs.value == rhs.value; + } + + friend constexpr bool operator!=( + basic_fixed lhs, basic_fixed rhs) noexcept { + return !(lhs == rhs); + } + + friend constexpr bool operator<(basic_fixed lhs, basic_fixed rhs) noexcept { + return lhs.value < rhs.value; + } + + friend constexpr bool operator>(basic_fixed lhs, basic_fixed rhs) noexcept { + return rhs < lhs; + } + + friend constexpr bool operator<=( + basic_fixed lhs, basic_fixed rhs) noexcept { + return !(rhs < lhs); + } + + friend constexpr bool operator>=( + basic_fixed lhs, basic_fixed rhs) noexcept { + return !(lhs < rhs); + } + + // Arithmetic Operators + friend constexpr basic_fixed operator+( + basic_fixed lhs, basic_fixed rhs) noexcept { + basic_fixed ret; + ret.value = lhs.value + rhs.value; + return ret; + } + + friend constexpr basic_fixed operator-( + basic_fixed lhs, basic_fixed rhs) noexcept { + basic_fixed ret; + ret.value = lhs.value - rhs.value; + return ret; + } + + friend constexpr basic_fixed operator*( + basic_fixed lhs, basic_fixed rhs) noexcept { + basic_fixed ret; + if constexpr (scaling_is_pow2_v) { + ret.value = (lhs.value * rhs.value) >> scaling_sqrt_v; + } else { + ret.value = (lhs.value * rhs.value) / scaling_v; + } + return ret; + } + + friend constexpr basic_fixed operator/( + basic_fixed lhs, basic_fixed rhs) noexcept { + basic_fixed ret; + if constexpr (scaling_is_pow2_v) { + ret.value = (lhs.value << scaling_sqrt_v) / rhs.value; + } else { + ret.value = (lhs.value * scaling_v) / rhs.value; + } + return ret; + } + + friend constexpr basic_fixed operator%( + basic_fixed lhs, basic_fixed rhs) noexcept { + basic_fixed ret; + ret.value = lhs.value % rhs.value; + return ret; + } + + // Public for serialization purposes. + value_t value = value_t(0); +}; + + +#if FEA_ARCH == 64 +// float32 decimal precision. +using fixed = fea::basic_fixed; +#elif FEA_ARCH == 32 +// A bad idea, provided for completeness. +using fixed = fea::basic_fixed; +#else +static_assert(false, "fea::fixed : Missing architecture."); +#endif + +// std::intmax_t == 8 bytes on win32... +using currency = fea::basic_fixed; + +} // namespace fea + +#include "imp/fixed.imp.hpp" diff --git a/include_cpp17/fea/numerics/imp/fixed.imp.hpp b/include_cpp17/fea/numerics/imp/fixed.imp.hpp new file mode 100644 index 00000000..c18763a4 --- /dev/null +++ b/include_cpp17/fea/numerics/imp/fixed.imp.hpp @@ -0,0 +1,132 @@ +namespace fea { +template +constexpr basic_fixed::basic_fixed(float f) noexcept + : value(value_t(f * _float_to_int_v)) { + // Keep it simple and fast for now. + // Could do modf for more fractional precision. +} + +template +constexpr basic_fixed::basic_fixed(double d) noexcept + : value(value_t(d * _double_to_int_v)) { +} + +template +constexpr auto mcexpr_if(TrueT lhs, FalseT rhs) { + if constexpr (B) { + return lhs; + } else { + return rhs; + } +} + +template +constexpr basic_fixed::basic_fixed(value_t v) noexcept { + if constexpr (scaling_is_pow2_v) { + value = v << scaling_sqrt_v; + } else { + value = v * scaling_v; + } +} + +template +constexpr basic_fixed::operator float() const noexcept { + return float(value) * _int_to_float_v; +} + +template +constexpr basic_fixed::operator double() const noexcept { + return double(value) * _int_to_double_v; +} + +template +constexpr basic_fixed::operator value_t() const noexcept { + if constexpr (scaling_is_pow2_v) { + return value >> scaling_sqrt_v; + } else { + return value / scaling_v; + } +} +} // namespace fea + +namespace std { +template +class numeric_limits> { + using value_t = fea::basic_fixed; + +public: + // Member constants + static constexpr bool is_specialized = true; + static constexpr bool is_signed = std::is_signed_v; + static constexpr bool is_integer = false; + static constexpr bool is_exact = true; + static constexpr bool has_infinity = false; + static constexpr bool has_quiet_NaN = false; + static constexpr bool has_signaling_NaN = false; + static constexpr std::float_denorm_style has_denorm = std::denorm_absent; + static constexpr bool has_denorm_loss = false; + static constexpr std::float_round_style round_style + = std::round_toward_zero; + static constexpr bool is_iec559 = false; + static constexpr bool is_bounded = true; + static constexpr bool is_modulo = std::numeric_limits::is_modulo; + + static constexpr int digits = std::numeric_limits::digits; + static constexpr int digits10 = std::numeric_limits::digits10; + static constexpr int max_digits10 = std::numeric_limits::max_digits10; + static constexpr int radix = 2; + + static constexpr int min_exponent = 0; + static constexpr int min_exponent10 = 0; + static constexpr int max_exponent = 0; + static constexpr int max_exponent10 = 0; + static constexpr bool traps = std::numeric_limits::traps; + static constexpr bool tinyness_before = false; + + // Member functions + static constexpr value_t min() noexcept { + // Even though it isn't necessary, since we can perfectly represent + // zero. Return value closest to zero (0), to allow interchange with + // floating types. + value_t ret; + ret.value = T(0); + // ret.value = (std::numeric_limits::min)(); + return ret; + } + + static constexpr value_t lowest() noexcept { + value_t ret; + ret.value = std::numeric_limits::lowest(); + return ret; + } + + static constexpr value_t max() noexcept { + value_t ret; + ret.value = (std::numeric_limits::max)(); + return ret; + } + + static constexpr value_t epsilon() noexcept { + value_t ret; + ret.value = T(1); + return ret; + } + + static constexpr value_t round_error() noexcept { + return value_t(0.5); + } + + static constexpr value_t infinity() noexcept { + return value_t(T(0)); + } + static constexpr value_t quiet_NaN() noexcept { + return value_t(T(0)); + } + static constexpr value_t signaling_NaN() noexcept { + return value_t(T(0)); + } + static constexpr value_t denorm_min() noexcept { + return value_t(T(0)); + } +}; +} // namespace std diff --git a/include_cpp17/fea/numerics/random.hpp b/include_cpp17/fea/numerics/random.hpp index 51a2aab4..937bcee5 100644 --- a/include_cpp17/fea/numerics/random.hpp +++ b/include_cpp17/fea/numerics/random.hpp @@ -1,4 +1,35 @@ -#pragma once +/* +BSD 3-Clause License + +Copyright (c) 2024, Philippe Groarke +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#pragma once #include "fea/utils/platform.hpp" #include diff --git a/include_cpp17/fea/performance/intrinsics.hpp b/include_cpp17/fea/performance/intrinsics.hpp index 886358f6..57953767 100644 --- a/include_cpp17/fea/performance/intrinsics.hpp +++ b/include_cpp17/fea/performance/intrinsics.hpp @@ -117,6 +117,7 @@ constexpr auto to_unsigned(T t) noexcept { return uint64_t(t); } else { // someday... + assert(false); return uint64_t(t); } } diff --git a/include_cpp17/fea/utils/platform.hpp b/include_cpp17/fea/utils/platform.hpp index 9d7e8b87..60e41b0e 100644 --- a/include_cpp17/fea/utils/platform.hpp +++ b/include_cpp17/fea/utils/platform.hpp @@ -88,24 +88,58 @@ namespace fea { */ // Are we building in 32 bits or 64 bits? #undef FEA_ARCH +#undef FEA_8BIT +#undef FEA_16BIT #undef FEA_32BIT #undef FEA_64BIT +#undef FEA_128BIT +#undef FEA_256BIT #define FEA_ARCH 0 +#define FEA_8BIT 0 +#define FEA_16BIT 0 #define FEA_32BIT 0 #define FEA_64BIT 0 +#define FEA_128BIT 0 +#define FEA_256BIT 0 -#if INTPTR_MAX == INT32_MAX +#if INTPTR_MAX == INT8_MAX +#undef FEA_ARCH +#undef FEA_8BIT +#define FEA_ARCH 8 +#define FEA_8BIT 1 +inline constexpr size_t arch = 8u; +#elif INTPTR_MAX == INT16_MAX +#undef FEA_ARCH +#undef FEA_16BIT +#define FEA_ARCH 16 +#define FEA_16BIT 1 +inline constexpr size_t arch = 16u; +#elif INTPTR_MAX == INT32_MAX #undef FEA_ARCH #undef FEA_32BIT #define FEA_ARCH 32 #define FEA_32BIT 1 inline constexpr size_t arch = 32u; -#else +#elif INTPTR_MAX == INT64_MAX #undef FEA_ARCH #undef FEA_64BIT #define FEA_ARCH 64 #define FEA_64BIT 1 inline constexpr size_t arch = 64u; +// #elif INTPTR_MAX == INT128_MAX +// #undef FEA_ARCH +// #undef FEA_128BIT +// #define FEA_ARCH 128 +// #define FEA_128BIT 1 +// inline constexpr size_t arch = 128u; +// #elif INTPTR_MAX == INT256_MAX +// #undef FEA_ARCH +// #undef FEA_256BIT +// #define FEA_ARCH 256 +// #define FEA_256BIT 1 +// inline constexpr size_t arch = 256u; +#else +static_assert(false, "fea::arch : Missing architecture."); #endif // Disables exceptions in classes that support it. diff --git a/tests_cpp17/numerics/fixed.cpp b/tests_cpp17/numerics/fixed.cpp new file mode 100644 index 00000000..d6868c9c --- /dev/null +++ b/tests_cpp17/numerics/fixed.cpp @@ -0,0 +1,547 @@ +#include +#include + +namespace { +#define FAIL_MSG "fixed.cpp : failed test" + +using mint_t = typename fea::fixed::value_t; + +TEST(fixed, basics) { + // ctors and simple conversions + { + const double val = 42.0; + fea::fixed t = val; + t = val; + EXPECT_EQ(double(t), val); + + t = 1.0; + EXPECT_EQ(double(t), 1.0); + + t = fea::fixed(mint_t(1)); + EXPECT_EQ(mint_t(t), 1); + + t = 1.f; + EXPECT_EQ(float(t), 1.f); + + fea::fixed cpy = t; + EXPECT_EQ(float(t), 1.f); + EXPECT_EQ(double(t), 1.0); + EXPECT_EQ(mint_t(t), 1); + + cpy = std::move(t); + EXPECT_EQ(float(t), 1.f); + EXPECT_EQ(double(t), 1.0); + EXPECT_EQ(mint_t(t), 1); + t = cpy; + + cpy = fea::fixed(t); + EXPECT_EQ(float(t), 1.f); + EXPECT_EQ(double(t), 1.0); + EXPECT_EQ(mint_t(t), 1); + + cpy = fea::fixed(std::move(t)); + EXPECT_EQ(float(t), 1.f); + EXPECT_EQ(double(t), 1.0); + EXPECT_EQ(mint_t(t), 1); + t = cpy; + } + + // Comparison operators. + { + fea::fixed f1 = 2.0; + fea::fixed f2 = 2.0; + EXPECT_TRUE(f1 == f2); + EXPECT_FALSE(f1 != f2); + EXPECT_FALSE(f1 < f2); + EXPECT_FALSE(f1 > f2); + EXPECT_TRUE(f1 <= f2); + EXPECT_TRUE(f1 >= f2); + + f1 = 0.0; + f2 = 2.0; + EXPECT_FALSE(f1 == f2); + EXPECT_TRUE(f1 != f2); + EXPECT_TRUE(f1 < f2); + EXPECT_FALSE(f1 > f2); + EXPECT_TRUE(f1 <= f2); + EXPECT_FALSE(f1 >= f2); + + f1 = 2.0; + f2 = 0.0; + EXPECT_FALSE(f1 == f2); + EXPECT_TRUE(f1 != f2); + EXPECT_FALSE(f1 < f2); + EXPECT_TRUE(f1 > f2); + EXPECT_FALSE(f1 <= f2); + EXPECT_TRUE(f1 >= f2); + + fea::currency c1 = 2.0; + fea::currency c2 = 2.0; + EXPECT_TRUE(c1 == c2); + EXPECT_FALSE(c1 != c2); + EXPECT_FALSE(c1 < c2); + EXPECT_FALSE(c1 > c2); + EXPECT_TRUE(c1 <= c2); + EXPECT_TRUE(c1 >= c2); + + c1 = 0.0; + c2 = 2.0; + EXPECT_FALSE(c1 == c2); + EXPECT_TRUE(c1 != c2); + EXPECT_TRUE(c1 < c2); + EXPECT_FALSE(c1 > c2); + EXPECT_TRUE(c1 <= c2); + EXPECT_FALSE(c1 >= c2); + + c1 = 2.0; + c2 = 0.0; + EXPECT_FALSE(c1 == c2); + EXPECT_TRUE(c1 != c2); + EXPECT_FALSE(c1 < c2); + EXPECT_TRUE(c1 > c2); + EXPECT_FALSE(c1 <= c2); + EXPECT_TRUE(c1 >= c2); + } + + // Math operators. + { + fea::fixed f1 = 2.0; + fea::fixed f2 = 2.0; + fea::fixed ans = f1 + f2; + EXPECT_EQ(ans, fea::fixed(4.0)); + + ans = f1 - f2; + EXPECT_EQ(ans, fea::fixed(0.0)); + + ans = f1 * f2; + EXPECT_EQ(ans, fea::fixed(4.0)); + + ans = f1 / f2; + EXPECT_EQ(ans, fea::fixed(1.0)); + + ans = f1 % f2; + EXPECT_EQ(ans, fea::fixed(0.0)); + + f1 = 8.0; + f2 = 2.0; + ans = f1 + f2; + EXPECT_EQ(ans, fea::fixed(10.0)); + + ans = f1 - f2; + EXPECT_EQ(ans, fea::fixed(6.0)); + + ans = f1 * f2; + EXPECT_EQ(ans, fea::fixed(16.0)); + + ans = f1 / f2; + EXPECT_EQ(ans, fea::fixed(4.0)); + + ans = f1 % f2; + EXPECT_EQ(ans, fea::fixed(0.0)); + + f1 = 2.0; + f2 = 8.0; + ans = f1 + f2; + EXPECT_EQ(ans, fea::fixed(10.0)); + + ans = f1 - f2; + EXPECT_EQ(ans, fea::fixed(-6.0)); + + ans = f1 * f2; + EXPECT_EQ(ans, fea::fixed(16.0)); + + ans = f1 / f2; + EXPECT_EQ(ans, fea::fixed(0.25)); + + ans = f1 % f2; + EXPECT_EQ(ans, fea::fixed(2.0)); + + f1 = -2.0; + f2 = 8.0; + ans = f1 + f2; + EXPECT_EQ(ans, fea::fixed(6.0)); + + ans = f1 - f2; + EXPECT_EQ(ans, fea::fixed(-10.0)); + + ans = f1 * f2; + EXPECT_EQ(ans, fea::fixed(-16.0)); + + ans = f1 / f2; + EXPECT_EQ(ans, fea::fixed(-0.25)); + + ans = f1 % f2; + EXPECT_EQ(ans, fea::fixed(-2.0)); + + f1 = 2.0; + f2 = -8.0; + ans = f1 + f2; + EXPECT_EQ(ans, fea::fixed(-6.0)); + + ans = f1 - f2; + EXPECT_EQ(ans, fea::fixed(10.0)); + + ans = f1 * f2; + EXPECT_EQ(ans, fea::fixed(-16.0)); + + ans = f1 / f2; + EXPECT_EQ(ans, fea::fixed(-0.25)); + + ans = f1 % f2; + EXPECT_EQ(ans, fea::fixed(2.0)); + + // A few more modulo + f1 = 1.0; + f2 = 5.0; + ans = f1 % f2; + EXPECT_EQ(ans, fea::fixed(1.0)); + f1 = 5.0; + f2 = 1.0; + ans = f1 % f2; + EXPECT_EQ(ans, fea::fixed(0.0)); + } + + { + fea::currency f1 = 2.0; + fea::currency f2 = 2.0; + fea::currency ans = f1 + f2; + EXPECT_EQ(ans, fea::currency(4.0)); + + ans = f1 - f2; + EXPECT_EQ(ans, fea::currency(0.0)); + + ans = f1 * f2; + EXPECT_EQ(ans, fea::currency(4.0)); + + ans = f1 / f2; + EXPECT_EQ(ans, fea::currency(1.0)); + + ans = f1 % f2; + EXPECT_EQ(ans, fea::currency(0.0)); + + f1 = 8.0; + f2 = 2.0; + ans = f1 + f2; + EXPECT_EQ(ans, fea::currency(10.0)); + + ans = f1 - f2; + EXPECT_EQ(ans, fea::currency(6.0)); + + ans = f1 * f2; + EXPECT_EQ(ans, fea::currency(16.0)); + + ans = f1 / f2; + EXPECT_EQ(ans, fea::currency(4.0)); + + ans = f1 % f2; + EXPECT_EQ(ans, fea::currency(0.0)); + + f1 = 2.0; + f2 = 8.0; + ans = f1 + f2; + EXPECT_EQ(ans, fea::currency(10.0)); + + ans = f1 - f2; + EXPECT_EQ(ans, fea::currency(-6.0)); + + ans = f1 * f2; + EXPECT_EQ(ans, fea::currency(16.0)); + + ans = f1 / f2; + EXPECT_EQ(ans, fea::currency(0.25)); + + ans = f1 % f2; + EXPECT_EQ(ans, fea::currency(2.0)); + + f1 = -2.0; + f2 = 8.0; + ans = f1 + f2; + EXPECT_EQ(ans, fea::currency(6.0)); + + ans = f1 - f2; + EXPECT_EQ(ans, fea::currency(-10.0)); + + ans = f1 * f2; + EXPECT_EQ(ans, fea::currency(-16.0)); + + ans = f1 / f2; + EXPECT_EQ(ans, fea::currency(-0.25)); + + ans = f1 % f2; + EXPECT_EQ(ans, fea::currency(-2.0)); + + f1 = 2.0; + f2 = -8.0; + ans = f1 + f2; + EXPECT_EQ(ans, fea::currency(-6.0)); + + ans = f1 - f2; + EXPECT_EQ(ans, fea::currency(10.0)); + + ans = f1 * f2; + EXPECT_EQ(ans, fea::currency(-16.0)); + + ans = f1 / f2; + EXPECT_EQ(ans, fea::currency(-0.25)); + + ans = f1 % f2; + EXPECT_EQ(ans, fea::currency(2.0)); + + // A few more modulo + f1 = 1.0; + f2 = 5.0; + ans = f1 % f2; + EXPECT_EQ(ans, fea::currency(1.0)); + f1 = 5.0; + f2 = 1.0; + ans = f1 % f2; + EXPECT_EQ(ans, fea::currency(0.0)); + } + + { + fea::fixed f1(mint_t(2)); + fea::fixed f2(mint_t(2)); + fea::fixed ans = f1 + f2; + EXPECT_EQ(mint_t(ans), 4); + + ans = f1 - f2; + EXPECT_EQ(mint_t(ans), 0); + + ans = f1 * f2; + EXPECT_EQ(mint_t(ans), 4); + + ans = f1 / f2; + EXPECT_EQ(mint_t(ans), 1); + + ans = f1 % f2; + EXPECT_EQ(mint_t(ans), 0); + + f1 = 8.0; + f2 = 2.0; + ans = f1 + f2; + EXPECT_EQ(mint_t(ans), 10); + + ans = f1 - f2; + EXPECT_EQ(mint_t(ans), 6); + + ans = f1 * f2; + EXPECT_EQ(mint_t(ans), 16); + + ans = f1 / f2; + EXPECT_EQ(mint_t(ans), 4); + + ans = f1 % f2; + EXPECT_EQ(mint_t(ans), 0); + } + + // Sanity checks. + { + using mfixed1 = fea::basic_fixed; + using mfixed2 = fea::basic_fixed; + using mfixed3 = fea::basic_fixed; + using mfixed4 = fea::basic_fixed; + using mfixed5 = fea::basic_fixed; + + static_assert(mfixed1::scaling_is_pow2_v, FAIL_MSG); + static_assert(!mfixed2::scaling_is_pow2_v, FAIL_MSG); + static_assert(!mfixed3::scaling_is_pow2_v, FAIL_MSG); + static_assert(mfixed4::scaling_is_pow2_v, FAIL_MSG); + static_assert(mfixed5::scaling_is_pow2_v, FAIL_MSG); + + static_assert(mfixed1::scaling_sqrt_v == 23, FAIL_MSG); + static_assert(mfixed2::scaling_sqrt_v == 0, FAIL_MSG); + static_assert(mfixed3::scaling_sqrt_v == 0, FAIL_MSG); + static_assert(mfixed4::scaling_sqrt_v == 1, FAIL_MSG); + static_assert(mfixed5::scaling_sqrt_v == 2, FAIL_MSG); + +#if FEA_ARCH >= 64 + using mfixed6 = fea::basic_fixed; + static_assert(mfixed6::scaling_is_pow2_v, FAIL_MSG); + static_assert(mfixed6::scaling_sqrt_v == 62, FAIL_MSG); +#endif + } + + // numeric_limits specialization. + { + // We just check that all these are implemented and working. + static_assert(std::numeric_limits::is_specialized == true); + static_assert(std::numeric_limits::is_signed + == std::is_signed_v); + static_assert(std::numeric_limits::is_integer == false); + static_assert(std::numeric_limits::is_exact == true); + static_assert(std::numeric_limits::has_infinity == false); + static_assert(std::numeric_limits::has_quiet_NaN == false); + static_assert( + std::numeric_limits::has_signaling_NaN == false); + static_assert(std::numeric_limits::has_denorm + == std::denorm_absent); + static_assert( + std::numeric_limits::has_denorm_loss == false); + static_assert(std::numeric_limits::round_style + == std::round_toward_zero); + static_assert(std::numeric_limits::is_iec559 == false); + static_assert(std::numeric_limits::is_bounded == true); + static_assert(std::numeric_limits::is_modulo + == std::numeric_limits::is_modulo); + static_assert(std::numeric_limits::digits + == std::numeric_limits::digits); + static_assert(std::numeric_limits::digits10 + == std::numeric_limits::digits10); + static_assert(std::numeric_limits::max_digits10 + == std::numeric_limits::max_digits10); + static_assert(std::numeric_limits::radix == 2); + static_assert(std::numeric_limits::min_exponent == 0); + static_assert(std::numeric_limits::min_exponent10 == 0); + static_assert(std::numeric_limits::max_exponent == 0); + static_assert(std::numeric_limits::max_exponent10 == 0); + static_assert(std::numeric_limits::traps + == std::numeric_limits::traps); + static_assert( + std::numeric_limits::tinyness_before == false); + + // We behave like floats, to be interchangeable. + // min() closest to zero, lowest() lowest negative value. + constexpr fea::fixed mmin = (std::numeric_limits::min)(); + static_assert(mmin == 0.0, FAIL_MSG); + static_assert(mint_t(mmin) == mint_t(0), FAIL_MSG); + + constexpr fea::fixed mlowest + = std::numeric_limits::lowest(); + static_assert(mlowest < 0.0, FAIL_MSG); + static_assert(mint_t(mlowest) < mint_t(0), FAIL_MSG); +#if FEA_ARCH == 64 + constexpr mint_t mlowest_expected = -(mint_t(1) << (64 - 23 - 1)); +#else + constexpr mint_t mlowest_expected = -(mint_t(1) << (32 - 11 - 1)); +#endif + static_assert(mint_t(mlowest) == mlowest_expected, FAIL_MSG); + + constexpr fea::fixed mmax = (std::numeric_limits::max)(); +#if FEA_ARCH == 64 + constexpr mint_t mmax_expected = (mint_t(1) << (64 - 23 - 1)) - 1; +#else + constexpr mint_t mmax_expected = (mint_t(1) << (32 - 11 - 1)) - 1; +#endif + static_assert(mint_t(mmax) == mmax_expected, FAIL_MSG); + +#if FEA_ARCH == 64 + constexpr fea::fixed mepsilon + = std::numeric_limits::epsilon(); + + // At 23 bits of fractional digits, we should have the same epsilon + // precision as a float32. + // Only applicable to 64bits. + static_assert( + mepsilon == std::numeric_limits::epsilon(), FAIL_MSG); +#endif + + constexpr fea::fixed mround_err + = std::numeric_limits::round_error(); + static_assert(mround_err == 0.5, FAIL_MSG); + + constexpr fea::fixed minfinity + = std::numeric_limits::infinity(); + static_assert(minfinity == 0.0, FAIL_MSG); + + constexpr fea::fixed mquiet_nan + = std::numeric_limits::quiet_NaN(); + static_assert(mquiet_nan == 0.0, FAIL_MSG); + + constexpr fea::fixed msignaling_nan + = std::numeric_limits::signaling_NaN(); + static_assert(msignaling_nan == 0.0, FAIL_MSG); + + constexpr fea::fixed mdenorm_min + = std::numeric_limits::denorm_min(); + static_assert(mdenorm_min == 0.0, FAIL_MSG); + } +} + +TEST(fixed, precision) { + { + constexpr fea::fixed eps = std::numeric_limits::epsilon(); + constexpr fea::fixed f1 = eps + eps; + constexpr fea::fixed f2 = eps * 2.0; + static_assert(f1 == f2, FAIL_MSG); + + constexpr fea::fixed f22 = 2.0 * eps; + static_assert(f2 == f22, FAIL_MSG); + + constexpr fea::fixed f3 = eps - eps; + static_assert(f3 == 0.0, FAIL_MSG); + + constexpr fea::fixed f4 = eps / eps; + static_assert(f4 == 1.0, FAIL_MSG); + } + + { + fea::fixed eps = std::numeric_limits::epsilon(); + fea::fixed f1 = eps + eps; + fea::fixed f2 = eps * 2.0; + EXPECT_EQ(f1, f2); + + fea::fixed f22 = 2.0 * eps; + EXPECT_EQ(f2, f22); + + fea::fixed f3 = eps - eps; + EXPECT_EQ(f3, 0.0); + + fea::fixed f4 = eps / eps; + EXPECT_EQ(f4, 1.0); + } + + { + constexpr fea::fixed eps = std::numeric_limits::epsilon(); + constexpr fea::fixed f1 = 1.0 + eps; + static_assert(f1 != 1.0, FAIL_MSG); + + constexpr fea::fixed f12 = eps + 1.0; + static_assert(f12 != 1.0, FAIL_MSG); + + constexpr fea::fixed f2 = 1.0 * eps; + static_assert(f2 != 1.0, FAIL_MSG); + + constexpr fea::fixed f22 = eps * 1.0; + static_assert(f22 != 1.0, FAIL_MSG); + + constexpr fea::fixed f3 = 1.0 - eps; + static_assert(f3 != 1.0, FAIL_MSG); + + constexpr fea::fixed f32 = eps - 1.0; + static_assert(f32 != 1.0, FAIL_MSG); + + constexpr fea::fixed f4 = 1.0 / eps; + static_assert(f4 != 1.0, FAIL_MSG); + + constexpr fea::fixed f42 = eps / 1.0; + static_assert(f42 != 1.0, FAIL_MSG); + } + + { + fea::fixed eps = std::numeric_limits::epsilon(); + fea::fixed f1 = 1.0 + eps; + EXPECT_NE(f1, 1.0); + + fea::fixed f12 = eps + 1.0; + EXPECT_NE(f12, 1.0); + + fea::fixed f2 = 1.0 * eps; + EXPECT_NE(f2, 1.0); + + fea::fixed f22 = eps * 1.0; + EXPECT_NE(f22, 1.0); + + fea::fixed f3 = 1.0 - eps; + EXPECT_NE(f3, 1.0); + + fea::fixed f32 = eps - 1.0; + EXPECT_NE(f32, 1.0); + + fea::fixed f4 = 1.0 / eps; + EXPECT_NE(f4, 1.0); + + fea::fixed f42 = eps / 1.0; + EXPECT_NE(f42, 1.0); + } +} +} // namespace \ No newline at end of file