Line data Source code
1 : /*
2 : * Copyright (C) 2004-2025 Savoir-faire Linux Inc.
3 : *
4 : * This program is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 3 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * This program is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 : */
17 : #pragma once
18 :
19 : #include <utility> // std::swap
20 : #include <cstdlib> // std::abs
21 : #include <iostream>
22 : #include <cmath> // std::fmod
23 : #include <functional> // std::modulus
24 : #include <ciso646> // and, or ...
25 : #include <fmt/format.h>
26 :
27 : extern "C" {
28 : #include <libavutil/rational.h> // specify conversions for AVRational
29 : }
30 :
31 : namespace jami {
32 :
33 : /**
34 : * Naive implementation of the boost::rational interface, described here:
35 : * https://www.boost.org/doc/libs/latest/libs/rational/rational.html
36 : */
37 : template<typename I>
38 : class rational
39 : {
40 : public:
41 : // Zero
42 5609 : constexpr rational() {}
43 :
44 : // Equal to n/1
45 1 : constexpr rational(I n)
46 1 : : num_(n) {}
47 :
48 : // General case (n/d)
49 474 : constexpr rational(I n, I d)
50 474 : : num_(n)
51 474 : , den_(d)
52 : {
53 474 : reduce();
54 474 : }
55 :
56 : // Define conversions to and from AVRational (equivalent)
57 393 : constexpr rational(AVRational r)
58 393 : : num_(r.num)
59 393 : , den_(r.den) {};
60 376 : constexpr operator AVRational() const { return AVRational {(int) num_, (int) den_}; }
61 :
62 0 : std::string to_string() const {
63 0 : return fmt::format("{}/{}", num_, den_);
64 : }
65 :
66 : // Normal copy constructors and assignment operators
67 :
68 : // Assignment from I
69 335 : rational& operator=(I n)
70 : {
71 335 : num_ = n;
72 335 : den_ = 1;
73 335 : return *this;
74 : }
75 :
76 : // Assign in place
77 : rational& assign(I n, I d)
78 : {
79 : num_ = n;
80 : den_ = d;
81 : reduce();
82 : return *this;
83 : }
84 :
85 : // Representation
86 311 : constexpr I numerator() const { return num_; };
87 159 : constexpr I denominator() const { return den_; };
88 :
89 : template<typename R = double>
90 808 : constexpr R real() const
91 : {
92 808 : return num_ / (R) den_;
93 : }
94 :
95 : // Arithmetic operators
96 : constexpr rational operator+(const rational& r) const
97 : {
98 : return {num_ * r.den_ + r.num_ * den_, den_ * r.den_};
99 : }
100 0 : constexpr rational operator-(const rational& r) const
101 : {
102 0 : return {num_ * r.den_ - r.num_ * den_, den_ * r.den_};
103 : }
104 0 : constexpr rational operator*(const rational& r) const { return {num_ * r.num_, den_ * r.den_}; }
105 : constexpr rational operator/(const rational& r) const { return {num_ * r.den_, den_ * r.num_}; }
106 :
107 : constexpr rational& operator+=(const rational& r)
108 : {
109 : std::swap(*this, *this + r);
110 : return *this;
111 : }
112 : constexpr rational& operator-=(const rational& r)
113 : {
114 : std::swap(*this, *this - r);
115 : return *this;
116 : }
117 : constexpr rational& operator*=(const rational& r)
118 : {
119 : num_ *= r.num_;
120 : den_ *= r.den_;
121 : reduce();
122 : return *this;
123 : }
124 : constexpr rational& operator/=(const rational& r)
125 : {
126 : num_ *= r.den_;
127 : den_ *= r.num_;
128 : reduce();
129 : return *this;
130 : }
131 :
132 : // Arithmetic with integers
133 : constexpr rational& operator+=(I i)
134 : {
135 : num_ += i * den_;
136 : return *this;
137 : }
138 : constexpr rational& operator-=(I i)
139 : {
140 : num_ -= i * den_;
141 : return *this;
142 : }
143 : constexpr rational& operator*=(I i)
144 : {
145 : num_ *= i;
146 : reduce();
147 : return *this;
148 : }
149 : constexpr rational& operator/=(I i)
150 : {
151 : den_ *= i;
152 : reduce();
153 : return *this;
154 : }
155 :
156 : // Increment and decrement
157 : constexpr const rational& operator++()
158 : {
159 : num_ += den_;
160 : return *this;
161 : }
162 : constexpr const rational& operator--()
163 : {
164 : num_ -= den_;
165 : return *this;
166 : }
167 :
168 : // Operator not
169 159 : constexpr bool operator!() const { return !num_; };
170 :
171 : // Boolean conversion
172 407 : explicit constexpr operator bool() const { return num_; }
173 :
174 : // Comparison operators
175 297 : constexpr bool operator<(const rational& r) const
176 : {
177 297 : bool inv = (den_ > 0) != (r.den_ > 0);
178 297 : return inv != (num_ * r.den_ < den_ * r.num_);
179 : }
180 : constexpr bool operator>(const rational& r) const
181 : {
182 : bool inv = (den_ > 0) != (r.den_ > 0);
183 : return inv != (num_ * r.den_ > den_ * r.num_);
184 : }
185 0 : constexpr bool operator==(const rational& r) const { return num_ * r.den_ == den_ * r.num_; }
186 : constexpr bool operator!=(const rational& r) const { return num_ * r.den_ != den_ * r.num_; }
187 :
188 : // Comparison with integers
189 33 : constexpr bool operator<(I i) const { return den_ < 0 ? (num_ > i * den_) : (num_ < i * den_); }
190 : constexpr bool operator>(I i) const { return den_ < 0 ? (num_ < i * den_) : (num_ > i * den_); }
191 : constexpr bool operator==(I i) const { return num_ == i * den_; }
192 : constexpr bool operator!=(I i) const { return num_ != i * den_; }
193 :
194 : private:
195 : I num_ {0};
196 : I den_ {1};
197 :
198 910 : static constexpr I gcd(I a, I b) { return b == (I) 0 ? a : gcd(b, std::modulus<I>()(a, b)); }
199 474 : constexpr void reduce()
200 : {
201 : if constexpr (std::is_integral<I>::value) {
202 431 : if (num_ and den_) {
203 322 : auto g = gcd(num_ >= 0 ? num_ : -num_, den_ >= 0 ? den_ : -den_);
204 322 : if (g > (I) 1) {
205 0 : num_ /= g;
206 0 : den_ /= g;
207 : }
208 : }
209 : }
210 474 : }
211 : };
212 :
213 : // Unary operators
214 : template<typename I>
215 : rational<I>
216 : operator+(const rational<I>& r)
217 : {
218 : return r;
219 : }
220 : template<typename I>
221 : rational<I>
222 : operator-(const rational<I>& r)
223 : {
224 : return {-r.numerator(), r.denominator()};
225 : }
226 :
227 : // Reversed order operators for - and / between (types convertible to) I and rational
228 : template<typename I, typename II>
229 : inline rational<I> operator-(II i, const rational<I>& r);
230 : template<typename I, typename II>
231 : inline rational<I>
232 106 : operator/(II i, const rational<I>& r)
233 : {
234 106 : return {i * r.denominator(), r.numerator()};
235 : }
236 :
237 : // Absolute value
238 : template<typename I>
239 : rational<I>
240 : abs(const rational<I>& r)
241 : {
242 : return {std::abs(r.numerator()), std::abs(r.denominator())};
243 : }
244 :
245 : // Input and output
246 : template<typename I>
247 : std::istream&
248 : operator>>(std::istream& is, rational<I>& r)
249 : {
250 : char sep;
251 : is >> r.num_ >> sep >> r.den_;
252 : return is;
253 : }
254 :
255 : template<typename I>
256 : std::ostream&
257 0 : operator<<(std::ostream& os, const rational<I>& r)
258 : {
259 0 : os << r.numerator() << '/' << r.denominator();
260 0 : return os;
261 : }
262 :
263 : // Type conversion
264 : template<typename T, typename I>
265 : T rational_cast(const rational<I>& r);
266 :
267 : } // namespace jami
268 :
269 : namespace std {
270 : template<>
271 : struct modulus<double>
272 : {
273 : double operator()(const double& lhs, const double& rhs) const { return std::fmod(lhs, rhs); }
274 : };
275 : } // namespace std
276 :
277 : template<typename I>
278 : struct fmt::formatter<jami::rational<I>> : fmt::formatter<std::string_view> {
279 : template<typename FormatContext>
280 : auto format(const jami::rational<I>& r, FormatContext& ctx) const -> decltype(ctx.out()) {
281 : return fmt::formatter<std::string_view>::format(r.to_string(), ctx);
282 : }
283 : };
|