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