diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 7a2c9f2..6383c60 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -15,7 +15,8 @@ add_executable(tests packet.cpp parser.cpp button.cpp - rand.cpp) + rand.cpp + fixedpoint.cpp) target_link_libraries(tests PUBLIC diff --git a/Tests/fixedpoint.cpp b/Tests/fixedpoint.cpp new file mode 100644 index 0000000..6278c97 --- /dev/null +++ b/Tests/fixedpoint.cpp @@ -0,0 +1,232 @@ +// +// Created by erki on 30.04.21. +// + +#include + +#include "utility_fixedpoint.hpp" + +using FP15 = Peripherals::FixedPoint; + +TEST_CASE("FP constructors work appropriately.", "[utility],[fixed point]") +{ + SECTION("Empty FP constructs with data 0.") + { + FP15 fp; + + REQUIRE(fp.data == 0); + } + + SECTION("FP with integral data constructors appropriately.") + { + FP15 fp(1); + + REQUIRE(fp.data == (1 << 15)); + } + + SECTION("FP assignment operators work appropriately.") + { + FP15 fp; + fp = 15; + + REQUIRE(fp.data == (15 << 15)); + } + + SECTION("FP with floating point data constructors works appropriately.") + { + FP15 fp(1.5f); + + REQUIRE(fp.data == ((1 << 15) | (1 << 14))); + + FP15 fp2(1.5); + + REQUIRE(fp2.data == ((1 << 15) | (1 << 14))); + } + + SECTION("FP assignment operators work with floating point data appropriately.") + { + FP15 fp; + fp = 1.5f; + + REQUIRE(fp.data == ((1 << 15) | (1 << 14))); + + fp = 1.5; + + REQUIRE(fp.data == ((1 << 15) | (1 << 14))); + } +} + +TEST_CASE("FP comparison operators work appropriately.", "[utility],[fixed point]") +{ + SECTION("FPs compare equal with other FPs appropriately.") + { + const FP15 fp1(15); + const FP15 fp2(15); + + REQUIRE(fp1 == fp2); + + const FP15 fp3(32); + + REQUIRE(fp1 != fp3); + } + + SECTION("FPs compare equal with integers appropriately.") + { + const FP15 fp1(15); + + REQUIRE(fp1 == 15); + REQUIRE(fp1 != 32); + } + + SECTION("FPs compare equal with floats appropriately.") + { + const FP15 fp1(1.45f); + + REQUIRE(fp1 == 1.45f); + REQUIRE(fp1 == 1.45); + } + + SECTION("FPs compare lt and gt with other FPs appropriately.") + { + const FP15 fp(1.45f); + const FP15 fp2(1.4525f); + const FP15 fp3(1.4475f); + + REQUIRE(fp < fp2); + REQUIRE(fp > fp3); + REQUIRE(fp <= fp2); + REQUIRE(fp >= fp3); + + REQUIRE(fp <= fp); + REQUIRE(fp >= fp); + REQUIRE(!(fp < fp)); + REQUIRE(!(fp > fp)); + } + + SECTION("FPs compare lt and gt with integers appropriately.") + { + const FP15 fp(15); + + REQUIRE(fp < 16); + REQUIRE(fp > 14); + REQUIRE(!(fp < 15)); + REQUIRE(!(fp > 15)); + + REQUIRE(fp <= 15); + REQUIRE(fp <= 16); + REQUIRE(fp >= 15); + REQUIRE(fp >= 14); + } + + SECTION("FPs compare lt and gt with floating point numbers appropriately.") + { + const FP15 fp(45.75f); + + REQUIRE(fp < 45.80f); + REQUIRE(fp > 45.70f); + REQUIRE(!(fp < 45.75f)); + REQUIRE(!(fp > 45.75f)); + + REQUIRE(fp <= 45.75f); + REQUIRE(fp <= 45.80f); + REQUIRE(fp >= 45.75f); + REQUIRE(fp >= 45.70f); + } +} + +TEST_CASE("FP arithmetic operators work appropriately.", "[utility],[fixed point]") +{ + SECTION("FPs add with other FPs appropriately.") + { + const FP15 fp1(45.75f); + const FP15 fp2(0.25f); + + REQUIRE((fp1 + fp2) == FP15(46)); + } + + SECTION("FPs subtract from other FPs appropriately.") + { + const FP15 fp1(45.75f); + const FP15 fp2(0.75f); + + REQUIRE((fp1 - fp2) == FP15(45)); + } + + SECTION("FPs add with arithmetic types appropriately.") + { + const FP15 fp(45.75f); + + REQUIRE((fp + 10) == FP15(55.75f)); + REQUIRE((fp + 0.25f) == FP15(46)); + } + + SECTION("FPs subtract from arithmetic types appropriately.") + { + const FP15 fp(45.75f); + + REQUIRE((fp - 10) == FP15(35.75f)); + REQUIRE((fp - 0.75f) == FP15(45)); + } +} + +TEMPLATE_TEST_CASE("FP converting back to integral values works appropriately.", "[utility],[fixed point]", + std::int8_t, std::int16_t, std::int32_t) +{ + using FP = Peripherals::FixedPoint; + + FP fp(4); + + REQUIRE(fp.template toValue() == 4); +} + +TEMPLATE_TEST_CASE("FP converting back to floating point values works appropriately.", "[utility],[fixed point]", + float, double) +{ + FP15 fp(TestType(4.45)); + + REQUIRE(TestType(fp) == Approx(TestType(4.45))); +} + +TEST_CASE("FP multiplication works appropriately.", "[utility],[fixed point]") +{ + SECTION("FP multiplies with other FPs appropriately.") + { + FP15 fp1(0.25); + FP15 fp2(0.1); + + FP15 fp3 = fp1 * fp2; + + REQUIRE(fp3 == FP15(0.025)); + } + + SECTION("FP multiplies with real values appropriately.") + { + FP15 fp1(0.25); + + FP15 fp2 = fp1 * 0.1; + + REQUIRE(fp2 == FP15(0.025)); + } +} + +TEST_CASE("FP division works appropriately.", "[utility],[fixed point]") +{ + SECTION("FP divides with other FPs appropriately.") + { + FP15 fp1(0.25); + FP15 fp2(0.1); + + FP15 fp3 = fp1 / fp2; + + REQUIRE(fp3.data == FP15(2.5).data - 5);// Rounding errors, yay! + } + + SECTION("FP divides with real values appropriately.") + { + FP15 fp1(0.25); + + FP15 fp3 = fp1 / 0.1; + + REQUIRE(fp3.data == FP15(2.5).data - 5);// Rounding errors, yay! + } +} diff --git a/Utility/Inc/utility_fixedpoint.hpp b/Utility/Inc/utility_fixedpoint.hpp new file mode 100644 index 0000000..8f2753c --- /dev/null +++ b/Utility/Inc/utility_fixedpoint.hpp @@ -0,0 +1,292 @@ +// +// Created by erki on 30.04.21. +// + +#ifndef SKULLC_UTILITY_FIXEDPOINT_HPP +#define SKULLC_UTILITY_FIXEDPOINT_HPP + +#include + +namespace Peripherals +{ + +namespace Details +{ + +template +struct NextRankingInteger +{ +}; + +template<> +struct NextRankingInteger +{ + using T = std::int32_t; +}; + +template<> +struct NextRankingInteger +{ + using T = std::int64_t; +}; + +template<> +struct NextRankingInteger +{ + using T = std::int64_t; +}; + +template<> +struct NextRankingInteger +{ + using T = std::uint32_t; +}; + +template<> +struct NextRankingInteger +{ + using T = std::uint64_t; +}; + +template<> +struct NextRankingInteger +{ + using T = std::uint64_t; +}; + +}// namespace Details + +template +struct FixedPoint +{ + using underlying = U; + using isSigned = std::is_signed; + constexpr static std::size_t fractionalBits = D; + + underlying data; + + constexpr FixedPoint() + : data{0} + {} + + template + constexpr explicit FixedPoint(const T& value) + { + static_assert(std::is_arithmetic_v, "T is not an arithmetic type."); + *this = FixedPoint::fromValue(value); + } + + template + constexpr FixedPoint& operator=(const T& value) + { + static_assert(std::is_arithmetic_v, "T is not an arithmetic type."); + + *this = FixedPoint::fromValue(value); + + return *this; + } + + constexpr bool operator==(const FixedPoint& o) const + { + return data == o.data; + } + + template + constexpr bool operator==(const T& value) const + { + static_assert(std::is_arithmetic_v, "T is not an integral type."); + const auto other = FixedPoint(value); + return *this == other; + } + + template + constexpr bool operator!=(const T& value) const + { + static_assert(std::is_arithmetic_v, "T is not an integral type."); + const auto other = FixedPoint(value); + return *this != other; + } + + constexpr bool operator!=(const FixedPoint& o) const + { + return !operator==(o); + } + + constexpr bool operator<(const FixedPoint& o) const + { + return data < o.data; + } + + constexpr bool operator<=(const FixedPoint& o) const + { + return data <= o.data; + } + + constexpr bool operator>(const FixedPoint& o) const + { + return data > o.data; + } + + constexpr bool operator>=(const FixedPoint& o) const + { + return data >= o.data; + } + + template + constexpr bool operator<(const T& value) const + { + const auto o = fromValue(value); + return data < o.data; + } + + template + constexpr bool operator<=(const T& value) const + { + const auto o = fromValue(value); + return data <= o.data; + } + + template + constexpr bool operator>(const T& value) const + { + const auto o = fromValue(value); + return data > o.data; + } + + template + constexpr bool operator>=(const T& value) const + { + const auto o = fromValue(value); + return data >= o.data; + } + + friend constexpr FixedPoint operator+(const FixedPoint& a, const FixedPoint& b) + { + auto clone = a; + clone.data += b.data; + + return clone; + } + + friend constexpr FixedPoint operator-(const FixedPoint& a, const FixedPoint& b) + { + auto clone = a; + clone.data -= b.data; + + return clone; + } + + template + friend constexpr FixedPoint operator+(const FixedPoint& a, const T& b) + { + static_assert(std::is_arithmetic_v, "T is not an arithmetic type."); + return a + FixedPoint(b); + } + + template + friend constexpr FixedPoint operator-(const FixedPoint& a, const T& b) + { + static_assert(std::is_arithmetic_v, "T is not an arithmetic type."); + return a - FixedPoint(b); + } + + friend constexpr FixedPoint operator*(const FixedPoint& a, const FixedPoint& b) + { + // Ref: https://spin.atomicobject.com/2012/03/15/simple-fixed-point-math/ + // Ref: https://en.wikipedia.org/wiki/Q_(number_format) + using T = typename Details::NextRankingInteger::T; + + const T axb = T(a.data) * T(b.data); + const T round_value = axb >> (fractionalBits - 1); + + auto dest = FixedPoint(); + dest.data = (axb / (underlying(1) << fractionalBits)) + (round_value & 1); + return dest; + } + + friend constexpr FixedPoint operator/(const FixedPoint& a, const FixedPoint& b) + { + // Ref: https://en.wikipedia.org/wiki/Q_(number_format) + using T = typename Details::NextRankingInteger::T; + + T temp = T(a.data) << fractionalBits; + + // rounding + if ((temp >= 0 && b.data >= 0) || (temp < 0 && b.data < 0)) + temp += b.data / 2; + else + temp -= b.data / 2; + + auto dest = FixedPoint(); + dest.data = temp / b.data; + return dest; + } + + template + friend constexpr FixedPoint operator*(const FixedPoint& a, const T& b) + { + static_assert(std::is_arithmetic_v, "T is not an arithmetic type."); + return a * FixedPoint(b); + } + + template + friend constexpr FixedPoint operator/(const FixedPoint& a, const T& b) + { + static_assert(std::is_arithmetic_v, "T is not an arithmetic type."); + return a / FixedPoint(b); + } + + template + static constexpr FixedPoint fromValue(const T& value) + { + static_assert(std::is_integral_v, "T is not integral value."); + auto fp = FixedPoint(); + + fp.data = value << D; + + return fp; + } + + constexpr static FixedPoint fromValue(const float& value) + { + auto fp = FixedPoint(); + + fp.data = std::round(value * (underlying(1) << fractionalBits)); + + return fp; + } + + constexpr static FixedPoint fromValue(const double& value) + { + auto fp = FixedPoint(); + + fp.data = std::round(value * (underlying(1) << fractionalBits)); + + return fp; + } + + template + constexpr T toValue() const + { + static_assert(std::is_integral_v, "T is not an integral type."); + return data >> fractionalBits; + } + + constexpr explicit operator float() const + { + return float(data) / float(underlying(1) << fractionalBits); + } + + constexpr explicit operator double() const + { + return double(data) / double(underlying(1) << fractionalBits); + } +}; + +using Q7 = FixedPoint; +using Q15 = FixedPoint; +using Q31 = FixedPoint; + +}// namespace Peripherals + +#endif//SKULLC_UTILITY_FIXEDPOINT_HPP