Pangolin
Header-only C++20 plane computational geometry library
Loading...
Searching...
No Matches
bigint.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "core/numeric.hpp"
4
18
19#include <cassert>
20#include <cmath>
21#include <compare>
22#include <concepts>
23#include <cstddef>
24#include <cstdint>
25#include <functional>
26#include <iostream>
27#include <limits>
28#include <stdexcept>
29#include <string>
30#include <utility>
31#include <vector>
32
33#include "numeric.hpp"
34
35// Marks the rarely-taken, out-of-line limb-path helpers cold so the int128 fast
36// paths stay small enough to inline into hot callers. GCC and clang honour the
37// hint; other compilers (e.g. MSVC on the Boost int128 fallback) ignore it.
38#if defined(__GNUC__) || defined(__clang__)
39#define PGL_BIGINT_COLD [[gnu::noinline, gnu::cold]]
40#else
41#define PGL_BIGINT_COLD
42#endif
43
44namespace pgl {
45
46namespace detail {
47
63class LimbStore {
64 pgl::int128* data_ = nullptr;
65
66 std::size_t count() const { return static_cast<std::size_t>(data_[-1]); }
67
68 void allocate(std::size_t n) {
69 pgl::int128* base = new pgl::int128[n + 1];
70 base[0] = static_cast<pgl::int128>(n);
71 data_ = base + 1;
72 }
73
74 void copyFrom(const pgl::int128* src, std::size_t n) {
75 allocate(n);
76 for (std::size_t i = 0; i < n; ++i) {
77 data_[i] = src[i];
78 }
79 }
80
81 void release() {
82 if (data_) {
83 delete[] (data_ - 1);
84 data_ = nullptr;
85 }
86 }
87
88public:
89 LimbStore() = default;
90 LimbStore(const LimbStore& o) {
91 if (o.data_) {
92 copyFrom(o.data_, o.count());
93 }
94 }
95 LimbStore(LimbStore&& o) noexcept : data_(o.data_) { o.data_ = nullptr; }
96 LimbStore& operator=(const LimbStore& o) {
97 if (this != &o) {
98 release();
99 if (o.data_) {
100 copyFrom(o.data_, o.count());
101 }
102 }
103 return *this;
104 }
105 LimbStore& operator=(LimbStore&& o) noexcept {
106 if (this != &o) {
107 release();
108 data_ = o.data_;
109 o.data_ = nullptr;
110 }
111 return *this;
112 }
113 ~LimbStore() { release(); }
114
116 LimbStore& operator=(const std::vector<pgl::int128>& v) {
117 release();
118 if (!v.empty()) {
119 copyFrom(v.data(), v.size());
120 }
121 return *this;
122 }
123
124 bool empty() const { return data_ == nullptr; }
125 std::size_t size() const { return data_ ? count() : 0; }
126 pgl::int128& operator[](std::size_t i) { return data_[i]; }
127 const pgl::int128& operator[](std::size_t i) const { return data_[i]; }
128 const pgl::int128* begin() const { return data_; }
129 const pgl::int128* end() const { return data_ ? data_ + count() : nullptr; }
130 void clear() { release(); }
131
133 void assign(std::size_t n, pgl::int128 val) {
134 release();
135 if (n) {
136 allocate(n);
137 for (std::size_t i = 0; i < n; ++i) {
138 data_[i] = val;
139 }
140 }
141 }
142};
143
144} // namespace detail
145
157class BigInt {
158private:
159 // alignas(16): on the x86_64-windows-msvc target clang gives __int128 a
160 // *preferred* alignment of 16 (used by alignof and by aligned-move codegen,
161 // e.g. movaps) but an *ABI* alignment of only 8 (used to lay out aggregates
162 // and stack slots). The mismatch means a Point/Rational holding a BigInt can
163 // land on an 8-byte-aligned address while the generated code stores small_
164 // with movaps, faulting. Pinning the storage to 16 forces the ABI alignment
165 // to match the codegen on every toolchain (a no-op where __int128 is already
166 // 16-aligned, e.g. the System V ABI, and on the Boost fallback).
167 alignas(16) pgl::int128 small_ = 0;
168 detail::LimbStore limbs_;
169 bool negative_ = false;
170
171 using Limbs = std::vector<pgl::int128>;
172
173 friend struct std::hash<BigInt>;
174
176 static constexpr int kLimbBits = 62;
177
179 static pgl::int128 base() { return pgl::int128(1) << kLimbBits; }
180
182 static pgl::int128 limbMask() { return base() - 1; }
183
185 static pgl::int128 int128Max() { return pgl::detail::numeric_limits<pgl::int128>::max(); }
186
187 // --- magnitude helpers (operate on normalized little-endian limb vectors) ---
188
190 static void trim(Limbs& v) {
191 while (!v.empty() && v.back() == 0) {
192 v.pop_back();
193 }
194 }
195
197 static int cmpMag(const Limbs& a, const Limbs& b) {
198 if (a.size() != b.size()) {
199 return a.size() < b.size() ? -1 : 1;
200 }
201 for (std::size_t i = a.size(); i-- > 0;) {
202 if (a[i] != b[i]) {
203 return a[i] < b[i] ? -1 : 1;
204 }
205 }
206 return 0;
207 }
208
210 static Limbs addMag(const Limbs& a, const Limbs& b) {
211 Limbs r;
212 const std::size_t n = a.size() < b.size() ? b.size() : a.size();
213 r.reserve(n + 1);
214 pgl::int128 carry = 0;
215 for (std::size_t i = 0; i < n; ++i) {
216 pgl::int128 sum = carry;
217 if (i < a.size()) sum += a[i];
218 if (i < b.size()) sum += b[i];
219 r.push_back(sum & limbMask());
220 carry = sum >> kLimbBits;
221 }
222 if (carry != 0) {
223 r.push_back(carry);
224 }
225 return r;
226 }
227
229 static void subMagInPlace(Limbs& a, const Limbs& b) {
230 pgl::int128 borrow = 0;
231 for (std::size_t i = 0; i < a.size(); ++i) {
232 pgl::int128 d = a[i] - borrow - (i < b.size() ? b[i] : pgl::int128(0));
233 if (d < 0) {
234 d += base();
235 borrow = 1;
236 } else {
237 borrow = 0;
238 }
239 a[i] = d;
240 }
241 trim(a);
242 }
243
245 static Limbs subMag(const Limbs& a, const Limbs& b) {
246 Limbs r = a;
247 subMagInPlace(r, b);
248 return r;
249 }
250
252 static void shiftLeftOneMag(Limbs& v) {
253 pgl::int128 carry = 0;
254 for (pgl::int128& limb : v) {
255 const pgl::int128 next = limb >> (kLimbBits - 1);
256 limb = ((limb << 1) & limbMask()) | carry;
257 carry = next;
258 }
259 if (carry != 0) {
260 v.push_back(carry);
261 }
262 }
263
265 static Limbs mulMag(const Limbs& a, const Limbs& b) {
266 if (a.empty() || b.empty()) {
267 return {};
268 }
269 Limbs r(a.size() + b.size(), pgl::int128(0));
270 for (std::size_t i = 0; i < a.size(); ++i) {
271 pgl::int128 carry = 0;
272 for (std::size_t j = 0; j < b.size(); ++j) {
273 pgl::int128 cur = r[i + j] + a[i] * b[j] + carry;
274 r[i + j] = cur & limbMask();
275 carry = cur >> kLimbBits;
276 }
277 r[i + b.size()] += carry;
278 }
279 trim(r);
280 return r;
281 }
282
284 static int topBit(pgl::int128 x) {
285 int n = 0;
286 while (x > 1) {
287 x >>= 1;
288 ++n;
289 }
290 return n;
291 }
292
294 static std::size_t bitLengthMag(const Limbs& v) {
295 if (v.empty()) {
296 return 0;
297 }
298 return (v.size() - 1) * kLimbBits + static_cast<std::size_t>(topBit(v.back())) + 1;
299 }
300
302 static bool testBitMag(const Limbs& v, std::size_t i) {
303 const std::size_t limb = i / kLimbBits;
304 const std::size_t off = i % kLimbBits;
305 if (limb >= v.size()) {
306 return false;
307 }
308 return ((v[limb] >> static_cast<int>(off)) & 1) != 0;
309 }
310
319 static std::pair<Limbs, Limbs> divmodMag(const Limbs& n, const Limbs& d) {
320 if (cmpMag(n, d) < 0) {
321 return {Limbs(), n};
322 }
323 Limbs q(n.size(), pgl::int128(0));
324 if (d.size() == 1) {
325 const pgl::int128 divisor = d[0];
326 pgl::int128 remainder = 0;
327 for (std::size_t i = n.size(); i-- > 0;) {
328 // remainder < divisor < 2^62, so this is below 2^124.
329 const pgl::int128 current = (remainder << kLimbBits) | n[i];
330 q[i] = current / divisor;
331 remainder = current % divisor;
332 }
333 trim(q);
334 Limbs r;
335 if (remainder != 0) {
336 r.push_back(remainder);
337 }
338 return {std::move(q), std::move(r)};
339 }
340 Limbs r;
341 r.reserve(d.size() + 1);
342 for (std::size_t bit = bitLengthMag(n); bit-- > 0;) {
343 shiftLeftOneMag(r);
344 if (testBitMag(n, bit)) {
345 if (r.empty()) {
346 r.push_back(pgl::int128(0));
347 }
348 r[0] |= pgl::int128(1);
349 }
350 if (cmpMag(r, d) >= 0) {
351 subMagInPlace(r, d);
352 q[bit / kLimbBits] |= pgl::int128(1) << static_cast<int>(bit % kLimbBits);
353 }
354 }
355 trim(q);
356 trim(r);
357 return {std::move(q), std::move(r)};
358 }
359
360 // --- conversions between the small and limb representations ---
361
363 Limbs magToLimbs() const {
364 if (!limbs_.empty()) {
365 return Limbs(limbs_.begin(), limbs_.end());
366 }
367 Limbs v;
368 pgl::int128 x = small_;
369 while (x > 0) {
370 v.push_back(x & limbMask());
371 x >>= kLimbBits;
372 }
373 return v;
374 }
375
377 static bool limbsFitInt128(const Limbs& v, pgl::int128& out) {
378 pgl::int128 acc = 0;
379 const pgl::int128 maxv = int128Max();
380 for (std::size_t i = v.size(); i-- > 0;) {
381 if (acc > maxv / base()) {
382 return false;
383 }
384 acc *= base();
385 if (acc > maxv - v[i]) {
386 return false;
387 }
388 acc += v[i];
389 }
390 out = acc;
391 return true;
392 }
393
396 void setFromLimbs(Limbs v, bool neg) {
397 trim(v);
398 if (v.empty()) {
399 small_ = 0;
400 limbs_.clear();
401 negative_ = false;
402 return;
403 }
404 pgl::int128 fitted;
405 if (limbsFitInt128(v, fitted)) {
406 small_ = fitted;
407 limbs_.clear();
408 } else {
409 small_ = 0;
410 limbs_ = std::move(v);
411 }
412 negative_ = neg;
413 }
414
416 static BigInt fromSmall(pgl::int128 magnitude, bool neg) {
417 BigInt b;
418 b.small_ = magnitude;
419 b.negative_ = (magnitude != 0) && neg;
420 return b;
421 }
422
424 int compareMag(const BigInt& o) const {
425 if (limbs_.empty() && o.limbs_.empty()) {
426 if (small_ == o.small_) return 0;
427 return small_ < o.small_ ? -1 : 1;
428 }
429 return compareMagGeneral(o);
430 }
431
436 int compareMagGeneral(const BigInt& o) const {
437 // A limb store only ever holds a magnitude past what an int128 can, so
438 // having one at all settles the order against an inline value, and two
439 // stores compare by width and then from the top limb down: nothing
440 // needs copying out.
441 if (limbs_.empty()) {
442 return -1;
443 }
444 if (o.limbs_.empty()) {
445 return 1;
446 }
447 const std::size_t n = limbs_.size(), m = o.limbs_.size();
448 if (n != m) {
449 return n < m ? -1 : 1;
450 }
451 for (std::size_t i = n; i-- > 0;) {
452 if (limbs_[i] != o.limbs_[i]) {
453 return limbs_[i] < o.limbs_[i] ? -1 : 1;
454 }
455 }
456 return 0;
457 }
458
460 std::string magToDecimalString() const {
461 Limbs v = magToLimbs();
462 if (v.empty()) {
463 return "0";
464 }
465 const Limbs divisor = {pgl::int128(int64_t(1000000000000000000))}; // 10^18
466 std::vector<int64_t> chunks;
467 while (!v.empty()) {
468 auto [quotient, remainder] = divmodMag(v, divisor);
469 chunks.push_back(remainder.empty() ? int64_t(0) : static_cast<int64_t>(remainder[0]));
470 v = std::move(quotient);
471 }
472 std::string s = std::to_string(chunks.back());
473 for (std::size_t i = chunks.size() - 1; i-- > 0;) {
474 std::string part = std::to_string(chunks[i]);
475 s += std::string(18 - part.size(), '0');
476 s += part;
477 }
478 return s;
479 }
480
486 static BigInt addGeneral(const BigInt& a, const BigInt& b) {
487 Limbs av = a.magToLimbs();
488 Limbs bv = b.magToLimbs();
489 BigInt r;
490 if (a.negative_ == b.negative_) {
491 r.setFromLimbs(addMag(av, bv), a.negative_);
492 } else {
493 const int c = cmpMag(av, bv);
494 if (c > 0) {
495 r.setFromLimbs(subMag(av, bv), a.negative_);
496 } else if (c < 0) {
497 r.setFromLimbs(subMag(bv, av), b.negative_);
498 }
499 // c == 0 leaves r as the default zero.
500 }
501 return r;
502 }
503
507 static pgl::int128 quotSmall(pgl::int128 a, pgl::int128 b) {
508 const std::uint64_t u64max = pgl::detail::numeric_limits<std::uint64_t>::max();
509 if (a <= u64max && b <= u64max) {
510 return pgl::int128(static_cast<std::uint64_t>(a) / static_cast<std::uint64_t>(b));
511 }
512 return a / b;
513 }
514
516 static pgl::int128 remSmall(pgl::int128 a, pgl::int128 b) {
517 const std::uint64_t u64max = pgl::detail::numeric_limits<std::uint64_t>::max();
518 if (a <= u64max && b <= u64max) {
519 return pgl::int128(static_cast<std::uint64_t>(a) % static_cast<std::uint64_t>(b));
520 }
521 return a % b;
522 }
523
529 static void divmod(const BigInt& a, const BigInt& b, BigInt& q, BigInt& r) {
530 if (b.isZero()) {
531 throw std::domain_error("pgl::BigInt: division by zero");
532 }
533 const bool quotientNegative = a.negative_ != b.negative_;
534 const bool remainderNegative = a.negative_; // remainder follows the dividend
535 if (a.limbs_.empty() && b.limbs_.empty()) {
536 q = fromSmall(quotSmall(a.small_, b.small_), quotientNegative);
537 r = fromSmall(remSmall(a.small_, b.small_), remainderNegative);
538 return;
539 }
540 auto [qm, rm] = divmodMag(a.magToLimbs(), b.magToLimbs());
541 q.setFromLimbs(std::move(qm), quotientNegative);
542 r.setFromLimbs(std::move(rm), remainderNegative);
543 }
544
552 pgl::int128 lowBitsInt128() const {
553#if defined(__SIZEOF_INT128__)
554 __uint128_t m = 0;
555 for (std::size_t i = 0; i < limbs_.size() && i < 3; ++i) {
556 m += static_cast<__uint128_t>(limbs_[i]) << (kLimbBits * i);
557 }
558 return static_cast<pgl::int128>(negative_ ? -m : m);
559#else
560 pgl::int128 m = 0;
561 pgl::int128 weight = 1;
562 for (std::size_t i = 0; i < limbs_.size() && i < 3; ++i) {
563 m += limbs_[i] * weight;
564 weight *= base();
565 }
566 return negative_ ? -m : m;
567#endif
568 }
569
570public:
572 BigInt() = default;
573
575 BigInt(pgl::detail::extended_integral auto value) {
576 pgl::int128 x = static_cast<pgl::int128>(value);
577 if (x == 0) {
578 return;
579 }
580 if (x == pgl::detail::numeric_limits<pgl::int128>::min()) {
581 // |min| == 2^127 does not fit in a signed int128, so spell it out.
582 negative_ = true;
583 limbs_.assign(3, pgl::int128(0));
584 limbs_[2] = pgl::int128(1) << 3; // 8 * 2^124 == 2^127
585 } else {
586 negative_ = x < 0;
587 small_ = x < 0 ? -x : x;
588 }
589 }
590
598 template <std::floating_point Float>
599 explicit BigInt(Float value) {
600 assert(std::isfinite(value)
601 && "pgl::BigInt: cannot construct from non-finite floating point");
602 const bool neg = value < 0;
603 const Float mag = std::trunc(neg ? -value : value); // |value|, fraction dropped
604 if (mag == 0) {
605 return; // zero (also covers -0.0 and tiny |value| < 1)
606 }
607 // Decompose the magnitude exactly as sig * 2^shift, where sig is the
608 // integer significand (at most `digits` bits, hence always within an
609 // int128) and `shift` places it at the right binary exponent.
610 int exponent;
611 const Float frac = std::frexp(mag, &exponent); // mag == frac * 2^exponent
612 const int digits = pgl::detail::numeric_limits<Float>::digits;
613 const pgl::int128 sig = static_cast<pgl::int128>(std::ldexp(frac, digits));
614 const int shift = exponent - digits; // mag == sig * 2^shift
615 if (shift <= 0) {
616 // mag < 2^digits <= 2^64, so it fits in a single int128 limb.
617 small_ = sig >> (-shift); // exact: the dropped low bits are zero
618 negative_ = neg;
619 return;
620 }
621 // shift > 0: lay sig down at bit offset `shift` in the base-2^62 store.
622 // sig < 2^digits and the in-limb offset is < 62, so the shifted chunk
623 // still fits in an int128 before it is split across limbs.
624 Limbs v(static_cast<std::size_t>(shift / kLimbBits), pgl::int128(0));
625 pgl::int128 x = sig << (shift % kLimbBits);
626 while (x > 0) {
627 v.push_back(x & limbMask());
628 x >>= kLimbBits;
629 }
630 setFromLimbs(std::move(v), neg);
631 }
632
633 // --- inspectors ---
634
636 bool isZero() const { return limbs_.empty() && small_ == 0; }
637
643 bool isOne() const { return limbs_.empty() && !negative_ && small_ == 1; }
644
646 bool isNegative() const { return negative_; }
647
649 bool fitsInt128() const { return limbs_.empty(); }
650
656 bool fitsInt64() const {
657 return limbs_.empty() && small_ < (pgl::int128(1) << 63);
658 }
659
665 bool fitsLimbs(std::size_t limbs) const { return limbs_.size() <= limbs; }
666
668 int sign() const {
669 if (isZero()) return 0;
670 return negative_ ? -1 : 1;
671 }
672
674 BigInt abs() const {
675 BigInt r = *this;
676 r.negative_ = false;
677 return r;
678 }
679
680 // --- conversions ---
681
683 explicit operator pgl::int128() const {
684 if (limbs_.empty()) {
685 return negative_ ? -small_ : small_;
686 }
687 return lowBitsInt128();
688 }
689
698 template <std::signed_integral T>
699 explicit operator T() const {
700 return static_cast<T>(static_cast<pgl::int128>(*this));
701 }
702
703 explicit operator bool() const { return !isZero(); }
704
706 explicit operator long double() const {
707 long double d = 0;
708 if (limbs_.empty()) {
709 d = static_cast<long double>(small_);
710 } else {
711 const long double b = static_cast<long double>(base());
712 for (std::size_t i = limbs_.size(); i-- > 0;) {
713 d = d * b + static_cast<long double>(limbs_[i]);
714 }
715 }
716 return negative_ ? -d : d;
717 }
718 explicit operator double() const { return static_cast<double>(static_cast<long double>(*this)); }
719 explicit operator float() const { return static_cast<float>(static_cast<long double>(*this)); }
720
721 // --- arithmetic ---
722
724 BigInt r = *this;
725 if (!r.isZero()) {
726 r.negative_ = !r.negative_;
727 }
728 return r;
729 }
730
731 BigInt operator+() const { return *this; }
732
733 friend BigInt operator+(const BigInt& a, const BigInt& b) {
734 if (a.limbs_.empty() && b.limbs_.empty()) {
735 const pgl::int128 ma = a.small_;
736 const pgl::int128 mb = b.small_;
737 if (a.negative_ == b.negative_) {
738 if (ma <= int128Max() - mb) {
739 return fromSmall(ma + mb, a.negative_);
740 }
741 // magnitude overflow: fall through to the limb path
742 } else if (ma >= mb) {
743 return fromSmall(ma - mb, a.negative_);
744 } else {
745 return fromSmall(mb - ma, b.negative_);
746 }
747 }
748 return addGeneral(a, b);
749 }
750
751 friend BigInt operator-(const BigInt& a, const BigInt& b) {
752 if (a.limbs_.empty() && b.limbs_.empty()) {
753 const pgl::int128 ma = a.small_;
754 const pgl::int128 mb = b.small_;
755 // a - b == a + (-b); replicate operator+'s small path with b's sign
756 // flipped, so we avoid materialising the negated temporary -b.
757 if (a.negative_ != b.negative_) {
758 if (ma <= int128Max() - mb) {
759 return fromSmall(ma + mb, a.negative_);
760 }
761 } else if (ma >= mb) {
762 return fromSmall(ma - mb, a.negative_);
763 } else {
764 return fromSmall(mb - ma, !a.negative_);
765 }
766 }
767 return subGeneral(a, b);
768 }
769
772 static BigInt subGeneral(const BigInt& a, const BigInt& b) { return a + (-b); }
773
774 friend BigInt operator*(const BigInt& a, const BigInt& b) {
775 const bool neg = a.negative_ != b.negative_;
776 if (a.limbs_.empty() && b.limbs_.empty()) {
777 const pgl::int128 ma = a.small_;
778 const pgl::int128 mb = b.small_;
779#if defined(__SIZEOF_INT128__)
780 // Native int128: both magnitudes are non-negative, so a signed-overflow
781 // check on the product is exactly the "does it still fit" test we want,
782 // and __builtin_mul_overflow performs it without a 128-bit division.
783 pgl::int128 prod;
784 if (!__builtin_mul_overflow(ma, mb, &prod)) {
785 return fromSmall(prod, neg);
786 }
787#else
788 // Boost fallback (no native int128, no overflow builtin): guard the
789 // product with a division, matching operator+'s overflow style.
790 if (ma == 0 || mb <= int128Max() / ma) {
791 return fromSmall(ma * mb, neg);
792 }
793#endif
794 // magnitude overflow: fall through to the limb path
795 }
796 return mulGeneral(a, b, neg);
797 }
798
801 static BigInt mulGeneral(const BigInt& a, const BigInt& b, bool neg) {
802 BigInt out;
803 out.setFromLimbs(mulMag(a.magToLimbs(), b.magToLimbs()), neg);
804 return out;
805 }
806
807 friend BigInt operator/(const BigInt& a, const BigInt& b) {
808 if (a.limbs_.empty() && b.limbs_.empty()) {
809 if (b.small_ == 0) {
810 throw std::domain_error("pgl::BigInt: division by zero");
811 }
812 // The quotient is all the caller wants, so skip computing a remainder.
813 return fromSmall(quotSmall(a.small_, b.small_), a.negative_ != b.negative_);
814 }
815 BigInt q;
816 BigInt r;
817 divmod(a, b, q, r);
818 return q;
819 }
820
821 friend BigInt operator%(const BigInt& a, const BigInt& b) {
822 if (a.limbs_.empty() && b.limbs_.empty()) {
823 if (b.small_ == 0) {
824 throw std::domain_error("pgl::BigInt: division by zero");
825 }
826 // The remainder is all the caller wants, so skip computing a quotient.
827 return fromSmall(remSmall(a.small_, b.small_), a.negative_);
828 }
829 BigInt q;
830 BigInt r;
831 divmod(a, b, q, r);
832 return r;
833 }
834
835 BigInt& operator+=(const BigInt& o) { return *this = *this + o; }
836 BigInt& operator-=(const BigInt& o) { return *this = *this - o; }
837 BigInt& operator*=(const BigInt& o) { return *this = *this * o; }
838 BigInt& operator/=(const BigInt& o) { return *this = *this / o; }
839 BigInt& operator%=(const BigInt& o) { return *this = *this % o; }
840
841 BigInt& operator++() { return *this += BigInt(1); }
842 BigInt operator++(int) { BigInt tmp = *this; ++(*this); return tmp; }
843 BigInt& operator--() { return *this -= BigInt(1); }
844 BigInt operator--(int) { BigInt tmp = *this; --(*this); return tmp; }
845
846 // --- comparison ---
847
848 std::strong_ordering operator<=>(const BigInt& o) const {
849 if (negative_ != o.negative_) {
850 return negative_ ? std::strong_ordering::less : std::strong_ordering::greater;
851 }
852 int c = compareMag(o);
853 if (negative_) {
854 c = -c; // among negatives, the larger magnitude is the smaller value
855 }
856 if (c < 0) return std::strong_ordering::less;
857 if (c > 0) return std::strong_ordering::greater;
858 return std::strong_ordering::equal;
859 }
860
861 bool operator==(const BigInt& o) const {
862 return negative_ == o.negative_ && compareMag(o) == 0;
863 }
864
865 // --- comparison against floating point ---
866 //
867 // These are exact for every finite magnitude: rather than casting *this to a
868 // (lossy) double, the float is split at its truncation point with the same
869 // round-toward-zero rule the floating-point constructor uses, and the dropped
870 // fractional part breaks any tie on the integer part. Being members, they
871 // also cover the reversed forms (`f == b`, `f < b`, ...) via C++20 rewriting.
872
875 template <std::floating_point Float>
876 bool operator==(Float f) const {
877 if (!std::isfinite(f) || f != std::trunc(f)) {
878 return false; // NaN/inf or a fractional value can never equal an integer
879 }
880 return *this == BigInt(f);
881 }
882
884 template <std::floating_point Float>
885 std::partial_ordering operator<=>(Float f) const {
886 if (std::isnan(f)) {
887 return std::partial_ordering::unordered;
888 }
889 if (std::isinf(f)) {
890 return f > 0 ? std::partial_ordering::less : std::partial_ordering::greater;
891 }
892 // Compare against the truncated integer part first; if they differ, that
893 // settles it. BigInt(f) discards f's fraction exactly as std::trunc does.
894 const Float t = std::trunc(f);
895 if (std::strong_ordering c = *this <=> BigInt(f); c != 0) {
896 return c;
897 }
898 // Integer parts are equal, so the leftover fraction decides: a positive
899 // fraction makes f larger than this integer, a negative one smaller.
900 if (f == t) {
901 return std::partial_ordering::equivalent;
902 }
903 return f > t ? std::partial_ordering::less : std::partial_ordering::greater;
904 }
905
906 // --- stream I/O ---
907
908 friend std::ostream& operator<<(std::ostream& os, const BigInt& b) {
909 if (b.negative_) {
910 os << '-';
911 }
912 if (b.limbs_.empty()) {
913 os << b.small_; // uses the pgl::int128 stream operator
914 } else {
915 os << b.magToDecimalString();
916 }
917 return os;
918 }
919
920 friend std::istream& operator>>(std::istream& is, BigInt& b) {
921 std::istream::sentry sentry(is);
922 if (!sentry) {
923 return is;
924 }
925 bool neg = false;
926 int c = is.peek();
927 if (c == '+' || c == '-') {
928 neg = c == '-';
929 is.get();
930 c = is.peek();
931 }
932 if (c < '0' || c > '9') {
933 is.setstate(std::ios::failbit);
934 return is;
935 }
936 BigInt result;
937 const BigInt ten(10);
938 for (; c >= '0' && c <= '9'; c = is.peek()) {
939 is.get();
940 result = result * ten + BigInt(c - '0');
941 }
942 b = neg ? -result : result;
943 return is;
944 }
945};
946
948inline BigInt abs(const BigInt& v) { return v.abs(); }
949
950} // namespace pgl
951
952#undef PGL_BIGINT_COLD
#define PGL_BIGINT_COLD
Definition bigint.hpp:41
Arbitrary precision signed integer.
Definition bigint.hpp:157
bool fitsLimbs(std::size_t limbs) const
Whether the magnitude occupies at most limbs heap limbs.
Definition bigint.hpp:665
BigInt & operator*=(const BigInt &o)
Definition bigint.hpp:837
bool isNegative() const
Whether the value is strictly negative.
Definition bigint.hpp:646
static BigInt subGeneral(const BigInt &a, const BigInt &b)
Cold out-of-line subtraction fallback (a magnitude spilled to limbs).
Definition bigint.hpp:772
friend BigInt operator%(const BigInt &a, const BigInt &b)
Definition bigint.hpp:821
BigInt & operator--()
Definition bigint.hpp:843
friend BigInt operator/(const BigInt &a, const BigInt &b)
Definition bigint.hpp:807
friend std::ostream & operator<<(std::ostream &os, const BigInt &b)
Definition bigint.hpp:908
int sign() const
Sign of the value: -1, 0, or 1.
Definition bigint.hpp:668
BigInt & operator%=(const BigInt &o)
Definition bigint.hpp:839
BigInt operator--(int)
Definition bigint.hpp:844
bool operator==(const BigInt &o) const
Definition bigint.hpp:861
BigInt operator++(int)
Definition bigint.hpp:842
BigInt operator+() const
Definition bigint.hpp:731
friend std::istream & operator>>(std::istream &is, BigInt &b)
Definition bigint.hpp:920
BigInt operator-() const
Definition bigint.hpp:723
bool isOne() const
Whether the value is one.
Definition bigint.hpp:643
std::partial_ordering operator<=>(Float f) const
Ordering against a floating-point value (partial: NaN is unordered).
Definition bigint.hpp:885
BigInt()=default
Default constructor (zero).
friend BigInt operator-(const BigInt &a, const BigInt &b)
Definition bigint.hpp:751
friend BigInt operator+(const BigInt &a, const BigInt &b)
Definition bigint.hpp:733
BigInt(Float value)
Construct from floating point, truncating toward zero.
Definition bigint.hpp:599
BigInt & operator++()
Definition bigint.hpp:841
BigInt & operator-=(const BigInt &o)
Definition bigint.hpp:836
BigInt & operator/=(const BigInt &o)
Definition bigint.hpp:838
BigInt abs() const
Absolute value.
Definition bigint.hpp:674
BigInt(pgl::detail::extended_integral auto value)
Construct from any integer (including pgl::int128).
Definition bigint.hpp:575
std::strong_ordering operator<=>(const BigInt &o) const
Definition bigint.hpp:848
static BigInt mulGeneral(const BigInt &a, const BigInt &b, bool neg)
Cold out-of-line schoolbook multiplication (a magnitude exceeds int128).
Definition bigint.hpp:801
bool fitsInt128() const
Whether the magnitude fits in a single pgl::int128.
Definition bigint.hpp:649
bool fitsInt64() const
Whether the magnitude fits in 63 bits (i.e. in an int64_t).
Definition bigint.hpp:656
bool isZero() const
Whether the value is zero.
Definition bigint.hpp:636
friend BigInt operator*(const BigInt &a, const BigInt &b)
Definition bigint.hpp:774
bool operator==(Float f) const
Equality with a floating-point value (true only when f is a whole number equal to this integer).
Definition bigint.hpp:876
BigInt & operator+=(const BigInt &o)
Definition bigint.hpp:835
Definition arrangement.hpp:67
@ x
Definition intervaltree.hpp:24
BigInt abs(const BigInt &v)
Free-function absolute value, matching the integer helpers.
Definition bigint.hpp:948
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
Numeric concepts and helpers shared by exact geometry operations.