From a574d37b9dc112190522508073b04fa5153ec6c9 Mon Sep 17 00:00:00 2001 From: namark <namark@disroot.org> Date: Mon, 19 Jul 2021 19:44:22 +0400 Subject: [PATCH] WIP rational no longer uses array ops, denominator can be a compile time constant. --- source/simple/support/rational.hpp | 271 +++++++++++++++++++++++++---- unit_tests/rational.cpp | 14 ++ 2 files changed, 250 insertions(+), 35 deletions(-) diff --git a/source/simple/support/rational.hpp b/source/simple/support/rational.hpp index 8e3f405..8e8de07 100644 --- a/source/simple/support/rational.hpp +++ b/source/simple/support/rational.hpp @@ -1,66 +1,267 @@ #ifndef SIMPLE_SUPPORT_RATIONAL_HPP #define SIMPLE_SUPPORT_RATIONAL_HPP -#include "array.hpp" -#include "array_operators.hpp" #include "type_traits.hpp" namespace simple::support { // TODO: find and use a proper rational type - template <typename Int> - struct rational : public simple::support::array<Int, 2> + // no, not the math-scientific,always canonical,pure algebra rational. + // A fully customizable, widely applicable, "don't pay for what not use" rational. + // If there is a calculation trick I can do with ratios by hand, I should be able to it with rational here + // + // or I guess if we are doing this + // TODO: implement the rest of the ops trivially, disregarding overflow + // TODO: diagnose overflow + // TODO: tackle the actual hard problems, there is a whole world here of reducing the integers with common factors, canonicalizing on every move is a total overkill + // TODO: SFINAE Denominator to be either void meta_constant maybe, currently it's a bit loosey-goosey + + // want c++20 non type template paramssss ToT + template <typename T, T Value> + struct meta_constant + { + static constexpr T value = Value; + + constexpr operator T() { return value; }; + }; + + template <typename T, T left, T right> + constexpr auto operator* (meta_constant<T, left>, meta_constant<T, right>) + { return meta_constant<T, left * right>{}; } + + template <typename Num, typename Denom> + struct rational_base { - enum + // protected: + Num num; + using den = Denom; + }; + + template <typename Num> + struct rational_base<Num, void> + { + Num num; + Num den; + }; + + + template <typename Num, typename Denom = void> + class rational : private rational_base<Num, Denom> + { + using base = rational_base<Num, Denom>; + + // common + + public: + + constexpr rational() = default; + + constexpr rational& operator*=(const Num& value) + { + numerator() *= value; + return *this; + } + + constexpr rational& operator+=(const Num& other) + { + numerator() += other * denominator(); + return *this; + } + constexpr rational& operator-=(const Num& other) + { + numerator() -= other * denominator(); + return *this; + } + + constexpr rational& operator ++() { return operator+=(Num{} + 1); } + constexpr rational& operator --() { return operator-=(Num{} + 1); } + + constexpr rational operator ++(int) + { + auto old = *this; + operator++(); + return old; + } + constexpr rational operator --(int) + { + auto old = *this; + operator--(); + return old; + } + + constexpr explicit operator Num() const + { return numerator() / denominator(); } + + constexpr Num& numerator() & + { return this->num; } + constexpr const Num& numerator() const & + { return this->num; } + constexpr Num&& numerator() && + { return this->num; } + constexpr const Num&& numerator() const && + { return this->num; } + + constexpr decltype(auto) denominator() + { + if constexpr (std::is_same_v<Denom,void>) + return (this->den); // unknown denominator + else + return typename base::den{}; // known denominator + } + + constexpr decltype(auto) denominator() const { - numerator, - denominator - }; + if constexpr (std::is_same_v<Denom,void>) + return (this->den); // unknown denominator + else + return typename base::den{}; // known denominator + } + + // unknown denominator + + template <typename Numerator, + typename D = Denom, std::enable_if_t<std::is_same_v<D,void>>* = nullptr > + constexpr rational(Numerator numerator, Numerator denominator) : base{numerator, denominator} + {} - constexpr rational& operator*=(const Int& value) + template <typename OtherNum, typename OtherDenom, + typename D = Denom, std::enable_if_t<std::is_same_v<D, void>>* = nullptr> + constexpr rational& operator*=(const rational<OtherNum, OtherDenom>& value) { - (*this)[numerator] *= value; + numerator() *= value.numerator(); + denominator() *= value.denominator(); return *this; } - constexpr explicit operator Int() const + // known denominator + + template <typename Denominator, + typename D = Denom, std::enable_if_t<not std::is_same_v<D,void>>* = nullptr > + constexpr rational(Num numerator, Denominator) : base{numerator} + {} + + + template <typename D = Denom, std::enable_if_t<not std::is_same_v<D, void>>* = nullptr> + constexpr rational& operator+=(const rational& other) { - return (*this)[numerator] / (*this)[denominator]; + numerator() += other.numerator(); + return *this; } + }; - template <typename Int> - constexpr rational<Int> operator*(rational<Int> ratio, const non_deduced<Int>& value) + // unknown denominator + template <typename Num> rational(Num, Num) -> rational<Num, void>; + + // known denominator + template <typename Num, typename Denom> rational(Num, Denom) -> rational<Num, Denom>; + + template <typename Num, typename Denom, + std::enable_if_t<not std::is_same_v<Denom,void>>* = nullptr> + [[nodiscard]] constexpr + rational<Num, Denom> next(rational<Num,Denom> current) { - ratio *= value; - return ratio; + current.numerator() += (Num{} + 1); + } + template <typename Num, typename Denom, + std::enable_if_t<not std::is_same_v<Denom,void>>* = nullptr> + [[nodiscard]] constexpr + rational<Num, Denom> prev(rational<Num,Denom> current) + { + current.numerator() -= (Num{} + 1); } - template <typename Int> - constexpr rational<Int> operator*(const non_deduced<Int>& value, rational<Int> ratio) + template <typename Num, typename Denom, + std::enable_if_t<not std::is_same_v<Denom,void>>* = nullptr> + [[nodiscard]] constexpr + rational<Num, Denom> advance(rational<Num,Denom> current, Num offset) { - ratio *= value; - return ratio; + current.numerator() += offset; } - template <typename Int> rational(Int, Int) -> rational<Int>; + template <typename Num, typename Denom, + std::enable_if_t<not std::is_same_v<Denom,void>>* = nullptr> + [[nodiscard]] constexpr + bool operator== (const rational<Num,Denom>& one, const rational<Num,Denom>& other) + { return one.numerator() == other.numerator(); } + template <typename Num, typename Denom, + std::enable_if_t<not std::is_same_v<Denom,void>>* = nullptr> + [[nodiscard]] constexpr + bool operator< (const rational<Num,Denom>& one, const rational<Num,Denom>& other) + { return one.numerator() < other.numerator(); } -} // namespace simple::support + // common -namespace simple -{ - template<typename T> - struct support::define_array_operators<support::rational<T>> : - public support::trivial_array_accessor<support::rational<T>, 2> + template <typename Num, typename Denom> + constexpr auto operator*(const rational<Num,Denom>& multiplicand, const rational<Num,Denom>& multiplier) { - constexpr static auto enabled_right_element_operators = array_operator::none; - constexpr static auto enabled_left_element_operators = array_operator::none; - constexpr static auto enabled_operators = - array_operator::mul | - array_operator::mul_eq | - array_operator::none; - }; -} // namespace simple + return rational + ( + multiplicand.numerator() * multiplier.numerator(), + multiplicand.denominator() * multiplier.denominator() + ); + } + + // TODO: there is some boost library that does this stuff... + // is a boost library though... + + template <typename Num, typename Denom> + constexpr rational<Num,Denom> operator*(rational<Num,Denom> multiplicand, const non_deduced<Num>& multiplier) + { + multiplicand *= multiplier; + return multiplicand; + } + template <typename Num, typename Denom> + constexpr rational<Num,Denom> operator+(rational<Num,Denom> addend, const non_deduced<Num>& adder) + { + addend += adder; + return addend; + } + + template <typename Num, typename Denom> + constexpr rational<Num,Denom> operator*(const non_deduced<Num>& multiplier, rational<Num, Denom> multiplicand) + { + multiplicand *= multiplier; + return multiplicand; + } + template <typename Num, typename Denom> + constexpr rational<Num,Denom> operator+(const non_deduced<Num>& addend, rational<Num, Denom> adder) + { + addend += adder; + return addend; + } + + template <typename Num, typename Denom> + constexpr auto operator+(rational<Num,Denom> addend, const rational<Num,Denom>& adder) + -> decltype(addend += adder, rational<Num,Denom>{}) + { return addend += adder; } + + template <typename Num, typename Denom> + [[nodiscard]] constexpr + auto operator!=(rational<Num, Denom> one, rational<Num,Denom> other) + -> decltype(!(one == other)) + { return !(one == other); } + + template <typename Num, typename Denom> + [[nodiscard]] constexpr + auto operator> (rational<Num, Denom> left, rational<Num,Denom> right) + -> decltype(right < left) + { return right < left; } + + template <typename Num, typename Denom> + [[nodiscard]] constexpr + auto operator>= (rational<Num, Denom> left, rational<Num,Denom> right) + -> decltype(!(left < right)) + { return !(left < right); } + + template <typename Num, typename Denom> + [[nodiscard]] constexpr + auto operator<= (rational<Num, Denom> left, rational<Num,Denom> right) + -> decltype(!(left > right)) + { return !(left > right); } + + +} // namespace simple::support #endif /* end of include guard */ diff --git a/unit_tests/rational.cpp b/unit_tests/rational.cpp index 8c7333a..45dd416 100644 --- a/unit_tests/rational.cpp +++ b/unit_tests/rational.cpp @@ -1,6 +1,7 @@ #include "simple/support/rational.hpp" using simple::support::rational; +using simple::support::meta_constant; constexpr bool Ratio() { @@ -26,6 +27,19 @@ constexpr bool Ratio() ratio *= 2; assertion &= int(ratio) == 15; + auto half = rational(1, meta_constant<int,2>{}); + auto one = half + half; + auto quarter = half * half; + auto onenhalf = half + 1; + + assertion &= int(one) == 1; + assertion &= int(quarter * 16) == 4; + assertion &= int(quarter * 8) == 2; + assertion &= int(quarter * 15) == 3; + assertion &= (quarter * 2) == (quarter + quarter); + assertion &= (quarter * 2) == rational(2, meta_constant<int,4>{}); + assertion &= onenhalf == rational(3, meta_constant<int,2>{}); + return assertion; } -- GitLab