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