diff --git a/include/etl/ratio.h b/include/etl/ratio.h index 8c7fa81d9..1035e7741 100644 --- a/include/etl/ratio.h +++ b/include/etl/ratio.h @@ -33,6 +33,8 @@ SOFTWARE. #include "platform.h" +#include "type_traits.h" + #include <stddef.h> #include <stdint.h> @@ -41,17 +43,17 @@ SOFTWARE. namespace etl { - template <size_t NUM, size_t DEN = 1UL> + template <intmax_t NUM, intmax_t DEN = 1UL> struct ratio { static ETL_CONSTANT intmax_t num = NUM; static ETL_CONSTANT intmax_t den = DEN; }; - template <size_t NUM, size_t DEN> + template <intmax_t NUM, intmax_t DEN> ETL_CONSTANT intmax_t ratio<NUM, DEN>::num; - template <size_t NUM, size_t DEN> + template <intmax_t NUM, intmax_t DEN> ETL_CONSTANT intmax_t ratio<NUM, DEN>::den; #if INT_MAX > INT32_MAX @@ -97,6 +99,153 @@ namespace etl /// An approximation of e. typedef ratio<326, 120> ratio_e; + +#if ETL_USING_CPP11 + namespace private_ratio + { + // Primary template for GCD calculation + template <typename T, T A, T B, bool = (B == 0)> + struct ratio_gcd; + + // Specialisation for the case when B is not zero + template <typename T, T A, T B> + struct ratio_gcd<T, A, B, false> + { + static constexpr T value = ratio_gcd<T, B, A % B>::value; + }; + + // Specialisation for the case when B is zero + template <typename T, T A, T B> + struct ratio_gcd<T, A, B, true> + { + static constexpr T value = (A < 0) ? -A : A; + }; + + // Primary template for LCM calculation + template <typename T, T A, T B> + struct ratio_lcm + { + private: + + static constexpr T product = ((A * B) < 0) ? -(A * B) : A * B; + + public: + + static constexpr T value = product / ratio_gcd<T, A, B>::value; + }; + + template<typename R1> + struct ratio_reduce + { + private: + + static ETL_CONSTEXPR11 intmax_t gcd = etl::private_ratio::ratio_gcd<intmax_t, R1::num, R1::den>::value; + + public: + + using value = ratio<R1::num / gcd, R1::den / gcd>; + }; + + template<typename R1, typename R2> + struct ratio_add + { + private: + + static ETL_CONSTEXPR11 intmax_t lcm = etl::private_ratio::ratio_lcm<intmax_t, R1::den, R2::den>::value; + + public: + + using value = typename ratio_reduce<ratio<R1::num * lcm / R1::den + R2::num * lcm / R2::den, lcm>>::value; + }; + + template<typename R1, typename R2> + struct ratio_subtract + { + public: + using value = typename ratio_add<R1, ratio<-R2::num, R2::den>>::value; + }; + + template<typename R1, typename R2> + struct ratio_multiply + { + private: + static ETL_CONSTEXPR11 intmax_t gcd1 = etl::private_ratio::ratio_gcd<intmax_t, R1::num, R2::den>::value; + static ETL_CONSTEXPR11 intmax_t gcd2 = etl::private_ratio::ratio_gcd<intmax_t, R2::num, R1::den>::value; + + public: + using value = ratio<(R1::num / gcd1) * (R2::num / gcd2), (R1::den / gcd2) * (R2::den / gcd1)>; + }; + + template<typename R1, typename R2> + struct ratio_divide + { + public: + using value = typename ratio_multiply<R1, ratio<R2::den, R2::num>>::value; + }; + } + + template<typename R1, typename R2> + using ratio_add = typename private_ratio::ratio_add<R1, R2>::value; + + template<typename R1, typename R2> + using ratio_subtract = typename private_ratio::ratio_subtract<R1, R2>::value; + + template<typename R1, typename R2> + using ratio_multiply = typename private_ratio::ratio_multiply<R1, R2>::value; + + template<typename R1, typename R2> + using ratio_divide = typename private_ratio::ratio_divide<R1, R2>::value; + + template<typename R1, typename R2> + struct ratio_equal: etl::integral_constant<bool, (R1::num == R2::num && R1::den == R2::den)> + { + }; + + template<typename R1, typename R2> + struct ratio_not_equal: etl::integral_constant<bool, (R1::num != R2::num || R1::den != R2::den)> + { + }; + + template<typename R1, typename R2> + struct ratio_less: etl::integral_constant<bool, (R1::num * R2::den < R2::num * R1::den)> + { + }; + + template<typename R1, typename R2> + struct ratio_less_equal: etl::integral_constant<bool, (R1::num * R2::den <= R2::num * R1::den)> + { + }; + + template<typename R1, typename R2> + struct ratio_greater: etl::integral_constant<bool, (R1::num * R2::den > R2::num * R1::den)> + { + }; + + template<typename R1, typename R2> + struct ratio_greater_equal: etl::integral_constant<bool, (R1::num * R2::den >= R2::num * R1::den)> + { + }; + +#if ETL_USING_CPP14 + template<typename R1, typename R2> + ETL_CONSTEXPR14 bool ratio_equal_v = ratio_equal<R1, R2>::value; + + template<typename R1, typename R2> + ETL_CONSTEXPR14 bool ratio_not_equal_v = ratio_not_equal<R1, R2>::value; + + template<typename R1, typename R2> + ETL_CONSTEXPR14 bool ratio_less_v = ratio_less<R1, R2>::value; + + template<typename R1, typename R2> + ETL_CONSTEXPR14 bool ratio_less_equal_v = ratio_less_equal<R1, R2>::value; + + template<typename R1, typename R2> + ETL_CONSTEXPR14 bool ratio_greater_v = ratio_greater<R1, R2>::value; + + template<typename R1, typename R2> + ETL_CONSTEXPR14 bool ratio_greater_equal_v = ratio_greater_equal<R1, R2>::value; +#endif +#endif } #endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ce9e2a664..725b6928f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -222,6 +222,7 @@ add_executable(etl_tests test_queue_spsc_locked.cpp test_queue_spsc_locked_small.cpp test_random.cpp + test_ratio.cpp test_reference_flat_map.cpp test_reference_flat_multimap.cpp test_reference_flat_multiset.cpp diff --git a/test/test_ratio.cpp b/test/test_ratio.cpp new file mode 100644 index 000000000..12a0b406f --- /dev/null +++ b/test/test_ratio.cpp @@ -0,0 +1,144 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2025 BMW AG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +******************************************************************************/ + +#include "unit_test_framework.h" + +#include "etl/ratio.h" + +#if ETL_USING_CPP11 +namespace +{ + SUITE(test_ratio) + { + //************************************************************************* + TEST(test_ratio_add) + { + using r1 = etl::ratio<1, 2>; + using r2 = etl::ratio<2, 3>; + using r3 = etl::ratio_add<r1, r2>; + + CHECK((etl::ratio_equal<r3, etl::ratio<7, 6>>::value)); + CHECK((!etl::ratio_equal<r3, etl::ratio<1, 6>>::value)); + } + + //************************************************************************* + TEST(test_ratio_subtract) + { + using r1 = etl::ratio<1, 2>; + using r2 = etl::ratio<2, 3>; + using r3 = etl::ratio_subtract<r1, r2>; + + CHECK((etl::ratio_equal<r3, etl::ratio<-1, 6>>::value)); + CHECK((!etl::ratio_equal<r3, etl::ratio<-2, 6>>::value)); + } + + //************************************************************************* + TEST(test_ratio_multiply) + { + using r1 = etl::ratio<1, 2>; + using r2 = etl::ratio<2, 3>; + using r3 = etl::ratio_multiply<r1, r2>; + + CHECK((etl::ratio_equal<r3, etl::ratio<1, 3>>::value)); + CHECK((!etl::ratio_equal<r3, etl::ratio<1, 4>>::value)); + } + + //************************************************************************* + TEST(test_ratio_divide) + { + using r1 = etl::ratio<1, 2>; + using r2 = etl::ratio<2, 3>; + using r3 = etl::ratio_divide<r1, r2>; + + CHECK((!etl::ratio_not_equal<r3, etl::ratio<3, 4>>::value)); + CHECK((etl::ratio_not_equal<r3, etl::ratio<4, 3>>::value)); + } + + //************************************************************************* + TEST(test_ratio_equal) + { + using r1 = etl::ratio<1, 2>; + using r2 = etl::ratio<2, 3>; + + CHECK((etl::ratio_equal<r1, r1>::value)); + CHECK((!etl::ratio_equal<r1, r2>::value)); + } + + //************************************************************************* + TEST(test_ratio_not_equal) + { + using r1 = etl::ratio<1, 2>; + using r2 = etl::ratio<2, 3>; + + CHECK((!etl::ratio_not_equal<r1, r1>::value)); + CHECK((etl::ratio_not_equal<r1, r2>::value)); + } + + //************************************************************************* + TEST(test_ratio_less) + { + using r1 = etl::ratio<1, 2>; + using r2 = etl::ratio<2, 3>; + + CHECK((etl::ratio_less<r1, r2>::value)); + CHECK((!etl::ratio_less<r2, r1>::value)); + } + + //************************************************************************* + TEST(test_ratio_less_equal) + { + using r1 = etl::ratio<1, 2>; + using r2 = etl::ratio<2, 3>; + + CHECK((etl::ratio_less_equal<r1, r1>::value)); + CHECK((etl::ratio_less_equal<r1, r2>::value)); + } + + //************************************************************************* + TEST(test_ratio_greater) + { + using r1 = etl::ratio<4, 3>; + using r2 = etl::ratio<2, 3>; + + CHECK((etl::ratio_greater<r1, r2>::value)); + CHECK((!etl::ratio_greater<r2, r1>::value)); + } + + //************************************************************************* + TEST(test_ratio_greater_equal) + { + using r1 = etl::ratio<4, 3>; + using r2 = etl::ratio<2, 3>; + + CHECK((etl::ratio_greater_equal<r1, r1>::value)); + CHECK((etl::ratio_greater_equal<r1, r2>::value)); + } + }; + +} +#endif