1 // Copyright 2016-2021 Francesco Biscani (bluescarni@gmail.com)
2 //
3 // This file is part of the mp++ library.
4 //
5 // This Source Code Form is subject to the terms of the Mozilla
6 // Public License v. 2.0. If a copy of the MPL was not distributed
7 // with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
9 #ifndef MPPP_RATIONAL_HPP
10 #define MPPP_RATIONAL_HPP
11
12 #include <mp++/config.hpp>
13
14 #include <algorithm>
15 #include <cassert>
16 #include <cmath>
17 #include <complex>
18 #include <cstddef>
19 #include <functional>
20 #include <iostream>
21 #include <limits>
22 #include <stdexcept>
23 #include <string>
24 #include <type_traits>
25 #include <utility>
26 #include <vector>
27
28 #if defined(MPPP_HAVE_STRING_VIEW)
29 #include <string_view>
30 #endif
31
32 #if defined(MPPP_WITH_BOOST_S11N)
33
34 #include <boost/archive/binary_iarchive.hpp>
35 #include <boost/serialization/access.hpp>
36 #include <boost/serialization/split_member.hpp>
37 #include <boost/serialization/tracking.hpp>
38
39 #endif
40
41 #include <mp++/concepts.hpp>
42 #include <mp++/detail/gmp.hpp>
43 #include <mp++/detail/type_traits.hpp>
44 #include <mp++/detail/utils.hpp>
45 #include <mp++/detail/visibility.hpp>
46 #include <mp++/exceptions.hpp>
47 #include <mp++/fwd.hpp>
48 #include <mp++/integer.hpp>
49 #include <mp++/type_name.hpp>
50
51 #if defined(MPPP_WITH_MPFR)
52 #include <mp++/detail/mpfr.hpp>
53 #endif
54
55 namespace mppp
56 {
57
58 template <typename T, std::size_t SSize>
59 using is_rational_interoperable
60 = detail::disjunction<is_integer_cpp_arithmetic<T>, std::is_same<T, integer<SSize>>, is_integer_cpp_complex<T>>;
61
62 #if defined(MPPP_HAVE_CONCEPTS)
63
64 template <typename T, std::size_t SSize>
65 MPPP_CONCEPT_DECL rational_interoperable = is_rational_interoperable<T, SSize>::value;
66
67 #endif
68
69 template <typename T, std::size_t SSize>
70 using is_rational_integral_interoperable = detail::disjunction<is_cpp_integral<T>, std::is_same<T, integer<SSize>>>;
71
72 #if defined(MPPP_HAVE_CONCEPTS)
73
74 template <typename T, std::size_t SSize>
75 MPPP_CONCEPT_DECL rational_integral_interoperable = is_rational_integral_interoperable<T, SSize>::value;
76
77 #endif
78
79 template <typename T, std::size_t SSize>
80 using is_rational_cvr_interoperable = is_rational_interoperable<detail::uncvref_t<T>, SSize>;
81
82 #if defined(MPPP_HAVE_CONCEPTS)
83
84 template <typename T, std::size_t SSize>
85 MPPP_CONCEPT_DECL rational_cvr_interoperable = is_rational_cvr_interoperable<T, SSize>::value;
86
87 #endif
88
89 template <typename T, std::size_t SSize>
90 using is_rational_cvr_integral_interoperable = is_rational_integral_interoperable<detail::uncvref_t<T>, SSize>;
91
92 #if defined(MPPP_HAVE_CONCEPTS)
93
94 template <typename T, std::size_t SSize>
95 MPPP_CONCEPT_DECL rational_cvr_integral_interoperable = is_rational_cvr_integral_interoperable<T, SSize>::value;
96
97 #endif
98
99 namespace detail
100 {
101
102 // This is useful in various bits below.
103 template <std::size_t SSize>
fix_den_sign(rational<SSize> & q)104 inline void fix_den_sign(rational<SSize> &q)
105 {
106 if (q.get_den().sgn() == -1) {
107 q._get_num().neg();
108 q._get_den().neg();
109 }
110 }
111
112 // Detect rational.
113 template <typename T>
114 struct is_rational : std::false_type {
115 };
116
117 template <std::size_t SSize>
118 struct is_rational<rational<SSize>> : std::true_type {
119 };
120
121 // Detect rationals with the same static size.
122 template <typename T, typename U>
123 struct is_same_ssize_rational : std::false_type {
124 };
125
126 template <std::size_t SSize>
127 struct is_same_ssize_rational<rational<SSize>, rational<SSize>> : std::true_type {
128 };
129
130 // mpq_view getter fwd declaration.
131 // NOTE: the returned mpq_struct_t should always be marked as const, as we cannot modify its content
132 // (due to the use of const_cast() within the mpz view machinery).
133 template <std::size_t SSize>
134 mpq_struct_t get_mpq_view(const rational<SSize> &);
135
136 } // namespace detail
137
138 // Multiprecision rational class.
139 // NOTEs:
140 // - not clear if the NewRop flag helps at all. Needs to be benchmarked. If it does, its usage could
141 // be expanded in mul/div.
142 // - we might be paying a perf penalty for dynamic storage values due to the lack of pre-allocation for
143 // temporary variables in algorithms such as addsub, mul, div, etc. Needs to be investigated.
144 // - in the algorithm bits derived from GMP ones, we don't check for unitary GCDs because the original
145 // algos do not. We should investigate if there's perf benefits to be had there.
146 // - cmp() can be improved (see comments there).
147 template <std::size_t SSize>
148 class rational
149 {
150 #if defined(MPPP_WITH_BOOST_S11N)
151 // Boost serialization support.
152 friend class boost::serialization::access;
153
154 template <typename Archive>
save(Archive & ar,unsigned) const155 void save(Archive &ar, unsigned) const
156 {
157 ar << get_num();
158 ar << get_den();
159 }
160
161 template <typename Archive>
load(Archive & ar,unsigned)162 void load(Archive &ar, unsigned)
163 {
164 // NOTE: use tmp variables
165 // for exception safety.
166 int_t num, den;
167
168 ar >> num;
169 ar >> den;
170
171 *this = rational{std::move(num), std::move(den)};
172 }
load(boost::archive::binary_iarchive & ar,unsigned)173 void load(boost::archive::binary_iarchive &ar, unsigned)
174 {
175 // NOTE: for the binary archive, avoid calling the constructor,
176 // but still do it in 2 stages for exception safety.
177 int_t num, den;
178
179 ar >> num;
180 ar >> den;
181
182 _get_num() = std::move(num);
183 _get_den() = std::move(den);
184 }
185
186 BOOST_SERIALIZATION_SPLIT_MEMBER()
187 #endif
188
189 public:
190 // Underlying integral type.
191 using int_t = integer<SSize>;
192 // Alias for the template parameter SSize.
193 static constexpr std::size_t ssize = SSize;
194 // Default constructor.
rational()195 rational() : m_den(1u) {}
196 // Defaulted copy constructor.
197 rational(const rational &) = default;
198 // Move constructor.
rational(rational && other)199 rational(rational &&other) noexcept : m_num(std::move(other.m_num)), m_den(std::move(other.m_den))
200 {
201 // NOTE: the aim of this is to have other in a valid state. One reason is that,
202 // according to the standard, for use in std::sort() (and possibly other algorithms)
203 // it is required that a moved-from rational is still comparable, but if we simply move
204 // construct num and den we could end up with other in noncanonical form or zero den.
205 // http://stackoverflow.com/questions/26579132/what-is-the-post-condition-of-a-move-constructor
206 //
207 // NOTE: after move construction, other's data members are guaranteed to be static.
208 assert(other.m_num.is_static());
209 assert(other.m_den.is_static());
210 // Set other's numerator to 0 (see integer::set_zero()).
211 other.m_num.m_int.g_st()._mp_size = 0;
212 other.m_num.m_int.g_st().zero_upper_limbs(0);
213 // Set other's denominator to 1 (see integer::set_one()).
214 other.m_den.m_int.g_st()._mp_size = 1;
215 other.m_den.m_int.g_st().m_limbs[0] = 1;
216 other.m_den.m_int.g_st().zero_upper_limbs(1);
217 }
218
219 private:
220 // A tag for private constructors.
221 struct ptag {
222 };
223 template <typename T,
224 detail::enable_if_t<detail::disjunction<std::is_same<float, T>, std::is_same<double, T>>::value, int> = 0>
rational(const ptag &,const T & x)225 explicit rational(const ptag &, const T &x)
226 {
227 if (mppp_unlikely(!std::isfinite(x))) {
228 throw std::domain_error("Cannot construct a rational from the non-finite floating-point value "
229 + detail::to_string(x));
230 }
231 MPPP_MAYBE_TLS detail::mpq_raii q;
232 mpq_set_d(&q.m_mpq, static_cast<double>(x));
233 m_num = mpq_numref(&q.m_mpq);
234 m_den = mpq_denref(&q.m_mpq);
235 }
236 #if defined(MPPP_WITH_MPFR)
rational(const ptag &,const long double & x)237 explicit rational(const ptag &, const long double &x)
238 {
239 if (mppp_unlikely(!std::isfinite(x))) {
240 throw std::domain_error("Cannot construct a rational from the non-finite floating-point value "
241 + detail::to_string(x));
242 }
243 // NOTE: static checks for overflows and for the precision value are done in mpfr.hpp.
244 constexpr int d2 = std::numeric_limits<long double>::max_digits10 * 4;
245 MPPP_MAYBE_TLS detail::mpfr_raii mpfr(static_cast<::mpfr_prec_t>(d2));
246 MPPP_MAYBE_TLS detail::mpf_raii mpf(static_cast<::mp_bitcnt_t>(d2));
247 MPPP_MAYBE_TLS detail::mpq_raii mpq;
248 // NOTE: we go through an mpfr->mpf->mpq conversion chain as
249 // mpfr_get_q() does not exist.
250 // NOTE: probably coming in MPFR 4:
251 // https://lists.gforge.inria.fr/pipermail/mpfr-commits/2017-June/011186.html
252 ::mpfr_set_ld(&mpfr.m_mpfr, x, MPFR_RNDN);
253 ::mpfr_get_f(&mpf.m_mpf, &mpfr.m_mpfr, MPFR_RNDN);
254 mpq_set_f(&mpq.m_mpq, &mpf.m_mpf);
255 m_num = mpq_numref(&mpq.m_mpq);
256 m_den = mpq_denref(&mpq.m_mpq);
257 }
258 #endif
259 template <typename T, detail::enable_if_t<is_rational_cvr_integral_interoperable<T, SSize>::value, int> = 0>
rational(const ptag &,T && n)260 explicit rational(const ptag &, T &&n) : m_num(std::forward<T>(n)), m_den(1u)
261 {
262 }
263 // Constructor from std::complex.
264 template <typename T>
rational(const ptag & p,const std::complex<T> & c)265 explicit rational(const ptag &p, const std::complex<T> &c)
266 : rational(p, c.imag() == 0
267 ? c.real()
268 : throw std::domain_error(
269 "Cannot construct a rational from a complex C++ value with a non-zero imaginary part of "
270 + detail::to_string(c.imag())))
271 {
272 }
273
274 public:
275 // Generic constructor.
276 #if defined(MPPP_HAVE_CONCEPTS)
277 template <typename T>
278 requires rational_cvr_interoperable<T, SSize> &&(!rational_integral_interoperable<T, SSize>)
279 #else
280 template <
281 typename T,
282 detail::enable_if_t<detail::conjunction<is_rational_cvr_interoperable<T, SSize>,
283 detail::negation<is_rational_integral_interoperable<T, SSize>>>::value,
284 int> = 0>
285 #endif
286 // NOLINTNEXTLINE(bugprone-forwarding-reference-overload)
rational(T && x)287 explicit rational(T &&x)
288 : rational(ptag{}, std::forward<T>(x))
289 {
290 }
291 #if defined(MPPP_HAVE_CONCEPTS)
292 template <typename T>
293 requires rational_cvr_interoperable<T, SSize> && rational_integral_interoperable<T, SSize>
294 #else
295 template <typename T, detail::enable_if_t<detail::conjunction<is_rational_cvr_interoperable<T, SSize>,
296 is_rational_integral_interoperable<T, SSize>>::value,
297 int> = 0>
298 #endif
299 // NOLINTNEXTLINE(bugprone-forwarding-reference-overload)
rational(T && x)300 rational(T &&x) : rational(ptag{}, std::forward<T>(x))
301 {
302 }
303 // Constructor from numerator and denominator.
304 #if defined(MPPP_HAVE_CONCEPTS)
305 template <rational_cvr_integral_interoperable<SSize> T, rational_cvr_integral_interoperable<SSize> U>
306 #else
307 template <typename T, typename U,
308 detail::enable_if_t<detail::conjunction<is_rational_cvr_integral_interoperable<T, SSize>,
309 is_rational_cvr_integral_interoperable<U, SSize>>::value,
310 int> = 0>
311 #endif
rational(T && n,U && d,bool make_canonical=true)312 explicit rational(T &&n, U &&d, bool make_canonical = true) : m_num(std::forward<T>(n)), m_den(std::forward<U>(d))
313 {
314 if (mppp_unlikely(m_den.is_zero())) {
315 throw zero_division_error("Cannot construct a rational with zero as denominator");
316 }
317 if (make_canonical) {
318 canonicalise();
319 }
320 }
321
322 private:
323 // Implementation of the constructor from C string. Requires a def-cted object.
dispatch_c_string_ctor(const char * s,int base)324 void dispatch_c_string_ctor(const char *s, int base)
325 {
326 MPPP_MAYBE_TLS std::string tmp_str;
327 // NOLINTNEXTLINE(llvm-qualified-auto, readability-qualified-auto)
328 auto ptr = s;
329 for (; *ptr != '\0' && *ptr != '/'; ++ptr) {
330 }
331 tmp_str.assign(s, ptr);
332 m_num = int_t{tmp_str, base};
333 if (*ptr != '\0') {
334 tmp_str.assign(ptr + 1);
335 m_den = int_t{tmp_str, base};
336 if (mppp_unlikely(m_den.is_zero())) {
337 throw zero_division_error(
338 "A zero denominator was detected in the constructor of a rational from string");
339 }
340 canonicalise();
341 }
342 }
rational(const ptag &,const char * s,int base)343 explicit rational(const ptag &, const char *s, int base) : m_den(1u)
344 {
345 dispatch_c_string_ctor(s, base);
346 }
rational(const ptag &,const std::string & s,int base)347 explicit rational(const ptag &, const std::string &s, int base) : rational(s.c_str(), base) {}
348 #if defined(MPPP_HAVE_STRING_VIEW)
rational(const ptag &,const std::string_view & s,int base)349 explicit rational(const ptag &, const std::string_view &s, int base) : rational(s.data(), s.data() + s.size(), base)
350 {
351 }
352 #endif
353
354 public:
355 // Constructor from string.
356 #if defined(MPPP_HAVE_CONCEPTS)
357 template <string_type T>
358 #else
359 template <typename T, detail::enable_if_t<is_string_type<T>::value, int> = 0>
360 #endif
rational(const T & s,int base=10)361 explicit rational(const T &s, int base = 10) : rational(ptag{}, s, base)
362 {
363 }
364 // Constructor from range of characters.
rational(const char * begin,const char * end,int base=10)365 explicit rational(const char *begin, const char *end, int base = 10) : m_den(1u)
366 {
367 // Copy the range into a local buffer.
368 MPPP_MAYBE_TLS std::vector<char> buffer;
369 buffer.assign(begin, end);
370 buffer.emplace_back('\0');
371 dispatch_c_string_ctor(buffer.data(), base);
372 }
373 // Constructor from mpz_t.
rational(const::mpz_t n)374 explicit rational(const ::mpz_t n) : m_num(n), m_den(1u) {}
375 // Constructor from mpq_t.
rational(const::mpq_t q)376 explicit rational(const ::mpq_t q) : m_num(mpq_numref(q)), m_den(mpq_denref(q)) {}
377 #if !defined(_MSC_VER)
378 // Move constructor from mpq_t.
rational(::mpq_t && q)379 explicit rational(::mpq_t &&q) : m_num(::mpz_t{*mpq_numref(q)}), m_den(::mpz_t{*mpq_denref(q)}) {}
380 #endif
381
382 ~rational() = default;
383
384 // Copy-assignment operator.
385 // NOTE: as long as copy assignment of integer cannot
386 // throw, the default is good.
387 rational &operator=(const rational &) = default;
388 // Move assignment operator.
operator =(rational && other)389 rational &operator=(rational &&other) noexcept
390 {
391 // NOTE: see the rationale in the move ctor about why we don't want
392 // to default this.
393 //
394 // NOTE: we need the self check here because otherwise we will end
395 // up setting this to 0/1, in case of self assignment.
396 if (mppp_likely(this != &other)) {
397 m_num = std::move(other.m_num);
398 m_den = std::move(other.m_den);
399 other.m_num.set_zero();
400 other.m_den.set_one();
401 }
402 return *this;
403 }
404
405 private:
406 // Implementation of the generic assignment operator.
407 template <typename T>
dispatch_assignment(T && n,const std::true_type &)408 void dispatch_assignment(T &&n, const std::true_type &)
409 {
410 m_num = std::forward<T>(n);
411 m_den.set_one();
412 }
413 template <typename T>
dispatch_assignment(const T & x,const std::false_type &)414 void dispatch_assignment(const T &x, const std::false_type &)
415 {
416 *this = rational{x};
417 }
418
419 public:
420 // Generic assignment operator.
421 #if defined(MPPP_HAVE_CONCEPTS)
422 template <rational_cvr_interoperable<SSize> T>
423 #else
424 template <typename T, detail::enable_if_t<is_rational_cvr_interoperable<T, SSize>::value, int> = 0>
425 #endif
426 // NOLINTNEXTLINE(cppcoreguidelines-c-copy-assignment-signature, misc-unconventional-assign-operator)
operator =(T && x)427 rational &operator=(T &&x)
428 {
429 dispatch_assignment(std::forward<T>(x),
430 std::integral_constant<bool, is_rational_cvr_integral_interoperable<T, SSize>::value>{});
431 return *this;
432 }
433
434 // Declaration of the assignments from
435 // other mp++ classes.
436 #if defined(MPPP_WITH_QUADMATH)
437 rational &operator=(const real128 &);
438 rational &operator=(const complex128 &);
439 #endif
440 #if defined(MPPP_WITH_MPFR)
441 rational &operator=(const real &);
442 #endif
443 #if defined(MPPP_WITH_MPC)
444 rational &operator=(const complex &);
445 #endif
446
447 // Assignment from string.
448 #if defined(MPPP_HAVE_CONCEPTS)
449 template <string_type T>
450 #else
451 template <typename T, detail::enable_if_t<is_string_type<T>::value, int> = 0>
452 #endif
operator =(const T & s)453 rational &operator=(const T &s)
454 {
455 // NOLINTNEXTLINE(cppcoreguidelines-c-copy-assignment-signature, misc-unconventional-assign-operator)
456 return *this = rational{s};
457 }
458 // Assignment from mpz_t.
operator =(const::mpz_t n)459 rational &operator=(const ::mpz_t n)
460 {
461 m_num = n;
462 m_den.set_one();
463 return *this;
464 }
465 // Assignment from mpq_t.
operator =(const::mpq_t q)466 rational &operator=(const ::mpq_t q)
467 {
468 m_num = mpq_numref(q);
469 m_den = mpq_denref(q);
470 return *this;
471 }
472 #if !defined(_MSC_VER)
473 // Move assignment from mpq_t.
operator =(::mpq_t && q)474 rational &operator=(::mpq_t &&q)
475 {
476 m_num = ::mpz_t{*mpq_numref(q)};
477 m_den = ::mpz_t{*mpq_denref(q)};
478 return *this;
479 }
480 #endif
481 // Convert to string.
to_string(int base=10) const482 MPPP_NODISCARD std::string to_string(int base = 10) const
483 {
484 if (m_den.is_one()) {
485 return m_num.to_string(base);
486 }
487 return m_num.to_string(base) + "/" + m_den.to_string(base);
488 }
489
490 private:
491 // Conversion to int_t.
492 template <typename T, detail::enable_if_t<std::is_same<int_t, T>::value, int> = 0>
dispatch_conversion() const493 MPPP_NODISCARD std::pair<bool, T> dispatch_conversion() const
494 {
495 return std::make_pair(true, m_num / m_den);
496 }
497 // Conversion to bool.
498 template <typename T, detail::enable_if_t<std::is_same<bool, T>::value, int> = 0>
dispatch_conversion() const499 MPPP_NODISCARD std::pair<bool, T> dispatch_conversion() const
500 {
501 return std::make_pair(true, m_num.m_int.m_st._mp_size != 0);
502 }
503 // Conversion to integral types other than bool.
504 template <typename T,
505 detail::enable_if_t<
506 detail::conjunction<detail::is_integral<T>, detail::negation<std::is_same<bool, T>>>::value, int> = 0>
dispatch_conversion() const507 MPPP_NODISCARD std::pair<bool, T> dispatch_conversion() const
508 {
509 return static_cast<int_t>(*this).template dispatch_conversion<T>();
510 }
511 // Conversion to float/double.
512 template <typename T,
513 detail::enable_if_t<detail::disjunction<std::is_same<T, float>, std::is_same<T, double>>::value, int> = 0>
dispatch_conversion() const514 MPPP_NODISCARD std::pair<bool, T> dispatch_conversion() const
515 {
516 const auto v = detail::get_mpq_view(*this);
517 return std::make_pair(true, static_cast<T>(mpq_get_d(&v)));
518 }
519 #if defined(MPPP_WITH_MPFR)
520 // Conversion to long double.
521 template <typename T, detail::enable_if_t<std::is_same<T, long double>::value, int> = 0>
dispatch_conversion() const522 MPPP_NODISCARD std::pair<bool, T> dispatch_conversion() const
523 {
524 constexpr int d2 = std::numeric_limits<long double>::max_digits10 * 4;
525 MPPP_MAYBE_TLS detail::mpfr_raii mpfr(static_cast<::mpfr_prec_t>(d2));
526 const auto v = detail::get_mpq_view(*this);
527 ::mpfr_set_q(&mpfr.m_mpfr, &v, MPFR_RNDN);
528 return std::make_pair(true, ::mpfr_get_ld(&mpfr.m_mpfr, MPFR_RNDN));
529 }
530 #endif
531 // Conversion to std::complex.
532 template <typename T, detail::enable_if_t<is_cpp_complex<T>::value, int> = 0>
dispatch_conversion() const533 MPPP_NODISCARD std::pair<bool, T> dispatch_conversion() const
534 {
535 return std::make_pair(true, T(static_cast<typename T::value_type>(*this)));
536 }
537
538 public:
539 // Generic conversion operator.
540 #if defined(MPPP_HAVE_CONCEPTS)
541 template <rational_interoperable<SSize> T>
542 #else
543 template <typename T, detail::enable_if_t<is_rational_interoperable<T, SSize>::value, int> = 0>
544 #endif
operator T() const545 explicit operator T() const
546 {
547 auto retval = dispatch_conversion<T>();
548 if (mppp_unlikely(!retval.first)) {
549 throw std::overflow_error("Conversion of the rational " + to_string() + " to the type '" + type_name<T>()
550 + "' results in overflow");
551 }
552 return std::move(retval.second);
553 }
554
555 private:
556 // int_t getter.
dispatch_get(int_t & rop) const557 bool dispatch_get(int_t &rop) const
558 {
559 return tdiv_q(rop, m_num, m_den), true;
560 }
561 // The other getters.
562 template <typename T>
dispatch_get(T & rop) const563 bool dispatch_get(T &rop) const
564 {
565 auto retval = dispatch_conversion<T>();
566 if (retval.first) {
567 rop = std::move(retval.second);
568 return true;
569 }
570 return false;
571 }
572
573 public:
574 // Generic conversion member function.
575 #if defined(MPPP_HAVE_CONCEPTS)
576 template <rational_interoperable<SSize> T>
577 #else
578 template <typename T, detail::enable_if_t<is_rational_interoperable<T, SSize>::value, int> = 0>
579 #endif
get(T & rop) const580 bool get(T &rop) const
581 {
582 return dispatch_get(rop);
583 }
584 // Const numerator getter.
get_num() const585 MPPP_NODISCARD const int_t &get_num() const
586 {
587 return m_num;
588 }
589 // Const denominator getter.
get_den() const590 MPPP_NODISCARD const int_t &get_den() const
591 {
592 return m_den;
593 }
594 // Mutable numerator getter.
_get_num()595 int_t &_get_num()
596 {
597 return m_num;
598 }
599 // Mutable denominator getter.
_get_den()600 int_t &_get_den()
601 {
602 return m_den;
603 }
604 // Canonicalise.
canonicalise()605 rational &canonicalise()
606 {
607 if (m_num.is_zero()) {
608 m_den = 1;
609 return *this;
610 }
611 // NOTE: this is best in case of small m_num/m_den.
612 // For dynamically allocated num/den, it would be better
613 // to have a TLS integer and re-use that accross calls to
614 // canonicalise. Eventually, we could consider branching
615 // this bit out depending on whether num/den are static
616 // or not. Let's keep it simple for now.
617 // NOTE: gcd() always gets a positive value.
618 const auto g = gcd(m_num, m_den);
619 // This can be zero only if both num and den are zero.
620 assert(!g.is_zero());
621 if (!g.is_one()) {
622 divexact_gcd(m_num, m_num, g);
623 divexact_gcd(m_den, m_den, g);
624 }
625 // Fix mismatch in signs.
626 detail::fix_den_sign(*this);
627 // NOTE: consider attempting demoting num/den. Let's KIS for now.
628 return *this;
629 }
630 // Check canonical form.
is_canonical() const631 MPPP_NODISCARD bool is_canonical() const
632 {
633 if (m_num.is_zero()) {
634 // If num is zero, den must be one.
635 return m_den.is_one();
636 }
637 if (m_den.sgn() != 1) {
638 // Den must be strictly positive.
639 return false;
640 }
641 if (m_den.is_one()) {
642 // The rational is an integer.
643 return true;
644 }
645 // Num and den must be coprime.
646 MPPP_MAYBE_TLS int_t g;
647 gcd(g, m_num, m_den);
648 return g.is_one();
649 }
650 // Sign.
sgn() const651 MPPP_NODISCARD int sgn() const
652 {
653 return mppp::sgn(m_num);
654 }
655 // Negate in-place.
neg()656 rational &neg()
657 {
658 m_num.neg();
659 return *this;
660 }
661 // In-place absolute value.
abs()662 rational &abs()
663 {
664 m_num.abs();
665 return *this;
666 }
667 // In-place inversion.
inv()668 rational &inv()
669 {
670 if (mppp_unlikely(is_zero())) {
671 throw zero_division_error("Cannot invert a zero rational");
672 }
673 swap(m_num, m_den);
674 detail::fix_den_sign(*this);
675 return *this;
676 }
677 // Test if the value is zero.
is_zero() const678 MPPP_NODISCARD bool is_zero() const
679 {
680 return mppp::is_zero(m_num);
681 }
682 // Test if the value is one.
is_one() const683 MPPP_NODISCARD bool is_one() const
684 {
685 return mppp::is_one(m_num) && mppp::is_one(m_den);
686 }
687 // Test if the value is minus one.
is_negative_one() const688 MPPP_NODISCARD bool is_negative_one() const
689 {
690 return mppp::is_negative_one(m_num) && mppp::is_one(m_den);
691 }
692
693 private:
694 int_t m_num;
695 int_t m_den;
696 };
697
698 #if MPPP_CPLUSPLUS < 201703L
699
700 // NOTE: see the explanation in integer.hpp regarding static constexpr variables in C++17.
701
702 template <std::size_t SSize>
703 constexpr std::size_t rational<SSize>::ssize;
704
705 #endif
706
707 // Swap.
708 template <std::size_t SSize>
swap(rational<SSize> & q1,rational<SSize> & q2)709 inline void swap(rational<SSize> &q1, rational<SSize> &q2) noexcept
710 {
711 swap(q1._get_num(), q2._get_num());
712 swap(q1._get_den(), q2._get_den());
713 }
714
715 namespace detail
716 {
717
718 // Let's keep the view machinery private for now, as it suffers from the potential aliasing
719 // issues described in the mpz_view documentation. In this case, we have to fill in a shallow mpq_struct
720 // in any case, as the rational class is not backed by a regular mpq_t. We could then have aliasing
721 // between a real mpz_t and, say, the numerator extracted from a view which is internally pointing
722 // to the same mpz_t. Example:
723 //
724 // rational q;
725 // auto &n = q._get_num();
726 // n.promote();
727 // const auto q_view = get_mpq_view(q);
728 // mpz_add_ui(n.get_mpz_t(), mpq_numref(&q_view), 1);
729 //
730 // In the last line, mpq_numref() is extracting an mpz_struct from the view which internally
731 // points to the same limbs as the mpz_t inside n. The mpz_add_ui code will try to realloc()
732 // the limb pointer inside n, thus invalidating the limb pointer in the view (this would work
733 // if the view's mpz_struct were the same object as n's mpz_t, as the GMP code would update
734 // the new pointer after the realloc for both structs).
735 template <std::size_t SSize>
get_mpq_view(const rational<SSize> & q)736 inline mpq_struct_t get_mpq_view(const rational<SSize> &q)
737 {
738 return mpq_struct_t{*q.get_num().get_mpz_view().get(), *q.get_den().get_mpz_view().get()};
739 }
740
741 // Machinery for the determination of the result of a binary operation involving rational.
742 // Default is empty for SFINAE.
743 template <typename, typename, typename = void>
744 struct rational_common_type {
745 };
746
747 template <std::size_t SSize>
748 struct rational_common_type<rational<SSize>, rational<SSize>> {
749 using type = rational<SSize>;
750 };
751
752 template <std::size_t SSize>
753 struct rational_common_type<rational<SSize>, integer<SSize>> {
754 using type = rational<SSize>;
755 };
756
757 template <std::size_t SSize>
758 struct rational_common_type<integer<SSize>, rational<SSize>> {
759 using type = rational<SSize>;
760 };
761
762 template <std::size_t SSize, typename U>
763 struct rational_common_type<rational<SSize>, U, enable_if_t<is_cpp_integral<U>::value>> {
764 using type = rational<SSize>;
765 };
766
767 template <std::size_t SSize, typename T>
768 struct rational_common_type<T, rational<SSize>, enable_if_t<is_cpp_integral<T>::value>> {
769 using type = rational<SSize>;
770 };
771
772 template <std::size_t SSize, typename U>
773 struct rational_common_type<
774 rational<SSize>, U, enable_if_t<disjunction<is_integer_cpp_floating_point<U>, is_integer_cpp_complex<U>>::value>> {
775 using type = U;
776 };
777
778 template <std::size_t SSize, typename T>
779 struct rational_common_type<
780 T, rational<SSize>, enable_if_t<disjunction<is_integer_cpp_floating_point<T>, is_integer_cpp_complex<T>>::value>> {
781 using type = T;
782 };
783
784 template <typename T, typename U>
785 using rational_common_t = typename rational_common_type<T, U>::type;
786
787 // Implementation of binary add/sub. The NewRop flag indicates that
788 // rop is a def-cted rational distinct from op1 and op2.
789 template <bool AddOrSub, bool NewRop, std::size_t SSize>
addsub_impl(rational<SSize> & rop,const rational<SSize> & op1,const rational<SSize> & op2)790 inline void addsub_impl(rational<SSize> &rop, const rational<SSize> &op1, const rational<SSize> &op2)
791 {
792 assert(!NewRop || (rop.is_zero() && &rop != &op1 && &rop != &op2));
793 const bool u1 = op1.get_den().is_one(), u2 = op2.get_den().is_one();
794 // NOTE: it's important here to take care about overlapping arguments: we cannot use
795 // rop as a "temporary" storage space, because if it overlaps with op1/op2 we will be
796 // altering op1/op2 as well.
797 if (u1 && u2) {
798 // add/sub() are fine with overlapping args.
799 AddOrSub ? add(rop._get_num(), op1.get_num(), op2.get_num())
800 : sub(rop._get_num(), op1.get_num(), op2.get_num());
801 if (!NewRop) {
802 // Set rop's den to 1, if rop is not new (otherwise it's 1 already).
803 rop._get_den().set_one();
804 }
805 } else if (u1) {
806 if (NewRop) {
807 // rop is separate, can write into it directly.
808 rop._get_num() = op2.get_num();
809 if (AddOrSub) {
810 addmul(rop._get_num(), op1.get_num(), op2.get_den());
811 } else {
812 submul(rop._get_num(), op1.get_num(), op2.get_den());
813 rop._get_num().neg();
814 }
815 // NOTE: gcd(a+m*b,b) == gcd(a,b) for every integer m, no need to canonicalise the result.
816 rop._get_den() = op2.get_den();
817 } else {
818 integer<SSize> tmp{op2.get_num()};
819 // Ok, tmp is a separate variable, won't modify ops.
820 if (AddOrSub) {
821 addmul(tmp, op1.get_num(), op2.get_den());
822 } else {
823 submul(tmp, op1.get_num(), op2.get_den());
824 tmp.neg();
825 }
826 // Final assignments, potential for self-assignment
827 // in the second one.
828 rop._get_num() = std::move(tmp);
829 rop._get_den() = op2.get_den();
830 }
831 } else if (u2) {
832 // Mirror of the above.
833 if (NewRop) {
834 rop._get_num() = op1.get_num();
835 AddOrSub ? addmul(rop._get_num(), op2.get_num(), op1.get_den())
836 : submul(rop._get_num(), op2.get_num(), op1.get_den());
837 rop._get_den() = op1.get_den();
838 } else {
839 integer<SSize> tmp{op1.get_num()};
840 AddOrSub ? addmul(tmp, op2.get_num(), op1.get_den()) : submul(tmp, op2.get_num(), op1.get_den());
841 rop._get_num() = std::move(tmp);
842 rop._get_den() = op1.get_den();
843 }
844 } else if (op1.get_den() == op2.get_den()) {
845 // add()/sub() are fine with overlapping args.
846 AddOrSub ? add(rop._get_num(), op1.get_num(), op2.get_num())
847 : sub(rop._get_num(), op1.get_num(), op2.get_num());
848 // Set rop's den to the common den.
849 rop._get_den() = op1.get_den();
850 rop.canonicalise();
851 } else {
852 // NOTE: the algorithm here is taken from GMP's aors.c for mpq. The idea
853 // is, as usual, to avoid large canonicalisations and to try to keep
854 // the values as small as possible at every step. We need to do some
855 // testing about this, as I am not 100% sure that this is going to be
856 // a win for our small-operands focus. Let's bookmark here the previous
857 // implementation, at git commit:
858 // a8a397d67d6e2af43592aa99061016398a1457ad
859 auto g = gcd(op1.get_den(), op2.get_den());
860 if (g.is_one()) {
861 // This is the case in which the two dens are coprime.
862 AddOrSub ? add(rop._get_num(), op1.get_num() * op2.get_den(), op2.get_num() * op1.get_den())
863 : sub(rop._get_num(), op1.get_num() * op2.get_den(), op2.get_num() * op1.get_den());
864 mul(rop._get_den(), op1.get_den(), op2.get_den());
865 } else {
866 // Eliminate common factors between the dens.
867 auto t = divexact_gcd(op2.get_den(), g);
868 auto tmp2 = divexact_gcd(op1.get_den(), g);
869
870 // Compute the numerator (will be t).
871 auto tmp1 = op1.get_num() * t;
872 mul(t, op2.get_num(), tmp2);
873 AddOrSub ? add(t, tmp1, t) : sub(t, tmp1, t);
874
875 // Check if the numerator and the den GCD are coprime.
876 gcd(g, t, g);
877 if (g.is_one()) {
878 // They are coprime: assign the num and compute the final den.
879 rop._get_num() = std::move(t);
880 mul(rop._get_den(), op2.get_den(), tmp2);
881 } else {
882 // Assign numerator, reduced by the new gcd.
883 divexact_gcd(rop._get_num(), t, g);
884 // Reduced version of the second den.
885 divexact_gcd(tmp1, op2.get_den(), g);
886 // Assign final den: tmp1 x the reduced den1.
887 mul(rop._get_den(), tmp1, tmp2);
888 }
889 }
890 }
891 }
892 } // namespace detail
893
894 // Implementation of the rational op types concept, used in various operators.
895 template <typename T, typename U>
896 using are_rational_op_types = detail::is_detected<detail::rational_common_t, T, U>;
897
898 #if defined(MPPP_HAVE_CONCEPTS)
899
900 template <typename T, typename U>
901 MPPP_CONCEPT_DECL rational_op_types = are_rational_op_types<T, U>::value;
902
903 #endif
904
905 template <typename T, typename U>
906 using are_rational_real_op_types
907 = detail::conjunction<are_rational_op_types<T, U>, detail::negation<is_integer_cpp_complex<T>>,
908 detail::negation<is_integer_cpp_complex<U>>>;
909
910 #if defined(MPPP_HAVE_CONCEPTS)
911
912 template <typename T, typename U>
913 MPPP_CONCEPT_DECL rational_real_op_types = are_rational_real_op_types<T, U>::value;
914
915 #endif
916
917 // Generic conversion function.
918 #if defined(MPPP_HAVE_CONCEPTS)
919 template <std::size_t SSize, rational_interoperable<SSize> T>
920 #else
921 template <typename T, std::size_t SSize, detail::enable_if_t<is_rational_interoperable<T, SSize>::value, int> = 0>
922 #endif
get(T & rop,const rational<SSize> & q)923 inline bool get(T &rop, const rational<SSize> &q)
924 {
925 return q.get(rop);
926 }
927
928 // Ternary addition.
929 template <std::size_t SSize>
add(rational<SSize> & rop,const rational<SSize> & op1,const rational<SSize> & op2)930 inline rational<SSize> &add(rational<SSize> &rop, const rational<SSize> &op1, const rational<SSize> &op2)
931 {
932 detail::addsub_impl<true, false>(rop, op1, op2);
933 return rop;
934 }
935
936 // Ternary subtraction.
937 template <std::size_t SSize>
sub(rational<SSize> & rop,const rational<SSize> & op1,const rational<SSize> & op2)938 inline rational<SSize> &sub(rational<SSize> &rop, const rational<SSize> &op1, const rational<SSize> &op2)
939 {
940 detail::addsub_impl<false, false>(rop, op1, op2);
941 return rop;
942 }
943
944 namespace detail
945 {
946 template <bool NewRop, std::size_t SSize>
mul_impl(rational<SSize> & rop,const rational<SSize> & op1,const rational<SSize> & op2)947 inline void mul_impl(rational<SSize> &rop, const rational<SSize> &op1, const rational<SSize> &op2)
948 {
949 assert(!NewRop || rop.is_zero());
950 const bool u1 = op1.get_den().is_one(), u2 = op2.get_den().is_one();
951 // NOTE: it's important here to take care about overlapping arguments: we cannot use
952 // rop as a "temporary" storage space, because if it overlaps with op1/op2 we will be
953 // altering op1/op2 as well.
954 if (u1 && u2) {
955 // mul() is fine with overlapping args.
956 mul(rop._get_num(), op1.get_num(), op2.get_num());
957 if (!NewRop) {
958 // Set rop's den to 1, if rop is not a new value (in that case, den is 1 already).
959 rop._get_den().set_one();
960 }
961 } else if (op1.get_den() == op2.get_den()) {
962 // Special case: equal dens do not require canonicalisation.
963 mul(rop._get_num(), op1.get_num(), op2.get_num());
964 // NOTE: we can use a squaring function here, once implemented in integer.
965 mul(rop._get_den(), op1.get_den(), op2.get_den());
966 } else if (u1) {
967 // This is a * (b/c). Instead of doing (ab)/c and then canonicalise,
968 // we remove the common factors from a and c and we perform
969 // a normal multiplication (without canonicalisation). This allows us to
970 // perform a gcd with smaller operands.
971 auto g = gcd(op1.get_num(), op2.get_den());
972 if (g.is_one()) {
973 // NOTE: after this line, all nums are tainted.
974 mul(rop._get_num(), op2.get_num(), op1.get_num());
975 rop._get_den() = op2.get_den();
976 } else {
977 // NOTE: after this line, all dens are tainted.
978 divexact_gcd(rop._get_den(), op2.get_den(), g);
979 // Re-use g.
980 divexact_gcd(g, op1.get_num(), g);
981 mul(rop._get_num(), op2.get_num(), g);
982 }
983 } else if (u2) {
984 // Mirror of the above.
985 auto g = gcd(op2.get_num(), op1.get_den());
986 if (g.is_one()) {
987 mul(rop._get_num(), op1.get_num(), op2.get_num());
988 rop._get_den() = op1.get_den();
989 } else {
990 divexact_gcd(rop._get_den(), op1.get_den(), g);
991 divexact_gcd(g, op2.get_num(), g);
992 mul(rop._get_num(), op1.get_num(), g);
993 }
994 } else {
995 // General case: a/b * c/d
996 // NOTE: like above, we don't want to canonicalise (ac)/(bd),
997 // and we trade one big gcd with two smaller gcds.
998 // Compute gcd(a,d) and gcd(b,c).
999 const auto g1 = gcd(op1.get_num(), op2.get_den());
1000 const auto g2 = gcd(op1.get_den(), op2.get_num());
1001 // Remove common factors from the nums.
1002 auto tmp1 = divexact_gcd(op1.get_num(), g1);
1003 auto tmp2 = divexact_gcd(op2.get_num(), g2);
1004 // Compute rop's numerator.
1005 // NOTE: after this line, all nums are tainted.
1006 mul(rop._get_num(), tmp1, tmp2);
1007 // Remove common factors from the dens.
1008 divexact_gcd(tmp1, op2.get_den(), g1);
1009 divexact_gcd(tmp2, op1.get_den(), g2);
1010 // Compute rop's denominator.
1011 mul(rop._get_den(), tmp1, tmp2);
1012 }
1013 }
1014 } // namespace detail
1015
1016 // Ternary multiplication.
1017 template <std::size_t SSize>
mul(rational<SSize> & rop,const rational<SSize> & op1,const rational<SSize> & op2)1018 inline rational<SSize> &mul(rational<SSize> &rop, const rational<SSize> &op1, const rational<SSize> &op2)
1019 {
1020 detail::mul_impl<false>(rop, op1, op2);
1021 return rop;
1022 }
1023
1024 // Ternary division.
1025 template <std::size_t SSize>
div(rational<SSize> & rop,const rational<SSize> & op1,const rational<SSize> & op2)1026 inline rational<SSize> &div(rational<SSize> &rop, const rational<SSize> &op1, const rational<SSize> &op2)
1027 {
1028 if (mppp_unlikely(op2.is_zero())) {
1029 throw zero_division_error("Zero divisor in rational division");
1030 }
1031 if (mppp_unlikely(&rop == &op2)) {
1032 // Following the GMP algorithm, special case in which rop and op2 are the same object.
1033 // This allows us to use op2.get_num() safely later, even after setting rop's num, as
1034 // we will be sure that rop and op2 do not overlap.
1035 if (mppp_unlikely(&rop == &op1)) {
1036 // x = x/x = 1.
1037 rop._get_num().set_one();
1038 rop._get_den().set_one();
1039 return rop;
1040 }
1041 // Set rop to 1/rop by swapping num/den.
1042 // NOTE: we already checked that op2 is nonzero, so inverting it
1043 // does not yield division by zero.
1044 swap(rop._get_num(), rop._get_den());
1045 // Fix den sign.
1046 detail::fix_den_sign(rop);
1047 // Multiply by op1.
1048 mul(rop, rop, op1);
1049 return rop;
1050 }
1051 const bool u1 = op1.get_den().is_one(), u2 = op2.get_den().is_one();
1052 if ((u1 && u2) || (op1.get_den() == op2.get_den())) {
1053 const auto g = gcd(op1.get_num(), op2.get_num());
1054 if (g.is_one()) {
1055 rop._get_num() = op1.get_num();
1056 // NOTE: op2 not tainted, as it's separate from rop.
1057 rop._get_den() = op2.get_num();
1058 } else {
1059 divexact_gcd(rop._get_num(), op1.get_num(), g);
1060 divexact_gcd(rop._get_den(), op2.get_num(), g);
1061 }
1062 } else if (u1) {
1063 // Same idea as in the mul().
1064 auto g = gcd(op1.get_num(), op2.get_num());
1065 if (g.is_one()) {
1066 mul(rop._get_num(), op2.get_den(), op1.get_num());
1067 // NOTE: op2's num is not tainted, as op2 is distinct
1068 // from rop.
1069 rop._get_den() = op2.get_num();
1070 } else {
1071 // NOTE: dens tainted after this, apart from op2's
1072 // as we have established earlier that rop and op2
1073 // are distinct.
1074 divexact_gcd(rop._get_den(), op2.get_num(), g);
1075 divexact_gcd(g, op1.get_num(), g);
1076 mul(rop._get_num(), op2.get_den(), g);
1077 }
1078 } else if (u2) {
1079 auto g = gcd(op1.get_num(), op2.get_num());
1080 if (g.is_one()) {
1081 rop._get_num() = op1.get_num();
1082 mul(rop._get_den(), op1.get_den(), op2.get_num());
1083 } else {
1084 divexact_gcd(rop._get_num(), op1.get_num(), g);
1085 divexact_gcd(g, op2.get_num(), g);
1086 mul(rop._get_den(), op1.get_den(), g);
1087 }
1088 } else {
1089 // (a/b) / (c/d) -> a/b * d/c
1090 // The two gcds.
1091 const auto g1 = gcd(op1.get_num(), op2.get_num());
1092 const auto g2 = gcd(op1.get_den(), op2.get_den());
1093 // Remove common factors.
1094 auto tmp1 = divexact_gcd(op1.get_num(), g1);
1095 auto tmp2 = divexact_gcd(op2.get_den(), g2);
1096 // Compute the numerator.
1097 mul(rop._get_num(), tmp1, tmp2);
1098 // Remove common factors.
1099 divexact_gcd(tmp1, op2.get_num(), g1);
1100 divexact_gcd(tmp2, op1.get_den(), g2);
1101 // Denominator.
1102 mul(rop._get_den(), tmp1, tmp2);
1103 }
1104 // Fix wrong sign in the den.
1105 detail::fix_den_sign(rop);
1106 return rop;
1107 }
1108
1109 // Binary negation.
1110 template <std::size_t SSize>
neg(rational<SSize> & rop,const rational<SSize> & q)1111 inline rational<SSize> &neg(rational<SSize> &rop, const rational<SSize> &q)
1112 {
1113 rop = q;
1114 return rop.neg();
1115 }
1116
1117 // Unary negation.
1118 template <std::size_t SSize>
neg(const rational<SSize> & q)1119 inline rational<SSize> neg(const rational<SSize> &q)
1120 {
1121 rational<SSize> ret(q);
1122 ret.neg();
1123 return ret;
1124 }
1125
1126 // Binary absolute value.
1127 template <std::size_t SSize>
abs(rational<SSize> & rop,const rational<SSize> & q)1128 inline rational<SSize> &abs(rational<SSize> &rop, const rational<SSize> &q)
1129 {
1130 rop = q;
1131 return rop.abs();
1132 }
1133
1134 // Unary absolute value.
1135 template <std::size_t SSize>
abs(const rational<SSize> & q)1136 inline rational<SSize> abs(const rational<SSize> &q)
1137 {
1138 rational<SSize> ret(q);
1139 ret.abs();
1140 return ret;
1141 }
1142
1143 // Binary inversion.
1144 template <std::size_t SSize>
inv(rational<SSize> & rop,const rational<SSize> & q)1145 inline rational<SSize> &inv(rational<SSize> &rop, const rational<SSize> &q)
1146 {
1147 rop = q;
1148 return rop.inv();
1149 }
1150
1151 // Unary inversion.
1152 template <std::size_t SSize>
inv(const rational<SSize> & q)1153 inline rational<SSize> inv(const rational<SSize> &q)
1154 {
1155 rational<SSize> ret(q);
1156 ret.inv();
1157 return ret;
1158 }
1159
1160 namespace detail
1161 {
1162
1163 MPPP_DLL_PUBLIC std::ostream &rational_stream_operator_impl(std::ostream &, const mpz_struct_t *, const mpz_struct_t *,
1164 int, bool);
1165
1166 } // namespace detail
1167
1168 // Output stream operator.
1169 template <std::size_t SSize>
operator <<(std::ostream & os,const rational<SSize> & q)1170 inline std::ostream &operator<<(std::ostream &os, const rational<SSize> &q)
1171 {
1172 return detail::rational_stream_operator_impl(os, q.get_num().get_mpz_view(), q.get_den().get_mpz_view(), q.sgn(),
1173 q.get_den().is_one());
1174 }
1175
1176 // Identity operator.
1177 template <std::size_t SSize>
operator +(const rational<SSize> & q)1178 inline rational<SSize> operator+(const rational<SSize> &q)
1179 {
1180 return q;
1181 }
1182
1183 namespace detail
1184 {
1185
1186 // Dispatching for the binary addition operator.
1187 template <std::size_t SSize>
dispatch_binary_add(const rational<SSize> & op1,const rational<SSize> & op2)1188 inline rational<SSize> dispatch_binary_add(const rational<SSize> &op1, const rational<SSize> &op2)
1189 {
1190 rational<SSize> retval;
1191 addsub_impl<true, true>(retval, op1, op2);
1192 return retval;
1193 }
1194
1195 template <std::size_t SSize>
dispatch_binary_add(const rational<SSize> & op1,const integer<SSize> & op2)1196 inline rational<SSize> dispatch_binary_add(const rational<SSize> &op1, const integer<SSize> &op2)
1197 {
1198 rational<SSize> retval{op1};
1199 if (op1.get_den().is_one()) {
1200 add(retval._get_num(), retval._get_num(), op2);
1201 } else {
1202 addmul(retval._get_num(), retval.get_den(), op2);
1203 }
1204 return retval;
1205 }
1206
1207 template <std::size_t SSize>
dispatch_binary_add(const integer<SSize> & op1,const rational<SSize> & op2)1208 inline rational<SSize> dispatch_binary_add(const integer<SSize> &op1, const rational<SSize> &op2)
1209 {
1210 return dispatch_binary_add(op2, op1);
1211 }
1212
1213 template <std::size_t SSize, typename T, enable_if_t<is_cpp_integral<T>::value, int> = 0>
dispatch_binary_add(const rational<SSize> & op1,T n)1214 inline rational<SSize> dispatch_binary_add(const rational<SSize> &op1, T n)
1215 {
1216 return dispatch_binary_add(op1, integer<SSize>{n});
1217 }
1218
1219 template <std::size_t SSize, typename T, enable_if_t<is_cpp_integral<T>::value, int> = 0>
dispatch_binary_add(T n,const rational<SSize> & op2)1220 inline rational<SSize> dispatch_binary_add(T n, const rational<SSize> &op2)
1221 {
1222 return dispatch_binary_add(op2, n);
1223 }
1224
1225 template <std::size_t SSize, typename T,
1226 enable_if_t<disjunction<is_cpp_floating_point<T>, is_cpp_complex<T>>::value, int> = 0>
dispatch_binary_add(const rational<SSize> & op1,const T & x)1227 inline T dispatch_binary_add(const rational<SSize> &op1, const T &x)
1228 {
1229 return static_cast<T>(op1) + x;
1230 }
1231
1232 template <std::size_t SSize, typename T,
1233 enable_if_t<disjunction<is_cpp_floating_point<T>, is_cpp_complex<T>>::value, int> = 0>
dispatch_binary_add(const T & x,const rational<SSize> & op2)1234 inline T dispatch_binary_add(const T &x, const rational<SSize> &op2)
1235 {
1236 return dispatch_binary_add(op2, x);
1237 }
1238
1239 } // namespace detail
1240
1241 // Binary addition operator.
1242 template <typename T, typename U>
1243 #if defined(MPPP_HAVE_CONCEPTS)
1244 requires rational_op_types<T, U>
1245 inline auto
1246 #else
1247 inline detail::rational_common_t<T, U>
1248 #endif
operator +(const T & op1,const U & op2)1249 operator+(const T &op1, const U &op2)
1250 {
1251 return detail::dispatch_binary_add(op1, op2);
1252 }
1253
1254 namespace detail
1255 {
1256
1257 // Dispatching for in-place add.
1258 template <std::size_t SSize>
dispatch_in_place_add(rational<SSize> & retval,const rational<SSize> & q)1259 inline void dispatch_in_place_add(rational<SSize> &retval, const rational<SSize> &q)
1260 {
1261 add(retval, retval, q);
1262 }
1263
1264 template <std::size_t SSize>
dispatch_in_place_add(rational<SSize> & retval,const integer<SSize> & n)1265 inline void dispatch_in_place_add(rational<SSize> &retval, const integer<SSize> &n)
1266 {
1267 if (retval.get_den().is_one()) {
1268 add(retval._get_num(), retval._get_num(), n);
1269 } else {
1270 addmul(retval._get_num(), retval.get_den(), n);
1271 }
1272 }
1273
1274 template <std::size_t SSize, typename T, enable_if_t<is_cpp_integral<T>::value, int> = 0>
dispatch_in_place_add(rational<SSize> & retval,const T & n)1275 inline void dispatch_in_place_add(rational<SSize> &retval, const T &n)
1276 {
1277 dispatch_in_place_add(retval, integer<SSize>{n});
1278 }
1279
1280 template <std::size_t SSize, typename T,
1281 enable_if_t<disjunction<is_cpp_floating_point<T>, is_cpp_complex<T>>::value, int> = 0>
dispatch_in_place_add(rational<SSize> & retval,const T & x)1282 inline void dispatch_in_place_add(rational<SSize> &retval, const T &x)
1283 {
1284 retval = static_cast<T>(retval) + x;
1285 }
1286
1287 template <typename T, std::size_t SSize, enable_if_t<is_rational_interoperable<T, SSize>::value, int> = 0>
dispatch_in_place_add(T & rop,const rational<SSize> & op)1288 inline void dispatch_in_place_add(T &rop, const rational<SSize> &op)
1289 {
1290 rop = static_cast<T>(rop + op);
1291 }
1292
1293 } // namespace detail
1294
1295 // In-place addition operator.
1296 #if defined(MPPP_HAVE_CONCEPTS)
1297 template <typename T, typename U>
1298 requires rational_op_types<T, U>
1299 #else
1300 template <typename T, typename U, detail::enable_if_t<are_rational_op_types<T, U>::value, int> = 0>
1301 #endif
operator +=(T & rop,const U & op)1302 inline T &operator+=(T &rop, const U &op)
1303 {
1304 detail::dispatch_in_place_add(rop, op);
1305 return rop;
1306 }
1307
1308 // Prefix increment.
1309 template <std::size_t SSize>
operator ++(rational<SSize> & q)1310 inline rational<SSize> &operator++(rational<SSize> &q)
1311 {
1312 add(q._get_num(), q._get_num(), q.get_den());
1313 return q;
1314 }
1315
1316 // Suffix increment.
1317 template <std::size_t SSize>
operator ++(rational<SSize> & q,int)1318 inline rational<SSize> operator++(rational<SSize> &q, int)
1319 {
1320 auto retval(q);
1321 ++q;
1322 return retval;
1323 }
1324
1325 // Negated copy.
1326 template <std::size_t SSize>
operator -(const rational<SSize> & q)1327 inline rational<SSize> operator-(const rational<SSize> &q)
1328 {
1329 auto retval(q);
1330 retval.neg();
1331 return retval;
1332 }
1333
1334 namespace detail
1335 {
1336
1337 // Dispatching for the binary subtraction operator.
1338 template <std::size_t SSize>
dispatch_binary_sub(const rational<SSize> & op1,const rational<SSize> & op2)1339 inline rational<SSize> dispatch_binary_sub(const rational<SSize> &op1, const rational<SSize> &op2)
1340 {
1341 rational<SSize> retval;
1342 addsub_impl<false, true>(retval, op1, op2);
1343 return retval;
1344 }
1345
1346 template <std::size_t SSize>
dispatch_binary_sub(const rational<SSize> & op1,const integer<SSize> & op2)1347 inline rational<SSize> dispatch_binary_sub(const rational<SSize> &op1, const integer<SSize> &op2)
1348 {
1349 rational<SSize> retval{op1};
1350 if (op1.get_den().is_one()) {
1351 sub(retval._get_num(), retval._get_num(), op2);
1352 } else {
1353 submul(retval._get_num(), retval.get_den(), op2);
1354 }
1355 return retval;
1356 }
1357
1358 template <std::size_t SSize>
dispatch_binary_sub(const integer<SSize> & op1,const rational<SSize> & op2)1359 inline rational<SSize> dispatch_binary_sub(const integer<SSize> &op1, const rational<SSize> &op2)
1360 {
1361 auto retval = dispatch_binary_sub(op2, op1);
1362 retval.neg();
1363 return retval;
1364 }
1365
1366 template <std::size_t SSize, typename T, enable_if_t<is_cpp_integral<T>::value, int> = 0>
dispatch_binary_sub(const rational<SSize> & op1,T n)1367 inline rational<SSize> dispatch_binary_sub(const rational<SSize> &op1, T n)
1368 {
1369 return dispatch_binary_sub(op1, integer<SSize>{n});
1370 }
1371
1372 template <std::size_t SSize, typename T, enable_if_t<is_cpp_integral<T>::value, int> = 0>
dispatch_binary_sub(T n,const rational<SSize> & op2)1373 inline rational<SSize> dispatch_binary_sub(T n, const rational<SSize> &op2)
1374 {
1375 auto retval = dispatch_binary_sub(op2, n);
1376 retval.neg();
1377 return retval;
1378 }
1379
1380 template <std::size_t SSize, typename T,
1381 enable_if_t<disjunction<is_cpp_floating_point<T>, is_cpp_complex<T>>::value, int> = 0>
dispatch_binary_sub(const rational<SSize> & op1,const T & x)1382 inline T dispatch_binary_sub(const rational<SSize> &op1, const T &x)
1383 {
1384 return static_cast<T>(op1) - x;
1385 }
1386
1387 template <std::size_t SSize, typename T,
1388 enable_if_t<disjunction<is_cpp_floating_point<T>, is_cpp_complex<T>>::value, int> = 0>
dispatch_binary_sub(const T & x,const rational<SSize> & op2)1389 inline T dispatch_binary_sub(const T &x, const rational<SSize> &op2)
1390 {
1391 return -dispatch_binary_sub(op2, x);
1392 }
1393
1394 } // namespace detail
1395
1396 // Binary subtraction operator.
1397 template <typename T, typename U>
1398 #if defined(MPPP_HAVE_CONCEPTS)
1399 requires rational_op_types<T, U>
1400 inline auto
1401 #else
1402 inline detail::rational_common_t<T, U>
1403 #endif
operator -(const T & op1,const U & op2)1404 operator-(const T &op1, const U &op2)
1405 {
1406 return detail::dispatch_binary_sub(op1, op2);
1407 }
1408
1409 namespace detail
1410 {
1411
1412 // Dispatching for in-place sub.
1413 template <std::size_t SSize>
dispatch_in_place_sub(rational<SSize> & retval,const rational<SSize> & q)1414 inline void dispatch_in_place_sub(rational<SSize> &retval, const rational<SSize> &q)
1415 {
1416 sub(retval, retval, q);
1417 }
1418
1419 template <std::size_t SSize>
dispatch_in_place_sub(rational<SSize> & retval,const integer<SSize> & n)1420 inline void dispatch_in_place_sub(rational<SSize> &retval, const integer<SSize> &n)
1421 {
1422 if (retval.get_den().is_one()) {
1423 sub(retval._get_num(), retval._get_num(), n);
1424 } else {
1425 submul(retval._get_num(), retval.get_den(), n);
1426 }
1427 }
1428
1429 template <std::size_t SSize, typename T, enable_if_t<is_cpp_integral<T>::value, int> = 0>
dispatch_in_place_sub(rational<SSize> & retval,const T & n)1430 inline void dispatch_in_place_sub(rational<SSize> &retval, const T &n)
1431 {
1432 dispatch_in_place_sub(retval, integer<SSize>{n});
1433 }
1434
1435 template <std::size_t SSize, typename T,
1436 enable_if_t<disjunction<is_cpp_floating_point<T>, is_cpp_complex<T>>::value, int> = 0>
dispatch_in_place_sub(rational<SSize> & retval,const T & x)1437 inline void dispatch_in_place_sub(rational<SSize> &retval, const T &x)
1438 {
1439 retval = static_cast<T>(retval) - x;
1440 }
1441
1442 template <typename T, std::size_t SSize, enable_if_t<is_rational_interoperable<T, SSize>::value, int> = 0>
dispatch_in_place_sub(T & rop,const rational<SSize> & op)1443 inline void dispatch_in_place_sub(T &rop, const rational<SSize> &op)
1444 {
1445 rop = static_cast<T>(rop - op);
1446 }
1447
1448 } // namespace detail
1449
1450 // In-place subtraction operator.
1451 #if defined(MPPP_HAVE_CONCEPTS)
1452 template <typename T, typename U>
1453 requires rational_op_types<T, U>
1454 #else
1455 template <typename T, typename U, detail::enable_if_t<are_rational_op_types<T, U>::value, int> = 0>
1456 #endif
operator -=(T & rop,const U & op)1457 inline T &operator-=(T &rop, const U &op)
1458 {
1459 detail::dispatch_in_place_sub(rop, op);
1460 return rop;
1461 }
1462
1463 // Prefix decrement.
1464 template <std::size_t SSize>
operator --(rational<SSize> & q)1465 inline rational<SSize> &operator--(rational<SSize> &q)
1466 {
1467 sub(q._get_num(), q._get_num(), q.get_den());
1468 return q;
1469 }
1470
1471 // Suffix decrement.
1472 template <std::size_t SSize>
operator --(rational<SSize> & q,int)1473 inline rational<SSize> operator--(rational<SSize> &q, int)
1474 {
1475 auto retval(q);
1476 --q;
1477 return retval;
1478 }
1479
1480 namespace detail
1481 {
1482
1483 // Dispatching for the binary multiplication operator.
1484 template <std::size_t SSize>
dispatch_binary_mul(const rational<SSize> & op1,const rational<SSize> & op2)1485 inline rational<SSize> dispatch_binary_mul(const rational<SSize> &op1, const rational<SSize> &op2)
1486 {
1487 rational<SSize> retval;
1488 mul_impl<true>(retval, op1, op2);
1489 return retval;
1490 }
1491
1492 template <std::size_t SSize>
dispatch_binary_mul(const rational<SSize> & op1,const integer<SSize> & op2)1493 inline rational<SSize> dispatch_binary_mul(const rational<SSize> &op1, const integer<SSize> &op2)
1494 {
1495 rational<SSize> retval;
1496 if (op1.get_den().is_one()) {
1497 mul(retval._get_num(), op1.get_num(), op2);
1498 } else {
1499 auto g = gcd(op1.get_den(), op2);
1500 if (g.is_one()) {
1501 // Nums will be tainted after this.
1502 mul(retval._get_num(), op1.get_num(), op2);
1503 retval._get_den() = op1.get_den();
1504 } else {
1505 // Set the den first. Dens tainted after this.
1506 divexact_gcd(retval._get_den(), op1.get_den(), g);
1507 // Re-use the g variable as tmp storage.
1508 divexact_gcd(g, op2, g);
1509 mul(retval._get_num(), op1.get_num(), g);
1510 }
1511 }
1512 return retval;
1513 }
1514
1515 template <std::size_t SSize>
dispatch_binary_mul(const integer<SSize> & op1,const rational<SSize> & op2)1516 inline rational<SSize> dispatch_binary_mul(const integer<SSize> &op1, const rational<SSize> &op2)
1517 {
1518 return dispatch_binary_mul(op2, op1);
1519 }
1520
1521 template <std::size_t SSize, typename T, enable_if_t<is_cpp_integral<T>::value, int> = 0>
dispatch_binary_mul(const rational<SSize> & op1,T n)1522 inline rational<SSize> dispatch_binary_mul(const rational<SSize> &op1, T n)
1523 {
1524 return dispatch_binary_mul(op1, integer<SSize>{n});
1525 }
1526
1527 template <std::size_t SSize, typename T, enable_if_t<is_cpp_integral<T>::value, int> = 0>
dispatch_binary_mul(T n,const rational<SSize> & op2)1528 inline rational<SSize> dispatch_binary_mul(T n, const rational<SSize> &op2)
1529 {
1530 return dispatch_binary_mul(op2, n);
1531 }
1532
1533 template <std::size_t SSize, typename T,
1534 enable_if_t<disjunction<is_cpp_floating_point<T>, is_cpp_complex<T>>::value, int> = 0>
dispatch_binary_mul(const rational<SSize> & op1,const T & x)1535 inline T dispatch_binary_mul(const rational<SSize> &op1, const T &x)
1536 {
1537 return static_cast<T>(op1) * x;
1538 }
1539
1540 template <std::size_t SSize, typename T,
1541 enable_if_t<disjunction<is_cpp_floating_point<T>, is_cpp_complex<T>>::value, int> = 0>
dispatch_binary_mul(const T & x,const rational<SSize> & op2)1542 inline T dispatch_binary_mul(const T &x, const rational<SSize> &op2)
1543 {
1544 return dispatch_binary_mul(op2, x);
1545 }
1546
1547 } // namespace detail
1548
1549 // Binary multiplication operator.
1550 template <typename T, typename U>
1551 #if defined(MPPP_HAVE_CONCEPTS)
1552 requires rational_op_types<T, U>
1553 inline auto
1554 #else
1555 inline detail::rational_common_t<T, U>
1556 #endif
operator *(const T & op1,const U & op2)1557 operator*(const T &op1, const U &op2)
1558 {
1559 return detail::dispatch_binary_mul(op1, op2);
1560 }
1561
1562 namespace detail
1563 {
1564
1565 // Dispatching for in-place mul.
1566 template <std::size_t SSize>
dispatch_in_place_mul(rational<SSize> & retval,const rational<SSize> & q)1567 inline void dispatch_in_place_mul(rational<SSize> &retval, const rational<SSize> &q)
1568 {
1569 mul(retval, retval, q);
1570 }
1571
1572 template <std::size_t SSize>
dispatch_in_place_mul(rational<SSize> & retval,const integer<SSize> & n)1573 inline void dispatch_in_place_mul(rational<SSize> &retval, const integer<SSize> &n)
1574 {
1575 if (retval.get_den().is_one()) {
1576 // Integer multiplication.
1577 mul(retval._get_num(), retval.get_num(), n);
1578 } else {
1579 auto g = gcd(retval.get_den(), n);
1580 if (g.is_one()) {
1581 // No common factors, just multiply the numerators. Den is already
1582 // assigned.
1583 mul(retval._get_num(), retval.get_num(), n);
1584 } else {
1585 // Set the den first. Dens tainted after this.
1586 divexact_gcd(retval._get_den(), retval.get_den(), g);
1587 // Re-use the g variable as tmp storage.
1588 divexact_gcd(g, n, g);
1589 mul(retval._get_num(), retval.get_num(), g);
1590 }
1591 }
1592 }
1593
1594 template <std::size_t SSize, typename T, enable_if_t<is_cpp_integral<T>::value, int> = 0>
dispatch_in_place_mul(rational<SSize> & retval,const T & n)1595 inline void dispatch_in_place_mul(rational<SSize> &retval, const T &n)
1596 {
1597 dispatch_in_place_mul(retval, integer<SSize>{n});
1598 }
1599
1600 template <std::size_t SSize, typename T,
1601 enable_if_t<disjunction<is_cpp_floating_point<T>, is_cpp_complex<T>>::value, int> = 0>
dispatch_in_place_mul(rational<SSize> & retval,const T & x)1602 inline void dispatch_in_place_mul(rational<SSize> &retval, const T &x)
1603 {
1604 retval = static_cast<T>(retval) * x;
1605 }
1606
1607 template <typename T, std::size_t SSize, enable_if_t<is_rational_interoperable<T, SSize>::value, int> = 0>
dispatch_in_place_mul(T & rop,const rational<SSize> & op)1608 inline void dispatch_in_place_mul(T &rop, const rational<SSize> &op)
1609 {
1610 rop = static_cast<T>(rop * op);
1611 }
1612
1613 } // namespace detail
1614
1615 // In-place multiplication operator.
1616 #if defined(MPPP_HAVE_CONCEPTS)
1617 template <typename T, typename U>
1618 requires rational_op_types<T, U>
1619 #else
1620 template <typename T, typename U, detail::enable_if_t<are_rational_op_types<T, U>::value, int> = 0>
1621 #endif
operator *=(T & rop,const U & op)1622 inline T &operator*=(T &rop, const U &op)
1623 {
1624 detail::dispatch_in_place_mul(rop, op);
1625 return rop;
1626 }
1627
1628 namespace detail
1629 {
1630
1631 // Dispatching for the binary division operator.
1632 template <std::size_t SSize>
dispatch_binary_div(const rational<SSize> & op1,const rational<SSize> & op2)1633 inline rational<SSize> dispatch_binary_div(const rational<SSize> &op1, const rational<SSize> &op2)
1634 {
1635 rational<SSize> retval;
1636 div(retval, op1, op2);
1637 return retval;
1638 }
1639
1640 template <std::size_t SSize>
dispatch_binary_div(const rational<SSize> & op1,const integer<SSize> & op2)1641 inline rational<SSize> dispatch_binary_div(const rational<SSize> &op1, const integer<SSize> &op2)
1642 {
1643 if (mppp_unlikely(op2.is_zero())) {
1644 throw zero_division_error("Zero divisor in rational division");
1645 }
1646 rational<SSize> retval;
1647 auto g = gcd(op1.get_num(), op2);
1648 if (op1.get_den().is_one()) {
1649 if (g.is_one()) {
1650 retval._get_num() = op1.get_num();
1651 retval._get_den() = op2;
1652 } else {
1653 divexact_gcd(retval._get_num(), op1.get_num(), g);
1654 divexact_gcd(retval._get_den(), op2, g);
1655 }
1656 } else {
1657 if (g.is_one()) {
1658 retval._get_num() = op1.get_num();
1659 mul(retval._get_den(), op1.get_den(), op2);
1660 } else {
1661 // Set the num first.
1662 divexact_gcd(retval._get_num(), op1.get_num(), g);
1663 // Re-use the g variable as tmp storage.
1664 divexact_gcd(g, op2, g);
1665 mul(retval._get_den(), op1.get_den(), g);
1666 }
1667 }
1668 // Fix den sign.
1669 fix_den_sign(retval);
1670 return retval;
1671 }
1672
1673 // NOTE: could not find a way to easily share the implementation above, so there's some repetition here.
1674 template <std::size_t SSize>
dispatch_binary_div(const integer<SSize> & op1,const rational<SSize> & op2)1675 inline rational<SSize> dispatch_binary_div(const integer<SSize> &op1, const rational<SSize> &op2)
1676 {
1677 if (mppp_unlikely(op2.is_zero())) {
1678 throw zero_division_error("Zero divisor in rational division");
1679 }
1680 rational<SSize> retval;
1681 auto g = gcd(op1, op2.get_num());
1682 if (op2.get_den().is_one()) {
1683 if (g.is_one()) {
1684 retval._get_num() = op1;
1685 retval._get_den() = op2.get_num();
1686 } else {
1687 divexact_gcd(retval._get_num(), op1, g);
1688 divexact_gcd(retval._get_den(), op2.get_num(), g);
1689 }
1690 } else {
1691 if (g.is_one()) {
1692 mul(retval._get_num(), op1, op2.get_den());
1693 retval._get_den() = op2.get_num();
1694 } else {
1695 divexact_gcd(retval._get_den(), op2.get_num(), g);
1696 divexact_gcd(g, op1, g);
1697 mul(retval._get_num(), op2.get_den(), g);
1698 }
1699 }
1700 // Fix den sign.
1701 fix_den_sign(retval);
1702 return retval;
1703 }
1704
1705 template <std::size_t SSize, typename T, enable_if_t<is_cpp_integral<T>::value, int> = 0>
dispatch_binary_div(const rational<SSize> & op1,T n)1706 inline rational<SSize> dispatch_binary_div(const rational<SSize> &op1, T n)
1707 {
1708 return dispatch_binary_div(op1, integer<SSize>{n});
1709 }
1710
1711 template <std::size_t SSize, typename T, enable_if_t<is_cpp_integral<T>::value, int> = 0>
dispatch_binary_div(T n,const rational<SSize> & op2)1712 inline rational<SSize> dispatch_binary_div(T n, const rational<SSize> &op2)
1713 {
1714 return dispatch_binary_div(integer<SSize>{n}, op2);
1715 }
1716
1717 template <std::size_t SSize, typename T,
1718 enable_if_t<disjunction<is_cpp_floating_point<T>, is_cpp_complex<T>>::value, int> = 0>
dispatch_binary_div(const rational<SSize> & op1,const T & x)1719 inline T dispatch_binary_div(const rational<SSize> &op1, const T &x)
1720 {
1721 return static_cast<T>(op1) / x;
1722 }
1723
1724 template <std::size_t SSize, typename T,
1725 enable_if_t<disjunction<is_cpp_floating_point<T>, is_cpp_complex<T>>::value, int> = 0>
dispatch_binary_div(const T & x,const rational<SSize> & op2)1726 inline T dispatch_binary_div(const T &x, const rational<SSize> &op2)
1727 {
1728 return x / static_cast<T>(op2);
1729 }
1730
1731 } // namespace detail
1732
1733 // Binary division operator.
1734 template <typename T, typename U>
1735 #if defined(MPPP_HAVE_CONCEPTS)
1736 requires rational_op_types<T, U>
1737 inline auto
1738 #else
1739 inline detail::rational_common_t<T, U>
1740 #endif
operator /(const T & op1,const U & op2)1741 operator/(const T &op1, const U &op2)
1742 {
1743 return detail::dispatch_binary_div(op1, op2);
1744 }
1745
1746 namespace detail
1747 {
1748
1749 // Dispatching for in-place div.
1750 template <std::size_t SSize>
dispatch_in_place_div(rational<SSize> & retval,const rational<SSize> & q)1751 inline void dispatch_in_place_div(rational<SSize> &retval, const rational<SSize> &q)
1752 {
1753 div(retval, retval, q);
1754 }
1755
1756 template <std::size_t SSize>
dispatch_in_place_div(rational<SSize> & retval,const integer<SSize> & n)1757 inline void dispatch_in_place_div(rational<SSize> &retval, const integer<SSize> &n)
1758 {
1759 if (mppp_unlikely(n.is_zero())) {
1760 throw zero_division_error("Zero divisor in rational division");
1761 }
1762 auto g = gcd(retval.get_num(), n);
1763 if (retval.get_den().is_one()) {
1764 if (g.is_one()) {
1765 retval._get_den() = n;
1766 } else {
1767 divexact_gcd(retval._get_num(), retval.get_num(), g);
1768 divexact_gcd(retval._get_den(), n, g);
1769 }
1770 } else {
1771 if (g.is_one()) {
1772 mul(retval._get_den(), retval.get_den(), n);
1773 } else {
1774 // Set the num first.
1775 divexact_gcd(retval._get_num(), retval.get_num(), g);
1776 // Re-use the g variable as tmp storage.
1777 divexact_gcd(g, n, g);
1778 mul(retval._get_den(), retval.get_den(), g);
1779 }
1780 }
1781 // Fix den sign.
1782 fix_den_sign(retval);
1783 }
1784
1785 template <std::size_t SSize, typename T, enable_if_t<is_cpp_integral<T>::value, int> = 0>
dispatch_in_place_div(rational<SSize> & retval,const T & n)1786 inline void dispatch_in_place_div(rational<SSize> &retval, const T &n)
1787 {
1788 dispatch_in_place_div(retval, integer<SSize>{n});
1789 }
1790
1791 template <std::size_t SSize, typename T,
1792 enable_if_t<disjunction<is_cpp_floating_point<T>, is_cpp_complex<T>>::value, int> = 0>
dispatch_in_place_div(rational<SSize> & retval,const T & x)1793 inline void dispatch_in_place_div(rational<SSize> &retval, const T &x)
1794 {
1795 retval = static_cast<T>(retval) / x;
1796 }
1797
1798 template <typename T, std::size_t SSize, enable_if_t<is_rational_interoperable<T, SSize>::value, int> = 0>
dispatch_in_place_div(T & rop,const rational<SSize> & op)1799 inline void dispatch_in_place_div(T &rop, const rational<SSize> &op)
1800 {
1801 rop = static_cast<T>(rop / op);
1802 }
1803
1804 } // namespace detail
1805
1806 // In-place division operator.
1807 #if defined(MPPP_HAVE_CONCEPTS)
1808 template <typename T, typename U>
1809 requires rational_op_types<T, U>
1810 #else
1811 template <typename T, typename U, detail::enable_if_t<are_rational_op_types<T, U>::value, int> = 0>
1812 #endif
operator /=(T & rop,const U & op)1813 inline T &operator/=(T &rop, const U &op)
1814 {
1815 detail::dispatch_in_place_div(rop, op);
1816 return rop;
1817 }
1818
1819 namespace detail
1820 {
1821
1822 template <std::size_t SSize>
dispatch_equality(const rational<SSize> & op1,const rational<SSize> & op2)1823 inline bool dispatch_equality(const rational<SSize> &op1, const rational<SSize> &op2)
1824 {
1825 return op1.get_num() == op2.get_num() && op1.get_den() == op2.get_den();
1826 }
1827
1828 template <std::size_t SSize, typename T, enable_if_t<is_rational_integral_interoperable<T, SSize>::value, int> = 0>
dispatch_equality(const rational<SSize> & op1,const T & op2)1829 inline bool dispatch_equality(const rational<SSize> &op1, const T &op2)
1830 {
1831 return op1.get_den().is_one() && op1.get_num() == op2;
1832 }
1833
1834 template <std::size_t SSize, typename T, enable_if_t<is_rational_integral_interoperable<T, SSize>::value, int> = 0>
dispatch_equality(const T & op1,const rational<SSize> & op2)1835 inline bool dispatch_equality(const T &op1, const rational<SSize> &op2)
1836 {
1837 return dispatch_equality(op2, op1);
1838 }
1839
1840 template <std::size_t SSize, typename T,
1841 enable_if_t<disjunction<is_cpp_floating_point<T>, is_cpp_complex<T>>::value, int> = 0>
dispatch_equality(const rational<SSize> & op1,const T & op2)1842 inline bool dispatch_equality(const rational<SSize> &op1, const T &op2)
1843 {
1844 return static_cast<T>(op1) == op2;
1845 }
1846
1847 template <std::size_t SSize, typename T,
1848 enable_if_t<disjunction<is_cpp_floating_point<T>, is_cpp_complex<T>>::value, int> = 0>
dispatch_equality(const T & op1,const rational<SSize> & op2)1849 inline bool dispatch_equality(const T &op1, const rational<SSize> &op2)
1850 {
1851 return dispatch_equality(op2, op1);
1852 }
1853
1854 } // namespace detail
1855
1856 // Equality operator.
1857 #if defined(MPPP_HAVE_CONCEPTS)
1858 template <typename T, typename U>
1859 requires rational_op_types<T, U>
1860 #else
1861 template <typename T, typename U, detail::enable_if_t<are_rational_op_types<T, U>::value, int> = 0>
1862 #endif
operator ==(const T & op1,const U & op2)1863 inline bool operator==(const T &op1, const U &op2)
1864 {
1865 return detail::dispatch_equality(op1, op2);
1866 }
1867
1868 // Inequality operator.
1869 #if defined(MPPP_HAVE_CONCEPTS)
1870 template <typename T, typename U>
1871 requires rational_op_types<T, U>
1872 #else
1873 template <typename T, typename U, detail::enable_if_t<are_rational_op_types<T, U>::value, int> = 0>
1874 #endif
operator !=(const T & op1,const U & op2)1875 inline bool operator!=(const T &op1, const U &op2)
1876 {
1877 return !(op1 == op2);
1878 }
1879
1880 namespace detail
1881 {
1882
1883 // Less-than operator.
1884 template <std::size_t SSize>
dispatch_less_than(const rational<SSize> & a,const rational<SSize> & b)1885 inline bool dispatch_less_than(const rational<SSize> &a, const rational<SSize> &b)
1886 {
1887 return cmp(a, b) < 0;
1888 }
1889
1890 template <std::size_t SSize>
dispatch_less_than(const rational<SSize> & a,const integer<SSize> & b)1891 inline bool dispatch_less_than(const rational<SSize> &a, const integer<SSize> &b)
1892 {
1893 return cmp(a, b) < 0;
1894 }
1895
1896 template <std::size_t SSize>
dispatch_less_than(const integer<SSize> & a,const rational<SSize> & b)1897 inline bool dispatch_less_than(const integer<SSize> &a, const rational<SSize> &b)
1898 {
1899 return cmp(a, b) < 0;
1900 }
1901
1902 template <typename T, std::size_t SSize, enable_if_t<is_cpp_integral<T>::value, int> = 0>
dispatch_less_than(const rational<SSize> & a,T n)1903 inline bool dispatch_less_than(const rational<SSize> &a, T n)
1904 {
1905 return dispatch_less_than(a, integer<SSize>{n});
1906 }
1907
1908 template <typename T, std::size_t SSize, enable_if_t<is_cpp_integral<T>::value, int> = 0>
1909 bool dispatch_less_than(T, const rational<SSize> &);
1910
1911 template <typename T, std::size_t SSize, enable_if_t<is_cpp_floating_point<T>::value, int> = 0>
dispatch_less_than(const rational<SSize> & a,T x)1912 inline bool dispatch_less_than(const rational<SSize> &a, T x)
1913 {
1914 return static_cast<T>(a) < x;
1915 }
1916
1917 template <typename T, std::size_t SSize, enable_if_t<is_cpp_floating_point<T>::value, int> = 0>
1918 inline bool dispatch_less_than(T, const rational<SSize> &);
1919
1920 // Greater-than operator.
1921 template <std::size_t SSize>
dispatch_greater_than(const rational<SSize> & a,const rational<SSize> & b)1922 inline bool dispatch_greater_than(const rational<SSize> &a, const rational<SSize> &b)
1923 {
1924 return cmp(a, b) > 0;
1925 }
1926
1927 template <std::size_t SSize>
dispatch_greater_than(const rational<SSize> & a,const integer<SSize> & b)1928 inline bool dispatch_greater_than(const rational<SSize> &a, const integer<SSize> &b)
1929 {
1930 return cmp(a, b) > 0;
1931 }
1932
1933 template <std::size_t SSize>
dispatch_greater_than(const integer<SSize> & a,const rational<SSize> & b)1934 inline bool dispatch_greater_than(const integer<SSize> &a, const rational<SSize> &b)
1935 {
1936 return cmp(a, b) > 0;
1937 }
1938
1939 template <typename T, std::size_t SSize, enable_if_t<is_cpp_integral<T>::value, int> = 0>
dispatch_greater_than(const rational<SSize> & a,T n)1940 inline bool dispatch_greater_than(const rational<SSize> &a, T n)
1941 {
1942 return dispatch_greater_than(a, integer<SSize>{n});
1943 }
1944
1945 template <typename T, std::size_t SSize, enable_if_t<is_cpp_integral<T>::value, int> = 0>
dispatch_greater_than(T n,const rational<SSize> & a)1946 inline bool dispatch_greater_than(T n, const rational<SSize> &a)
1947 {
1948 return dispatch_less_than(a, integer<SSize>{n});
1949 }
1950
1951 template <typename T, std::size_t SSize, enable_if_t<is_cpp_floating_point<T>::value, int> = 0>
dispatch_greater_than(const rational<SSize> & a,T x)1952 inline bool dispatch_greater_than(const rational<SSize> &a, T x)
1953 {
1954 return static_cast<T>(a) > x;
1955 }
1956
1957 template <typename T, std::size_t SSize, enable_if_t<is_cpp_floating_point<T>::value, int> = 0>
dispatch_greater_than(T x,const rational<SSize> & a)1958 inline bool dispatch_greater_than(T x, const rational<SSize> &a)
1959 {
1960 return dispatch_less_than(a, x);
1961 }
1962
1963 // Implement them here, as we need visibility of dispatch_greater_than().
1964 template <typename T, std::size_t SSize, enable_if_t<is_cpp_integral<T>::value, int>>
dispatch_less_than(T n,const rational<SSize> & a)1965 inline bool dispatch_less_than(T n, const rational<SSize> &a)
1966 {
1967 return dispatch_greater_than(a, integer<SSize>{n});
1968 }
1969
1970 template <typename T, std::size_t SSize, enable_if_t<is_cpp_floating_point<T>::value, int>>
dispatch_less_than(T x,const rational<SSize> & a)1971 inline bool dispatch_less_than(T x, const rational<SSize> &a)
1972 {
1973 return dispatch_greater_than(a, x);
1974 }
1975 } // namespace detail
1976
1977 // Less-than operator.
1978 #if defined(MPPP_HAVE_CONCEPTS)
1979 template <typename T, typename U>
1980 requires rational_real_op_types<T, U>
1981 #else
1982 template <typename T, typename U, detail::enable_if_t<are_rational_real_op_types<T, U>::value, int> = 0>
1983 #endif
operator <(const T & op1,const U & op2)1984 inline bool operator<(const T &op1, const U &op2)
1985 {
1986 return detail::dispatch_less_than(op1, op2);
1987 }
1988
1989 // Less-than or equal operator.
1990 #if defined(MPPP_HAVE_CONCEPTS)
1991 template <typename T, typename U>
1992 requires rational_real_op_types<T, U>
1993 #else
1994 template <typename T, typename U, detail::enable_if_t<are_rational_real_op_types<T, U>::value, int> = 0>
1995 #endif
operator <=(const T & op1,const U & op2)1996 inline bool operator<=(const T &op1, const U &op2)
1997 {
1998 return !(op1 > op2);
1999 }
2000
2001 // Greater-than operator.
2002 #if defined(MPPP_HAVE_CONCEPTS)
2003 template <typename T, typename U>
2004 requires rational_real_op_types<T, U>
2005 #else
2006 template <typename T, typename U, detail::enable_if_t<are_rational_real_op_types<T, U>::value, int> = 0>
2007 #endif
operator >(const T & op1,const U & op2)2008 inline bool operator>(const T &op1, const U &op2)
2009 {
2010 return detail::dispatch_greater_than(op1, op2);
2011 }
2012
2013 // Greater-than or equal operator.
2014 #if defined(MPPP_HAVE_CONCEPTS)
2015 template <typename T, typename U>
2016 requires rational_real_op_types<T, U>
2017 #else
2018 template <typename T, typename U, detail::enable_if_t<are_rational_real_op_types<T, U>::value, int> = 0>
2019 #endif
operator >=(const T & op1,const U & op2)2020 inline bool operator>=(const T &op1, const U &op2)
2021 {
2022 return !(op1 < op2);
2023 }
2024
2025 // Comparison function for rationals.
2026 template <std::size_t SSize>
cmp(const rational<SSize> & op1,const rational<SSize> & op2)2027 inline int cmp(const rational<SSize> &op1, const rational<SSize> &op2)
2028 {
2029 // NOTE: here we have potential for 2 views referring to the same underlying
2030 // object. The same potential issues as described in the mpz_view class may arise.
2031 // Keep an eye on it.
2032 // NOTE: this can probably be improved by implementing the same strategy as in mpq_cmp()
2033 // but based on our primitives:
2034 // - if op1 and op2 are integers, compare the nums,
2035 // - try to see if the limb/bit sizes of nums and dens can tell use immediately which
2036 // number is larger,
2037 // - otherwise, do the two multiplications and compare.
2038 const auto v1 = detail::get_mpq_view(op1);
2039 const auto v2 = detail::get_mpq_view(op2);
2040 return mpq_cmp(&v1, &v2);
2041 }
2042
2043 // Comparison function for rational/integer arguments.
2044 template <std::size_t SSize>
cmp(const rational<SSize> & op1,const integer<SSize> & op2)2045 inline int cmp(const rational<SSize> &op1, const integer<SSize> &op2)
2046 {
2047 // NOTE: mpq_cmp_z() is a macro or a function, depending on the GMP version. Don't
2048 // call it with "::".
2049 const auto v1 = detail::get_mpq_view(op1);
2050 return mpq_cmp_z(&v1, op2.get_mpz_view());
2051 }
2052
2053 // Comparison function for integer/rational arguments.
2054 template <std::size_t SSize>
cmp(const integer<SSize> & op1,const rational<SSize> & op2)2055 inline int cmp(const integer<SSize> &op1, const rational<SSize> &op2)
2056 {
2057 // NOTE: this will clamp the return value in the [-1,1] range,
2058 // so not exactly identical to the GMP return value. It's still consistent
2059 // with the rest of GMP/mp++ rational comparison functions.
2060 // NOTE: we don't take directly the negative because it's not clear
2061 // how large the returned value could be. Like this, we prevent any
2062 // possible integral overflow shenanigans.
2063 return -detail::integral_sign(cmp(op2, op1));
2064 }
2065
2066 // Sign function.
2067 template <std::size_t SSize>
sgn(const rational<SSize> & q)2068 int sgn(const rational<SSize> &q)
2069 {
2070 return q.sgn();
2071 }
2072
2073 // Test if a rational is one.
2074 template <std::size_t SSize>
is_one(const rational<SSize> & q)2075 inline bool is_one(const rational<SSize> &q)
2076 {
2077 return q.is_one();
2078 }
2079
2080 // Test if a rational is minus one.
2081 template <std::size_t SSize>
is_negative_one(const rational<SSize> & q)2082 inline bool is_negative_one(const rational<SSize> &q)
2083 {
2084 return q.is_negative_one();
2085 }
2086
2087 // Test if a rational is zero.
2088 template <std::size_t SSize>
is_zero(const rational<SSize> & q)2089 inline bool is_zero(const rational<SSize> &q)
2090 {
2091 return q.is_zero();
2092 }
2093
2094 namespace detail
2095 {
2096
2097 // binomial() implementation.
2098 // rational - integer overload.
2099 // NOTE: this is very simplistic and probably really slow. Need to investigate
2100 // better ways of doing this.
2101 template <std::size_t SSize>
rational_binomial_impl(const rational<SSize> & t,const integer<SSize> & b)2102 inline rational<SSize> rational_binomial_impl(const rational<SSize> &t, const integer<SSize> &b)
2103 {
2104 if (t.get_den().is_one()) {
2105 // If top is an integer, forward to the integer overload of binomial().
2106 return rational<SSize>{binomial(t.get_num(), b)};
2107 }
2108 const auto b_sgn = b.sgn();
2109 if (b_sgn == -1) {
2110 // (rational negative-int) will always give zero.
2111 return rational<SSize>{};
2112 }
2113 if (b_sgn == 0) {
2114 // Zero at bottom results always in 1.
2115 return rational<SSize>{1};
2116 }
2117 // Falling factorial implementation.
2118 rational<SSize> tmp{t}, retval = t / b;
2119 integer<SSize> i{b};
2120 for (--i, --tmp; i.sgn() == 1; --i, --tmp) {
2121 retval *= tmp;
2122 retval /= i;
2123 }
2124 return retval;
2125 }
2126
2127 // rational - integral overload.
2128 template <std::size_t SSize, typename T>
rational_binomial_impl(const rational<SSize> & t,const T & b)2129 inline rational<SSize> rational_binomial_impl(const rational<SSize> &t, const T &b)
2130 {
2131 return rational_binomial_impl(t, integer<SSize>{b});
2132 }
2133
2134 } // namespace detail
2135
2136 // Binomial coefficient.
2137 #if defined(MPPP_HAVE_CONCEPTS)
2138 template <std::size_t SSize, rational_integral_interoperable<SSize> T>
2139 #else
2140 template <std::size_t SSize, typename T,
2141 detail::enable_if_t<is_rational_integral_interoperable<T, SSize>::value, int> = 0>
2142 #endif
binomial(const rational<SSize> & x,const T & y)2143 inline rational<SSize> binomial(const rational<SSize> &x, const T &y)
2144 {
2145 return detail::rational_binomial_impl(x, y);
2146 }
2147
2148 namespace detail
2149 {
2150
2151 // rational base, integral exponent implementation. Assumes exp is non-null.
2152 template <std::size_t SSize, typename T>
pow_impl_impl(const rational<SSize> & base,const T & exp,int exp_sign)2153 inline rational<SSize> pow_impl_impl(const rational<SSize> &base, const T &exp, int exp_sign)
2154 {
2155 assert(exp_sign != 0);
2156 rational<SSize> retval;
2157 if (exp_sign == 1) {
2158 retval._get_num() = pow(base.get_num(), exp);
2159 retval._get_den() = pow(base.get_den(), exp);
2160 } else {
2161 // Make integer copy of exp for safe negation.
2162 typename rational<SSize>::int_t exp_copy(exp);
2163 exp_copy.neg();
2164 retval._get_num() = pow(base.get_den(), exp_copy);
2165 retval._get_den() = pow(base.get_num(), exp_copy);
2166 fix_den_sign(retval);
2167 }
2168 return retval;
2169 }
2170
2171 // rational base, rational exponent implementation. Works only if exponent is an integral value, will call the other
2172 // impl_impl overload or error out.
2173 template <std::size_t SSize>
pow_impl_impl(const rational<SSize> & base,const rational<SSize> & exp,int exp_sign)2174 inline rational<SSize> pow_impl_impl(const rational<SSize> &base, const rational<SSize> &exp, int exp_sign)
2175 {
2176 if (mppp_unlikely(!is_one(exp.get_den()))) {
2177 throw std::domain_error("Cannot raise the rational base " + base.to_string() + " to the non-integral exponent "
2178 + exp.to_string());
2179 }
2180 return pow_impl_impl(base, exp.get_num(), exp_sign);
2181 }
2182
2183 // Rational base, integral or rational exp.
2184 template <
2185 std::size_t SSize, typename T,
2186 enable_if_t<disjunction<is_rational_integral_interoperable<T, SSize>, std::is_same<T, rational<SSize>>>::value,
2187 int> = 0>
pow_impl(const rational<SSize> & base,const T & exp)2188 inline rational<SSize> pow_impl(const rational<SSize> &base, const T &exp)
2189 {
2190 const auto exp_sign = sgn(exp);
2191 // Handle special cases first.
2192 if (is_one(base) || exp_sign == 0) {
2193 // 1**q == 1 ∀ q, q**0 == 1 ∀ q.
2194 return rational<SSize>{1};
2195 }
2196 if (is_zero(base)) {
2197 if (exp_sign == 1) {
2198 // 0**q == 0 ∀ q > 0.
2199 return rational<SSize>{};
2200 }
2201 // 0**q with q < 0 -> division by zero.
2202 throw zero_division_error("Cannot raise rational zero to the negative exponent " + to_string(exp));
2203 }
2204 return pow_impl_impl(base, exp, exp_sign);
2205 }
2206
2207 // Integral base, rational exponent.
2208 template <std::size_t SSize, typename T, enable_if_t<is_rational_integral_interoperable<T, SSize>::value, int> = 0>
pow_impl(const T & base,const rational<SSize> & exp)2209 inline rational<SSize> pow_impl(const T &base, const rational<SSize> &exp)
2210 {
2211 return pow_impl(rational<SSize>{base}, exp);
2212 }
2213
2214 // Rational base, fp/complex exponent.
2215 template <std::size_t SSize, typename T,
2216 enable_if_t<disjunction<is_cpp_floating_point<T>, is_cpp_complex<T>>::value, int> = 0>
pow_impl(const rational<SSize> & base,const T & exp)2217 inline T pow_impl(const rational<SSize> &base, const T &exp)
2218 {
2219 return std::pow(static_cast<T>(base), exp);
2220 }
2221
2222 // Fp/complex base, rational exponent.
2223 template <std::size_t SSize, typename T,
2224 enable_if_t<disjunction<is_cpp_floating_point<T>, is_cpp_complex<T>>::value, int> = 0>
pow_impl(const T & base,const rational<SSize> & exp)2225 inline T pow_impl(const T &base, const rational<SSize> &exp)
2226 {
2227 return std::pow(base, static_cast<T>(exp));
2228 }
2229
2230 } // namespace detail
2231
2232 // Binary exponentiation.
2233 #if defined(MPPP_HAVE_CONCEPTS)
2234 template <typename T, typename U>
2235 requires rational_op_types<T, U>
2236 inline auto
2237 #else
2238 template <typename T, typename U>
2239 inline detail::rational_common_t<T, U>
2240 #endif
pow(const T & base,const U & exp)2241 pow(const T &base, const U &exp)
2242 {
2243 return detail::pow_impl(base, exp);
2244 }
2245
2246 // Canonicalise.
2247 template <std::size_t SSize>
canonicalise(rational<SSize> & rop)2248 inline rational<SSize> &canonicalise(rational<SSize> &rop)
2249 {
2250 return rop.canonicalise();
2251 }
2252
2253 // Hash value.
2254 template <std::size_t SSize>
hash(const rational<SSize> & q)2255 inline std::size_t hash(const rational<SSize> &q)
2256 {
2257 // NOTE: just return the sum of the hashes. We are already doing
2258 // some hashing in the integers, hopefully this is enough to obtain
2259 // decent hashing on the rational as well.
2260 return hash(q.get_num()) + hash(q.get_den());
2261 }
2262
2263 // Implementation of integer's assignment
2264 // from rational.
2265 template <std::size_t SSize>
operator =(const rational<SSize> & q)2266 inline integer<SSize> &integer<SSize>::operator=(const rational<SSize> &q)
2267 {
2268 // NOLINTNEXTLINE(cppcoreguidelines-c-copy-assignment-signature, misc-unconventional-assign-operator)
2269 return *this = static_cast<integer<SSize>>(q);
2270 }
2271
2272 } // namespace mppp
2273
2274 #if defined(MPPP_WITH_BOOST_S11N)
2275
2276 // Disable tracking for mppp::rational.
2277 // NOTE: this code has been lifted from the Boost
2278 // macro, which does not support directly
2279 // class templates.
2280
2281 namespace boost
2282 {
2283
2284 namespace serialization
2285 {
2286
2287 template <std::size_t SSize>
2288 struct tracking_level<mppp::rational<SSize>> {
2289 typedef mpl::integral_c_tag tag;
2290 typedef mpl::int_<track_never> type;
2291 BOOST_STATIC_CONSTANT(int, value = tracking_level::type::value);
2292 BOOST_STATIC_ASSERT((mpl::greater<implementation_level<mppp::rational<SSize>>, mpl::int_<primitive_type>>::value));
2293 };
2294
2295 } // namespace serialization
2296
2297 } // namespace boost
2298
2299 #endif
2300
2301 namespace std
2302 {
2303
2304 // Specialisation of std::hash for mppp::rational.
2305 template <size_t SSize>
2306 struct hash<mppp::rational<SSize>> {
2307 #if MPPP_CPLUSPLUS < 201703L
2308 using argument_type = mppp::rational<SSize>;
2309 using result_type = size_t;
2310 #endif
operator ()std::hash2311 size_t operator()(const mppp::rational<SSize> &q) const
2312 {
2313 return mppp::hash(q);
2314 }
2315 };
2316 } // namespace std
2317
2318 #include <mp++/detail/rational_literals.hpp>
2319
2320 // Support for pretty printing in xeus-cling.
2321 #if defined(__CLING__)
2322
2323 #if __has_include(<nlohmann/json.hpp>)
2324
2325 #include <nlohmann/json.hpp>
2326
2327 namespace mppp
2328 {
2329
2330 template <std::size_t SSize>
mime_bundle_repr(const rational<SSize> & q)2331 inline nlohmann::json mime_bundle_repr(const rational<SSize> &q)
2332 {
2333 auto bundle = nlohmann::json::object();
2334
2335 bundle["text/plain"] = q.to_string();
2336 if (q.get_den().is_one()) {
2337 bundle["text/latex"] = "$" + q.get_num().to_string() + "$";
2338 } else {
2339 bundle["text/latex"] = "$\\frac{" + q.get_num().to_string() + "}{" + q.get_den().to_string() + "}$";
2340 }
2341
2342 return bundle;
2343 }
2344
2345 } // namespace mppp
2346
2347 #endif
2348
2349 #endif
2350
2351 #endif
2352