Pangolin
Header-only C++20 plane computational geometry library
Loading...
Searching...
No Matches
rational.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "core/bigint.hpp"
4
12
13#include <cmath>
14#include <iostream>
15#include <stdexcept>
16#include <numeric>
17#include <type_traits>
18#include <compare>
19#include <limits>
20#include <functional>
21#include <cstdint>
22#include <utility>
23#include <concepts>
24#include <cassert>
25#include <cstddef>
26
27
28
29namespace pgl {
30template <class T>
31struct is_Rational : std::false_type {};
32
33template <class T>
34struct is_Rational<Rational<T>> : std::true_type {};
35
36template <class T>
37inline constexpr bool is_Rational_v = is_Rational<T>::value;
38
39template <class T>
41
50template <class T>
52 using type = T;
53};
54
55template <class Int>
56struct rational_int<Rational<Int>> {
57 using type = Int;
58};
59
60template <class T>
62
74template <class T>
75using grid_number_t = std::conditional_t<std::signed_integral<rational_int_t<T>>,
76 rational_int_t<T>, std::int64_t>;
77
78namespace detail {
83inline pgl::BigInt pow2(int k) {
84 pgl::BigInt result(1), base(2);
85 while (k > 0) {
86 if (k & 1) result *= base;
87 base *= base;
88 k >>= 1;
89 }
90 return result;
91}
92} // namespace detail
93
94
105template <class Int = int64_t>
106class Rational {
107private:
108 Int num;
109 Int den;
110 bool normalized_ = true;
111
123 static constexpr std::size_t heapReductionLimbs = 8;
124
135 static constexpr bool reductionUrgent(const Int& n, const Int& d) {
136 if constexpr (requires { n.fitsInt64(); }) {
137 // BigInt, and the answer turns on what the gcd itself will cost.
138 //
139 // While both parts are inline (a single int128), BigInt divides on
140 // its native fast path, so the gcd is a handful of machine
141 // divisions. Reduce at half that width, exactly as the fixed-width
142 // rule below does, and the impending multiply or cross-addition
143 // stays inline too.
144 //
145 // Once a part has spilled onto the heap that bargain is off: every
146 // step of the gcd becomes a multi-limb long division, which costs
147 // far more than carrying the extra limbs through the one multiply
148 // the reduction was meant to shrink — and @ref operandParts throws
149 // the reduced form away with its locals, so the same value pays
150 // again at every use. Past that point reduce only to stop unbounded
151 // growth, not to save width.
152 if (n.fitsInt128() && d.fitsInt128()) {
153 return !n.fitsInt64() || !d.fitsInt64();
154 }
155 return !n.fitsLimbs(heapReductionLimbs) || !d.fitsLimbs(heapReductionLimbs);
156 } else {
157 constexpr int half = pgl::detail::numeric_limits<Int>::digits / 2 - 1;
158 const Int limit = Int(1) << half;
159 return pgl::detail::abs(n) >= limit || d >= limit;
160 }
161 }
162
177 static constexpr bool comparisonNeedsReduction(const Int& n, const Int& d) {
178 if constexpr (requires { n.fitsInt64(); }) {
179 return false;
180 } else {
181 return reductionUrgent(n, d);
182 }
183 }
184
195 constexpr bool storedInteger() const {
196 // An arbitrary-precision Int answers this off its inline store; building
197 // a value to compare against would cost more than the shortcut saves.
198 if constexpr (requires { den.isOne(); }) {
199 return den.isOne();
200 } else {
201 return den == Int(1);
202 }
203 }
204
209 constexpr void operandParts(Int& n, Int& d) const {
210 n = num;
211 d = den;
212 if (!normalized_ && reductionUrgent(n, d)) {
213 const Int g = pgl::detail::gcd(pgl::detail::abs(n), d);
214 n /= g;
215 d /= g;
216 }
217 }
218
219public:
223 constexpr Rational() : num(0), den(1) {}
224
232 constexpr Rational(RationalConcept auto n) : num(0), den(1) {
233 n.simplify();
234 num = Int(n.numerator());
235 den = Int(n.denominator());
236 }
237
241 constexpr Rational(pgl::detail::extended_integral auto n) : num(n), den(1) {}
242
258 template <std::same_as<Int> T>
259 requires(!pgl::detail::extended_integral<Int> && !std::floating_point<Int>
261 constexpr Rational(T n) : num(std::move(n)), den(1) {}
262
266 constexpr Rational(Int n, Int d, bool normalized = false)
267 : num(n), den(d), normalized_(normalized) {
268 if (den < 0) {
269 num = -num;
270 den = -den;
271 }
272
273 assert(den > 0);
274 // The reduction is deferred: it happens lazily at a read, a comparison,
275 // or when an arithmetic step would otherwise risk overflow. Integers are
276 // already in lowest terms, so flag them normalized to skip that work.
277 if (den == 1) normalized_ = true;
278 }
279
296 template<std::floating_point Float>
297 explicit constexpr Rational(Float f,
298 int digits = pgl::detail::numeric_limits<Int>::digits > 0
299 ? std::min(pgl::detail::numeric_limits<Float>::digits,
300 pgl::detail::numeric_limits<Int>::digits / 2 - 4)
301 : pgl::detail::numeric_limits<Float>::digits) {
302 if (!std::isfinite(f)) {
303 throw std::domain_error("pgl::Rational: cannot construct from a non-finite floating-point value");
304 }
305 if (f == 0) {
306 num = 0;
307 den = 1;
308 return;
309 }
310
311 // |f| lies in [2^(exponent - 1), 2^exponent), so exponent is how many
312 // of the requested bits the integer part takes for itself.
313 int exponent = 0;
314 std::frexp(f, &exponent);
315 const int fractionBits = exponent > 0 ? digits - exponent : digits;
316 if (fractionBits <= 0) {
317 // No fraction bit fits beside the integer part: the value is that
318 // part, exactly, whenever the storage type holds it.
319 if constexpr (pgl::detail::numeric_limits<Int>::is_bounded) {
320 if (exponent > pgl::detail::numeric_limits<Int>::digits) {
321 throw std::overflow_error("pgl::Rational: floating-point value does not fit the integer type");
322 }
323 }
324 num = Int(std::trunc(f));
325 den = Int(1);
326 normalized_ = true;
327 return;
328 }
329
330 if constexpr (requires(Int x, Float g) { x * g; }) {
331 bool negative = false;
332 if (f < 0) {
333 negative = true;
334 f = -f;
335 }
336 den = Int(1) << fractionBits;
337 // Wrap in Int(): for the Boost int128 fallback `den * f` yields a
338 // double (see the double-interop shim in numeric.hpp), so convert it
339 // back explicitly. For native __int128 / built-in ints this is the
340 // same truncating conversion that the implicit assignment did.
341 num = Int(negative ? -den * f : den * f);
342
343 normalized_ = false;
344 simplify();
345 } else {
346 // Int has no floating-point multiply (e.g. pgl::BigInt). A float's
347 // significand always fits in a 128-bit integer, so build the value
348 // with the requested number of fractional bits in Rational<int128>
349 // and widen it. This makes float -> Rational<BigInt> work, which the
350 // int128 -> BigInt type promotion relies on for mixed predicates.
351 const Rational<pgl::int128> r(f, digits);
352 num = Int(r.numerator());
353 den = Int(r.denominator());
354 normalized_ = true;
355 }
356 }
357
359 constexpr Int numerator() const noexcept {
360 if (normalized_) return num;
361 return num / pgl::detail::gcd(pgl::detail::abs(num), den);
362 }
363
365 constexpr Int denominator() const noexcept {
366 if (normalized_) return den;
367 return den / pgl::detail::gcd(pgl::detail::abs(num), den);
368 }
369
388 constexpr void simplify() {
389 if (normalized_) {
390 return;
391 }
392 normalized_ = true;
393 assert(den != 0);
394
395 if (num == 0) {
396 den = 1;
397 return;
398 }
399
400 if (den == 1 || num == 1 || num == -1) {
401 return; // a unit numerator or denominator is already in lowest terms
402 }
403
404 Int g = pgl::detail::gcd(pgl::detail::abs(num), den);
405 num /= g;
406 den /= g;
407 }
408
421 [[nodiscard]] constexpr Rational simplified() const {
422 if (normalized_) {
423 return *this;
424 }
425 if (num == 0) {
426 return Rational(Int(0), Int(1), true);
427 }
428 if (den == 1 || num == 1 || num == -1) {
429 return Rational(num, den, true);
430 }
431 const Int g = pgl::detail::gcd(pgl::detail::abs(num), den);
432 return Rational(num / g, den / g, true);
433 }
434
458 constexpr void simplifyIfLarge() {
459 if (!normalized_ && reductionUrgent(num, den)) {
460 simplify();
461 }
462 }
463
474 [[nodiscard]] constexpr Rational simplifiedIfLarge() const {
475 if (!normalized_ && reductionUrgent(num, den)) {
476 return simplified();
477 }
478 return *this;
479 }
480
489 [[nodiscard]] constexpr bool isInteger() const {
490 return normalized_ ? den == Int(1) : num % den == Int(0);
491 }
492
494 explicit constexpr operator float() const {
495 if (den == Int(1)) {
496 return static_cast<float>(num);
497 }
498 return static_cast<float>(num) / static_cast<float>(den);
499 }
500
508 explicit constexpr operator double() const {
509 if (den == Int(1)) {
510 return static_cast<double>(num);
511 }
512 return static_cast<double>(num) / static_cast<double>(den);
513 }
514
516 explicit constexpr operator long double() const {
517 if (den == Int(1)) {
518 return static_cast<long double>(num);
519 }
520 return static_cast<long double>(num) / static_cast<long double>(den);
521 }
522
532 explicit constexpr operator int() const {
533 if (den == Int(1)) {
534 return static_cast<int>(num);
535 }
536 return static_cast<int>(num / den);
537 }
538
541 explicit constexpr operator int64_t() const {
542 if (den == Int(1)) {
543 return static_cast<int64_t>(num);
544 }
545 return static_cast<int64_t>(num / den);
546 }
547
553 explicit constexpr operator Int() const
554 requires(!std::same_as<Int, int> && !std::same_as<Int, int64_t>) {
555 if (den == Int(1)) {
556 return num;
557 }
558 return num / den;
559 }
560
562 template <class OtherInt>
563 explicit constexpr operator Rational<OtherInt>() const {
564 return Rational<OtherInt>(num, den, normalized_);
565 }
566
587 template <std::floating_point Float = double>
588 constexpr Float lowerBound() const {
589 using Double = pgl::detail::promoted_number_t<Float>;
590
591 Double result = static_cast<Double>(num) / static_cast<Double>(den);
592 Float flt_result = static_cast<Float>(result);
593
594#ifndef NDEBUG
595 int i = 0;
596#endif
597 while (static_cast<Double>(flt_result) > result) {
598 flt_result = std::nextafter(flt_result, -pgl::detail::numeric_limits<Float>::infinity());
599 assert(i++ < 10); // Normally one iteration should be enough
600 }
601 // The quotient is itself a rounding of the exact value, so when the
602 // narrowing landed exactly on it nothing above tells which side of the
603 // value the candidate is on: settle that exactly. One step then
604 // suffices, since a float strictly below the quotient is below every
605 // value the quotient can have rounded from.
606 if (static_cast<Double>(flt_result) == result && (*this <=> flt_result) < 0) {
607 flt_result = std::nextafter(flt_result, -pgl::detail::numeric_limits<Float>::infinity());
608 }
609
610 return flt_result;
611 }
612
633 template <std::floating_point Float = double>
634 constexpr Float upperBound() const {
635 using Double = pgl::detail::promoted_number_t<Float>;
636
637 Double result = static_cast<Double>(num) / static_cast<Double>(den);
638 Float flt_result = static_cast<Float>(result);
639
640#ifndef NDEBUG
641 int i = 0;
642#endif
643 while (static_cast<Double>(flt_result) < result) {
644 flt_result = std::nextafter(flt_result, pgl::detail::numeric_limits<Float>::infinity());
645 assert(i++ < 10); // Normally one iteration should be enough
646 }
647 // See lowerBound: a candidate equal to the rounded quotient may still
648 // be below the exact value, and one exact step settles it.
649 if (static_cast<Double>(flt_result) == result && (*this <=> flt_result) > 0) {
650 flt_result = std::nextafter(flt_result, pgl::detail::numeric_limits<Float>::infinity());
651 }
652
653 return flt_result;
654 }
655
656 // Arithmetic results are left unreduced (the gcd is deferred); each operand
657 // is only reduced first when its magnitude would otherwise risk overflow.
658 // An integer result (denominator 1) is trivially in lowest terms.
659
662 constexpr bool safeRaw() const {
663 return normalized_ || !reductionUrgent(num, den);
664 }
665
666 constexpr Rational operator+(const Rational& r) const {
667 // Two integers add and multiply as integers, over the 1 they already
668 // share: the general form below would cross-multiply by it three times.
669 if (storedInteger() && r.storedInteger()) {
670 return Rational(num + r.num, den, true);
671 }
672 if (safeRaw() && r.safeRaw()) {
673 const Int rd = den * r.den;
674 return Rational(num * r.den + r.num * den, rd, rd == 1);
675 }
676 Int an, ad, bn, bd;
677 operandParts(an, ad);
678 r.operandParts(bn, bd);
679 const Int rd = ad * bd;
680 return Rational(an * bd + bn * ad, rd, rd == 1);
681 }
682
683 constexpr Rational operator-(const Rational& r) const {
684 // Two integers add and multiply as integers, over the 1 they already
685 // share: the general form below would cross-multiply by it three times.
686 if (storedInteger() && r.storedInteger()) {
687 return Rational(num - r.num, den, true);
688 }
689 if (safeRaw() && r.safeRaw()) {
690 const Int rd = den * r.den;
691 return Rational(num * r.den - r.num * den, rd, rd == 1);
692 }
693 Int an, ad, bn, bd;
694 operandParts(an, ad);
695 r.operandParts(bn, bd);
696 const Int rd = ad * bd;
697 return Rational(an * bd - bn * ad, rd, rd == 1);
698 }
699
700 constexpr Rational<Int> operator*(const Rational<Int>& r) const {
701 if (storedInteger() && r.storedInteger()) {
702 return Rational(num * r.num, den, true);
703 }
704 if (safeRaw() && r.safeRaw()) {
705 const Int rd = den * r.den;
706 return Rational(num * r.num, rd, rd == 1);
707 }
708 Int an, ad, bn, bd;
709 operandParts(an, ad);
710 r.operandParts(bn, bd);
711 const Int rd = ad * bd;
712 return Rational(an * bn, rd, rd == 1);
713 }
714
715 constexpr Rational reciprocal() const {
716 assert(num != 0);
717 // Swapping numerator and denominator preserves gcd(|num|, den), so the
718 // normalization state carries over unchanged.
719 return num < 0 ? Rational(-den, -num, normalized_)
720 : Rational(den, num, normalized_);
721 }
722
723 constexpr Rational operator/(const Rational& r) const {
724 return *this * r.reciprocal();
725 }
726
727 constexpr Rational operator-() const {
728 // Negation preserves gcd(|num|, den), so the normalization state holds.
729 return Rational(-num, den, normalized_);
730 }
731
732 // Compound
733 constexpr Rational& operator+=(const Rational& r) { return *this = *this + r; }
734 constexpr Rational& operator-=(const Rational& r) { return *this = *this - r; }
735 constexpr Rational& operator*=(const Rational& r) { return *this = *this * r; }
736 constexpr Rational& operator/=(const Rational& r) { return *this = *this / r; }
737
738 // Mixed integer operators. Templating on the (deduced) argument type makes
739 // an integer operand bind without a conversion, so these win cleanly over
740 // operator OP(const Rational&) instead of tying with it. The tie — and the
741 // resulting ambiguity — only arose when `int -> Int` is itself a
742 // user-defined conversion, i.e. for a class-type Int such as BigInt.
743 template <class I>
744 requires (pgl::detail::extended_integral<I> || std::same_as<I, Int>)
745 constexpr Rational operator+(const I& x) const { return *this + Rational(Int(x), true); }
746
747 template <class I>
748 requires (pgl::detail::extended_integral<I> || std::same_as<I, Int>)
749 constexpr Rational operator-(const I& x) const { return *this - Rational(Int(x), true); }
750
751 template <class I>
752 requires (pgl::detail::extended_integral<I> || std::same_as<I, Int>)
753 constexpr Rational operator*(const I& x) const {
754 Int n, d;
755 operandParts(n, d);
756 return Rational(n * Int(x), d);
757 }
758
759 template <class I>
760 requires (pgl::detail::extended_integral<I> || std::same_as<I, Int>)
761 constexpr Rational operator/(const I& x) const {
762 Int n, d;
763 operandParts(n, d);
764 return Rational(n, d * Int(x));
765 }
766
767 friend constexpr Rational operator+(Int x, const Rational& r) { return r + x; }
768 friend constexpr Rational operator-(Int x, const Rational& r) { return r + (-x); }
769 friend constexpr Rational operator*(Int x, const Rational& r) { return r * x; }
770 friend constexpr Rational operator/(Int x, const Rational& r) {
771 return x == 1 ? r.reciprocal() : Rational(x) / r;
772 }
773
790 template <class A, class B>
791 static constexpr std::strong_ordering compareValues(const A& a, const B& b) {
792 if constexpr (requires {
793 { a <=> b } -> std::same_as<std::strong_ordering>;
794 }) {
795 return a <=> b;
796 } else {
797 if (a < b)
798 return std::strong_ordering::less;
799 if (b < a)
800 return std::strong_ordering::greater;
801 return std::strong_ordering::equal;
802 }
803 }
804
808 constexpr std::strong_ordering operator<=>(const Rational& r) const {
809 using Wide = pgl::detail::promoted_number_t<Int>;
810 // Deferred fractions compare exactly by cross-multiplying in the wider
811 // type (both denominators are positive, so the sign is preserved, and
812 // compareValues yields the equal case). Reduce first only when an operand
813 // is large enough that the widened product could itself overflow.
814 // Two integers compare as integers: both cross products below would be
815 // a multiplication by one, and over a BigInt that is a real one.
816 if (storedInteger() && r.storedInteger()) {
817 return compareValues(num, r.num);
818 }
819 if ((!normalized_ && comparisonNeedsReduction(num, den)) ||
820 (!r.normalized_ && comparisonNeedsReduction(r.num, r.den))) {
821 Int an, ad, bn, bd;
822 operandParts(an, ad);
823 r.operandParts(bn, bd);
824 return compareValues(static_cast<Wide>(an) * bd, static_cast<Wide>(bn) * ad);
825 }
826 return compareValues(static_cast<Wide>(num) * r.den,
827 static_cast<Wide>(r.num) * den);
828 }
829
839 template <class U>
840 requires (!std::same_as<U, Int>)
841 constexpr std::strong_ordering operator<=>(const Rational<U>& r) const {
842 using Common = std::common_type_t<Int, U>;
843 using Wide = pgl::detail::promoted_number_t<Common>;
844 // Both parts of the other value are wanted, and a deferred fraction
845 // reduces itself anew on each read, so reduce it once and read twice.
846 const Rational<U> other = r.simplified();
847 const Wide lhs = static_cast<Wide>(num) * static_cast<Wide>(other.denominator());
848 const Wide rhs = static_cast<Wide>(other.numerator()) * static_cast<Wide>(den);
849 return compareValues(lhs, rhs);
850 }
851
855 constexpr bool operator==(const Rational& r) const {
856 using Wide = pgl::detail::promoted_number_t<Int>;
857 if (storedInteger() && r.storedInteger()) {
858 return num == r.num;
859 }
860 // Deferred fractions are equal iff their cross products match; reduce
861 // first only when an operand could overflow the widened product.
862 if ((!normalized_ && comparisonNeedsReduction(num, den)) ||
863 (!r.normalized_ && comparisonNeedsReduction(r.num, r.den))) {
864 Int an, ad, bn, bd;
865 operandParts(an, ad);
866 r.operandParts(bn, bd);
867 return static_cast<Wide>(an) * bd == static_cast<Wide>(bn) * ad;
868 }
869 return static_cast<Wide>(num) * r.den == static_cast<Wide>(r.num) * den;
870 }
871 constexpr bool operator!=(const Rational& r) const {
872 return !(*this == r);
873 }
874
882 template <class U>
883 requires (!std::same_as<U, Int>)
884 constexpr bool operator==(const Rational<U>& r) const {
885 const Rational mine = simplified();
886 const Rational<U> other = r.simplified();
887 return mine.num == other.numerator() && mine.den == other.denominator();
888 }
889
890 // --- comparison against floating point ---
891 //
892 // Exact for every finite value and exponent. The float f is decomposed by
893 // frexp into an integer significand times a power of two (f == sig * 2^E,
894 // with |sig| < 2^digits so it fits an int128), and num/den <=> sig*2^E is
895 // settled by cross-multiplying through the positive denominator in pgl::BigInt
896 // so the binary shift can never overflow. No precision is lost regardless of
897 // the magnitudes involved. Being members, these also cover the reversed forms
898 // (`f == r`, `f < r`, ...) via C++20 rewriting. The float -> Rational
899 // constructor is explicit and lossy, so an implicit conversion never silently
900 // approximates f here; this overload is the only float comparison path.
901 //
902 // Not constexpr: pgl::BigInt arithmetic is not constexpr-capable.
903
906 template <std::floating_point Float>
907 std::partial_ordering operator<=>(Float f) const {
908 if (std::isnan(f))
909 return std::partial_ordering::unordered;
910 if (std::isinf(f))
911 return f > 0 ? std::partial_ordering::less
912 : std::partial_ordering::greater;
913
914 // 0 and ±1 are settled in the integer domain, skipping the BigInt
915 // decomposition below entirely: den > 0, so the answer is the numerator
916 // against 0 or against ±den. (-0.0 compares equal to 0 and is handled
917 // here as the zero it is.)
918 if (f == 0) return compareValues(num, Int(0));
919 if (f == 1) return compareValues(num, den);
920 if (f == -1) return compareValues(num, -den);
921
922 // f == sig * 2^E exactly.
923 int exponent;
924 const Float frac = std::frexp(f, &exponent);
925 const int digits = pgl::detail::numeric_limits<Float>::digits;
926 const pgl::int128 sig = static_cast<pgl::int128>(std::ldexp(frac, digits));
927 const int E = exponent - digits;
928
929 // Compare num/den against sig*2^E by multiplying through by den > 0:
930 // sign(num/den - f) == sign(num - den*sig*2^E).
931 // The factor of 2^|E| is moved to whichever side keeps the exponent
932 // non-negative, and all of it is done in BigInt so nothing overflows.
933 pgl::BigInt lhs(num);
934 pgl::BigInt rhs = pgl::BigInt(den) * pgl::BigInt(sig);
935 if (E >= 0)
936 rhs *= detail::pow2(E);
937 else
938 lhs *= detail::pow2(-E);
939 return lhs <=> rhs;
940 }
941
943 template <std::floating_point Float>
944 bool operator==(Float f) const {
945 return std::isfinite(f) &&
946 (*this <=> f) == std::partial_ordering::equivalent;
947 }
948
949 // --- comparison against integers ---
950 //
951 // An integer converts implicitly to Rational, so `r == i` and `i < r` already
952 // work through the same-type operators. `i == r` does not, and neither does
953 // `i != r`: C++20 builds the reversed candidate for `==` out of the *declared*
954 // operator== functions, and the same-type one only matches after converting
955 // the integer, which is not something a reversed candidate is formed through.
956 // The relational forms escape this because they are rewritten via operator<=>,
957 // which is why `i < r` compiles while `i == r` does not — an asymmetry that
958 // surfaces wherever a predicate compares two coordinates of different types,
959 // such as Rectangle<Point<Rational>>::boundaryContains(Point<int>).
960 //
961 // Declaring the integer comparison as its own member template fixes both
962 // directions at once, exactly as the floating-point overloads above already
963 // do: the template matches the integer directly, so the reversed candidate is
964 // formed without any user-defined conversion. `Int` is admitted alongside the
965 // built-in integers to cover a storage type that is not one of them, BigInt
966 // being the case that matters here.
967
973 template <class I>
974 static constexpr bool isNegativeOne(const I& n) {
975 if constexpr (std::is_unsigned_v<I>) {
976 return false;
977 } else {
978 return n == -1;
979 }
980 }
981
997 template <class I>
998 requires(pgl::detail::extended_integral<I> || std::same_as<I, Int>)
999 constexpr std::strong_ordering operator<=>(const I& n) const {
1000 using Wide = pgl::detail::promoted_number_t<Int>;
1001
1002 // The three constant tests come first: the integer is nearly always a
1003 // literal, and then they fold away at compile time along with every
1004 // branch not taken.
1005 if (n == 0) return compareValues(num, Int(0));
1006 if (n == 1) return compareValues(num, den);
1007 if (isNegativeOne(n)) return compareValues(num, -den);
1008 if (den == 1) return compareValues(num, static_cast<Wide>(n));
1009
1010 // The general case multiplies through the positive denominator. Only the
1011 // integer is cast to the wide type: the denominator widens on its own
1012 // inside the product, and naming Wide for it too would copy an
1013 // arbitrary-precision denominator for nothing. A deferred fraction is
1014 // reduced first only when that product could overflow, exactly as the
1015 // Rational-vs-Rational comparison does.
1016 if (!normalized_ && comparisonNeedsReduction(num, den)) {
1017 Int an, ad;
1018 operandParts(an, ad);
1019 return compareValues(an, ad * static_cast<Wide>(n));
1020 }
1021 return compareValues(num, den * static_cast<Wide>(n));
1022 }
1023
1029 template <class I>
1030 requires(pgl::detail::extended_integral<I> || std::same_as<I, Int>)
1031 constexpr bool operator==(const I& n) const {
1032 return (*this <=> n) == 0;
1033 }
1034
1035 constexpr Rational& operator++() { return *this += 1; }
1036 constexpr Rational operator++(int) { Rational tmp = *this; ++(*this); return tmp; }
1037
1038 constexpr Rational& operator--() { return *this -= 1; }
1039 constexpr Rational operator--(int) { Rational tmp = *this; --(*this); return tmp; }
1040
1044 friend std::ostream& operator<<(std::ostream& os, const Rational& r) {
1045 // Reduced once, rather than once per part read.
1046 const Rational reduced = r.simplified();
1047 if (reduced.den == 1)
1048 return os << reduced.num;
1049 else
1050 return os << reduced.num << "/" << reduced.den;
1051 }
1052
1056 friend std::istream& operator>>(std::istream& is, Rational& r) {
1057 Int n, d = 1;
1058 char sep = 0;
1059
1060 if (!(is >> n)) return is;
1061
1062 // A bare integer (no "/den" suffix) is a valid, successful parse, but
1063 // peek() at end-of-stream sets failbit even though nothing is wrong;
1064 // clear it so a plain integer at the end of the stream doesn't fail.
1065 if (!is.eof()) {
1066 if (is.peek() == '/') {
1067 is >> sep >> d;
1068 } else if (is.fail()) {
1069 is.clear(is.rdstate() & ~std::ios::failbit);
1070 }
1071 }
1072
1073 r = Rational(n, d);
1074 return is;
1075 }
1076};
1077
1092template <class T>
1093 requires(!pgl::detail::extended_integral<T> && !std::floating_point<T>
1094 && !RationalConcept<T>)
1097
1098
1099
1100// Primary template: no valid type by default
1101template <int Bits>
1102struct select_int_ge
1103{
1104 using type = void;
1105};
1106
1107// Specializations for supported sizes
1108template <> struct select_int_ge<8> { using type = std::int8_t; };
1109template <> struct select_int_ge<16> { using type = std::int16_t; };
1110template <> struct select_int_ge<32> { using type = std::int32_t; };
1111template <> struct select_int_ge<64> { using type = std::int64_t; };
1112template <> struct select_int_ge<128> { using type = pgl::int128; };
1113
1114// Helper to round required bits up to the next supported size
1115constexpr int round_up_bits(int bits) {
1116 if (bits <= 8) return 8;
1117 if (bits <= 16) return 16;
1118 if (bits <= 32) return 32;
1119 if (bits <= 64) return 64;
1120 return 128;
1121}
1122
1123template <typename T>
1124struct is_int128 : std::false_type {};
1125
1126template <> struct is_int128<pgl::int128> : std::true_type {};
1127#ifdef __SIZEOF_INT128__
1128template <> struct is_int128<__uint128_t> : std::true_type {};
1129#endif
1130
1131template <typename T>
1133 std::is_integral_v<T> ||
1134 std::is_floating_point_v<T> ||
1135 is_int128<T>::value;
1136
1137template <NumericType T>
1138struct to_integer_with_digits {
1139private:
1140 static constexpr int bits = pgl::detail::numeric_limits<T>::digits;
1141 static constexpr int rounded = round_up_bits(bits);
1142
1143public:
1144 using type = typename select_int_ge<rounded>::type;
1145};
1146
1147template <typename T>
1148using to_integer_with_digits_t = typename to_integer_with_digits<T>::type;
1149
1152
1164template <class Number>
1166 using NumberType = std::remove_cvref_t<Number>;
1167 using type = std::conditional_t<
1168 std::floating_point<NumberType> || RationalConcept<NumberType>,
1169 NumberType,
1170 ERational>;
1171};
1172
1174template <class Number>
1176
1177}// pgl
1178
1179template<class U, class V>
1180struct std::common_type<pgl::Rational<U>, pgl::Rational<V>>{
1182};
1183
1184// Mixing a Rational with a floating-point type yields the floating-point type:
1185// the exact value is intentionally abandoned (the float→Rational conversion is
1186// explicit and lossy), so promotion collapses toward the inexact representation
1187// rather than silently approximating the float as a Rational.
1188template<class U, std::floating_point F>
1189struct std::common_type<pgl::Rational<U>, F>{
1190 using type = F;
1191};
1192template<std::floating_point F, class V>
1193struct std::common_type<F, pgl::Rational<V>>{
1194 using type = F;
1195};
1196
1200template <class Int>
1201struct std::numeric_limits<pgl::Rational<Int>> {
1202 static constexpr bool is_specialized = true;
1203
1204 static constexpr pgl::Rational<Int> min() noexcept {
1205 return pgl::Rational<Int>(pgl::detail::numeric_limits<Int>::min(), 1);
1206 }
1207
1208 static constexpr pgl::Rational<Int> max() noexcept {
1209 return pgl::Rational<Int>(pgl::detail::numeric_limits<Int>::max(), 1);
1210 }
1211
1212 static constexpr pgl::Rational<Int> lowest() noexcept {
1213 return min();
1214 }
1215
1216 static constexpr int digits = pgl::detail::numeric_limits<Int>::digits;
1217 static constexpr int digits10 = pgl::detail::numeric_limits<Int>::digits10;
1218
1219 static constexpr bool is_signed = pgl::detail::numeric_limits<Int>::is_signed;
1220 static constexpr bool is_integer = false;
1221 static constexpr bool is_exact = true;
1222
1223 // Remaining members keep the specialization complete so generic trait
1224 // machinery (e.g. Boost.Multiprecision, used as the int128 fallback) can
1225 // classify Rational. With is_integer == false and max_exponent == 0 it is
1226 // treated as an "unknown" category, so Boost never tries to convert it.
1227 static constexpr int radix = 2;
1228 static constexpr int min_exponent = 0;
1229 static constexpr int min_exponent10 = 0;
1230 static constexpr int max_exponent = 0;
1231 static constexpr int max_exponent10 = 0;
1232 static constexpr bool is_bounded = pgl::detail::numeric_limits<Int>::is_bounded;
1233 static constexpr bool is_modulo = false;
1234 static constexpr bool is_iec559 = false;
1235 static constexpr bool has_infinity = false;
1236 static constexpr bool has_quiet_NaN = false;
1237 static constexpr bool has_signaling_NaN = false;
1238 static constexpr bool traps = false;
1239 static constexpr bool tinyness_before = false;
1240 static constexpr std::float_round_style round_style = std::round_toward_zero;
1241
1242 static constexpr pgl::Rational<Int> epsilon() noexcept { return pgl::Rational<Int>(0, 1); }
1243 static constexpr pgl::Rational<Int> round_error() noexcept { return pgl::Rational<Int>(0, 1); }
1244 static constexpr pgl::Rational<Int> infinity() noexcept { return pgl::Rational<Int>(0, 1); }
1245 static constexpr pgl::Rational<Int> quiet_NaN() noexcept { return pgl::Rational<Int>(0, 1); }
1246 static constexpr pgl::Rational<Int> signaling_NaN() noexcept { return pgl::Rational<Int>(0, 1); }
1247 static constexpr pgl::Rational<Int> denorm_min() noexcept { return pgl::Rational<Int>(0, 1); }
1248};
Arbitrary precision signed integers, optimized for small values.
Arbitrary precision signed integer.
Definition bigint.hpp:157
Exact rational number class template.
Definition rational.hpp:106
constexpr Rational operator--(int)
Definition rational.hpp:1039
constexpr bool operator!=(const Rational &r) const
Definition rational.hpp:871
static constexpr std::strong_ordering compareValues(const A &a, const B &b)
Three-way ordering of two values.
Definition rational.hpp:791
constexpr Rational(Int n, Int d, bool normalized=false)
Construct from numerator and denominator.
Definition rational.hpp:266
constexpr Float lowerBound() const
Computes a monotone, downward-rounded floating-point approximation of num / den.
Definition rational.hpp:588
constexpr Rational< Int > operator*(const Rational< Int > &r) const
Definition rational.hpp:700
constexpr Rational(RationalConcept auto n)
Construct from another Rational.
Definition rational.hpp:232
constexpr Int numerator() const noexcept
Get numerator (in lowest terms).
Definition rational.hpp:359
constexpr Rational & operator+=(const Rational &r)
Definition rational.hpp:733
constexpr void simplify()
Reduces the stored fraction to lowest terms in place (den stays > 0).
Definition rational.hpp:388
constexpr bool safeRaw() const
Whether the raw num/den can be combined as-is without first reducing (i.e. already normalized,...
Definition rational.hpp:662
friend constexpr Rational operator-(Int x, const Rational &r)
Definition rational.hpp:768
constexpr Rational reciprocal() const
Definition rational.hpp:715
constexpr Rational & operator--()
Definition rational.hpp:1038
constexpr Float upperBound() const
Computes a monotone, upward-rounded floating-point approximation of num / den.
Definition rational.hpp:634
constexpr Rational & operator/=(const Rational &r)
Definition rational.hpp:736
constexpr Rational operator-(const Rational &r) const
Definition rational.hpp:683
constexpr Rational(Float f, int digits=pgl::detail::numeric_limits< Int >::digits > 0 ? std::min(pgl::detail::numeric_limits< Float >::digits, pgl::detail::numeric_limits< Int >::digits/2 - 4) :pgl::detail::numeric_limits< Float >::digits)
Construct from floating point.
Definition rational.hpp:297
friend std::ostream & operator<<(std::ostream &os, const Rational &r)
Output format: num/den or just num if den==1.
Definition rational.hpp:1044
constexpr Int denominator() const noexcept
Get denominator (in lowest terms).
Definition rational.hpp:365
constexpr std::strong_ordering operator<=>(const Rational &r) const
Three-way comparison operator.
Definition rational.hpp:808
constexpr Rational operator+(const Rational &r) const
Definition rational.hpp:666
constexpr Rational & operator-=(const Rational &r)
Definition rational.hpp:734
std::partial_ordering operator<=>(Float f) const
Exact three-way comparison against a floating-point value (partial: NaN compares unordered).
Definition rational.hpp:907
friend std::istream & operator>>(std::istream &is, Rational &r)
Input format: num/den or integer.
Definition rational.hpp:1056
constexpr Rational(T n)
Construct from a numerator of the rational's own integer type (denominator 1), covering integer types...
Definition rational.hpp:261
constexpr Rational simplifiedIfLarge() const
Returns this value reduced, but only when it is already wide enough that the next arithmetic step wou...
Definition rational.hpp:474
constexpr Rational & operator*=(const Rational &r)
Definition rational.hpp:735
constexpr Rational operator-() const
Definition rational.hpp:727
constexpr Rational operator/(const Rational &r) const
Definition rational.hpp:723
constexpr Rational()
Default constructor (0).
Definition rational.hpp:223
static constexpr bool isNegativeOne(const I &n)
Whether an integer argument is exactly -1.
Definition rational.hpp:974
bool operator==(Float f) const
Exact equality against a floating-point value.
Definition rational.hpp:944
friend constexpr Rational operator/(Int x, const Rational &r)
Definition rational.hpp:770
constexpr Rational & operator++()
Definition rational.hpp:1035
constexpr Rational operator++(int)
Definition rational.hpp:1036
friend constexpr Rational operator*(Int x, const Rational &r)
Definition rational.hpp:769
constexpr void simplifyIfLarge()
Reduces in place, but only when the stored parts have already grown wide enough that the next arithme...
Definition rational.hpp:458
constexpr Rational(pgl::detail::extended_integral auto n)
Construct from integer.
Definition rational.hpp:241
constexpr Rational simplified() const
Returns this value reduced to lowest terms.
Definition rational.hpp:421
constexpr bool operator==(const Rational &r) const
Equality comparison.
Definition rational.hpp:855
friend constexpr Rational operator+(Int x, const Rational &r)
Definition rational.hpp:767
constexpr bool isInteger() const
Whether this rational is exactly an integer.
Definition rational.hpp:489
Definition rational.hpp:1132
Definition rational.hpp:40
Definition arrangement.hpp:67
constexpr int round_up_bits(int bits)
Definition rational.hpp:1115
@ x
Definition intervaltree.hpp:24
std::conditional_t< std::signed_integral< rational_int_t< T > >, rational_int_t< T >, std::int64_t > grid_number_t
The integer type a coordinate rasterizes onto by default.
Definition rational.hpp:75
constexpr bool is_Rational_v
Definition rational.hpp:37
typename DivisionResult< Number >::type division_result_t
Convenience alias for DivisionResult.
Definition rational.hpp:1175
Rational< BigInt > ERational
Exact, overflow-free result used when integral coordinates require fractions.
Definition rational.hpp:1151
boost::multiprecision::number< boost::multiprecision::cpp_int_backend< 127, 127, boost::multiprecision::signed_magnitude, boost::multiprecision::unchecked, void > > int128
Signed 128-bit integer.
Definition numeric.hpp:64
Rational(T) -> Rational< T >
typename rational_int< T >::type rational_int_t
Definition rational.hpp:61
typename to_integer_with_digits< T >::type to_integer_with_digits_t
Definition rational.hpp:1148
Default result type for an operation that may require division.
Definition rational.hpp:1165
std::conditional_t< std::floating_point< NumberType >||RationalConcept< NumberType >, NumberType, ERational > type
Definition rational.hpp:1167
std::remove_cvref_t< Number > NumberType
Definition rational.hpp:1166
Int type
Definition rational.hpp:57
The integer type a Rational stores its parts in; any other type is its own answer.
Definition rational.hpp:51
T type
Definition rational.hpp:52