Pangolin
Header-only C++20 plane computational geometry library
Loading...
Searching...
No Matches
numeric.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "core/handle.hpp"
4
12
13#include <array>
14#include <cmath>
15#include <compare>
16#include <concepts>
17#include <cstdint>
18#include <istream>
19#include <limits>
20#include <numeric>
21#include <ostream>
22#include <type_traits>
23
24#if !defined(__SIZEOF_INT128__)
25// Toolchains without the 128-bit extension (e.g. MSVC) fall back to Boost.
26// Boost is only pulled in here, never when __int128_t is available.
27#include <boost/multiprecision/cpp_int.hpp>
28#endif
29
30namespace pgl {
31
37template <class T>
38class Rational;
39
44class BigInt;
45
54#if defined(__SIZEOF_INT128__)
55using int128 = __int128_t;
56#else
57// Boost's own `int128_t` is sign-magnitude with a *128-bit magnitude*, so its
58// max() is 2^128 - 1 — wider than native __int128 (two's complement, max
59// 2^127 - 1). Using it would silently give pgl::int128 a different range on the
60// fallback than on the native path, breaking the shared overflow guards and
61// invariants. A 127-bit magnitude reproduces native's max() exactly; the only
62// difference is min() is -(2^127 - 1) rather than -2^127, an extreme value the
63// exact-arithmetic intermediates never depend on.
64using int128 = boost::multiprecision::number<boost::multiprecision::cpp_int_backend<
65 127, 127, boost::multiprecision::signed_magnitude, boost::multiprecision::unchecked, void>>;
66#endif
67
68#if defined(__SIZEOF_INT128__)
76inline std::ostream& operator<<(std::ostream& stream, const int128& value) {
77 // The magnitude is taken unsigned: the most negative value has no negation
78 // in the signed type, and forming one there is undefined behavior that
79 // optimizers exploit -- a `value == -value` guard for it folds away.
80 __uint128_t magnitude = value < 0 ? -static_cast<__uint128_t>(value)
81 : static_cast<__uint128_t>(value);
82 char digits[41]; // 39 decimal digits, a sign and a terminator
83 char* first = digits + sizeof(digits) - 1;
84 *first = '\0';
85 do {
86 *--first = static_cast<char>('0' + static_cast<int>(magnitude % 10));
87 magnitude /= 10;
88 } while (magnitude != 0);
89 if (value < 0) {
90 *--first = '-';
91 }
92 return stream << first;
93}
94
106inline std::istream& operator>>(std::istream& stream, int128& value) {
107 std::istream::sentry sentry(stream);
108 if (!sentry) {
109 return stream;
110 }
111 bool neg = false;
112 int c = stream.peek();
113 if (c == '+' || c == '-') {
114 neg = c == '-';
115 stream.get();
116 c = stream.peek();
117 }
118 if (c < '0' || c > '9') {
119 stream.setstate(std::ios::failbit);
120 return stream;
121 }
122 // Accumulated unsigned, where the most negative value's magnitude exists
123 // and wrapping is defined; a magnitude past the type wraps, as before.
124 __uint128_t result = 0;
125 for (; c >= '0' && c <= '9'; c = stream.peek()) {
126 stream.get();
127 result = result * 10 + static_cast<unsigned>(c - '0');
128 }
129 value = static_cast<int128>(neg ? -result : result);
130 return stream;
131}
132#endif
133
134} // namespace pgl
135
136#if !defined(__SIZEOF_INT128__)
137// The Boost.Multiprecision fallback for pgl::int128 (used when the native
138// __int128 extension is unavailable, e.g. MSVC) deliberately omits mixed
139// operations with floating-point types, whereas native __int128 supports them
140// through the usual arithmetic conversions. These free operators restore that
141// double interop so pgl::int128 stays a drop-in replacement: each one converts
142// the 128-bit value to double and operates in double, exactly as the built-in
143// path would.
144//
145// They are defined in namespace boost::multiprecision (the namespace of
146// pgl::int128's underlying type) so that argument-dependent lookup finds them
147// everywhere -- crucially inside the `requires(Int x, Float g){ x * g; }` probe
148// in Rational's float constructor, where MSVC only consults ADL, not ordinary
149// lookup. A single operator<=> covers all four relational operators in both
150// argument orders, and a single operator== covers ==/!=, avoiding the C++20
151// reversed-candidate ambiguity that defining both directions would create.
153inline std::partial_ordering operator<=>(const pgl::int128& a, double b) {
154 return static_cast<double>(a) <=> b;
155}
156inline bool operator==(const pgl::int128& a, double b) {
157 return static_cast<double>(a) == b;
158}
159inline double operator+(const pgl::int128& a, double b) { return static_cast<double>(a) + b; }
160inline double operator+(double a, const pgl::int128& b) { return a + static_cast<double>(b); }
161inline double operator-(const pgl::int128& a, double b) { return static_cast<double>(a) - b; }
162inline double operator-(double a, const pgl::int128& b) { return a - static_cast<double>(b); }
163inline double operator*(const pgl::int128& a, double b) { return static_cast<double>(a) * b; }
164inline double operator*(double a, const pgl::int128& b) { return a * static_cast<double>(b); }
165inline double operator/(const pgl::int128& a, double b) { return static_cast<double>(a) / b; }
166inline double operator/(double a, const pgl::int128& b) { return a / static_cast<double>(b); }
167} // namespace boost::multiprecision
168#endif
169
170namespace pgl::detail {
171
184template <class T>
185struct numeric_limits : std::numeric_limits<T> {};
186
187#if defined(__SIZEOF_INT128__)
188template <>
189struct numeric_limits<__int128_t> {
190 static constexpr bool is_specialized = true;
191 static constexpr bool is_signed = true;
192 static constexpr bool is_integer = true;
193 static constexpr bool is_exact = true;
194 static constexpr bool is_bounded = true;
195 static constexpr int digits = 127; // value bits of a signed 128-bit int
196 static constexpr int digits10 = 38;
197 static constexpr __int128_t min() noexcept {
198 return static_cast<__int128_t>(static_cast<__uint128_t>(1) << 127);
199 }
200 static constexpr __int128_t lowest() noexcept { return min(); }
201 static constexpr __int128_t max() noexcept {
202 return static_cast<__int128_t>(~(static_cast<__uint128_t>(1) << 127));
203 }
204};
205#endif
206
207template <typename T>
208concept extended_integral =
209 std::integral<T>
210 || std::same_as<std::remove_cv_t<T>, pgl::int128>;
211
232template <class A, class B>
233constexpr std::strong_ordering strongOrder(const A& a, const B& b) {
234 if constexpr (requires { std::strong_order(a, b); }) {
235 if constexpr (std::is_floating_point_v<A> || std::is_floating_point_v<B>) {
236 if (a == b) {
237 return std::strong_ordering::equal;
238 }
239 }
240 return std::strong_order(a, b);
241 } else {
242 if (a < b) return std::strong_ordering::less;
243 if (b < a) return std::strong_ordering::greater;
244 return std::strong_ordering::equal;
245 }
246}
247
257template <class A, class B>
258constexpr auto threeWay(const A& a, const B& b) {
259 if constexpr (requires { a <=> b; }) {
260 return a <=> b;
261 } else {
262 return a < b ? std::strong_ordering::less
263 : b < a ? std::strong_ordering::greater
264 : std::strong_ordering::equal;
265 }
266}
267
276template <typename T>
277struct _promote {
278 using type = T;
279};
280
281#ifndef PGL_DISABLE_PROMOTION
282// Rational is deliberately never promoted: it manages its own overflow by
283// reducing to lowest terms, so the storage type stays as the user chose it.
284
285// Signed built-in integers promote by width rather than by fixed-width alias.
286// Which of `int`, `long` and `long long` each of `int32_t` and `int64_t` names
287// is platform-dependent, and the three are distinct types whatever their
288// widths: specializing on the aliases alone leaves whichever type they skip —
289// `long long` under LP64, `long` under LLP64 — matching nothing but the
290// identity primary template. Nothing about that is visible at the use site.
291// The coordinate still compiles everywhere it should, and every overflow guard
292// built on this trait is simply absent, so an intermediate meant to be
293// evaluated 128 bits wide wraps in 64 instead. Worse than a plain wrap, once a
294// wrapped value is widened again the modular arithmetic stops agreeing with the
295// exact arithmetic around it, which is how a wrong sign reaches a predicate.
296template <typename T>
297 requires std::signed_integral<T>
298struct _promote<T> {
299 using type =
300 std::conditional_t<sizeof(T) <= 1, int16_t,
301 std::conditional_t<sizeof(T) <= 2, int32_t,
302 std::conditional_t<sizeof(T) <= 4, int64_t,
303 std::conditional_t<sizeof(T) <= 8, pgl::int128, pgl::BigInt>>>>;
304};
305
306// Kept as an explicit specialization, which outranks the partial one above on
307// the platforms where pgl::int128 is the native extension and the standard
308// library counts it as an integer.
309template <>
310struct _promote<pgl::int128> {
311 using type = pgl::BigInt;
312};
313
314template <>
315struct _promote<float> {
316 using type = double;
317};
318
319template <>
320struct _promote<double> {
321 using type = long double;
322};
323#endif
324
325template <typename T>
326using promoted_number_t = typename _promote<T>::type;
327
361template <class Target, class Number>
362constexpr decltype(auto) asNumber(const Number& value) {
363 if constexpr (std::is_same_v<Target, Number>) {
364 return (value);
365 } else {
366 return static_cast<Target>(value);
367 }
368}
369
378template <class T>
379inline constexpr bool arbitraryPrecision = false;
380
381template <>
382inline constexpr bool arbitraryPrecision<pgl::BigInt> = true;
383
384template <class Int>
385inline constexpr bool arbitraryPrecision<pgl::Rational<Int>> = arbitraryPrecision<Int>;
386
401template <class ResultNumber>
402using floating_result_t =
403 std::conditional_t<std::is_floating_point_v<ResultNumber>, ResultNumber, double>;
404
408constexpr auto gcd(std::integral auto a, std::integral auto b) {
409 return std::gcd(a, b);
410}
411
415inline auto gcd(auto a, auto b) {
416 if (a == b) {
417 return a;
418 }
419
420 while (b != 0) {
421 const auto remainder = a % b;
422 a = b;
423 b = remainder;
424 }
425
426 return a;
427}
428
440template <class Integer>
441inline std::array<Integer, 3> extendedGcd(Integer a, Integer b) {
442 Integer remainder = a, currentRemainder = b;
443 Integer coefficientA(1), nextCoefficientA(0);
444 Integer coefficientB(0), nextCoefficientB(1);
445 while (currentRemainder != Integer(0)) {
446 const Integer quotient = remainder / currentRemainder;
447 Integer next = remainder - quotient * currentRemainder;
448 remainder = currentRemainder;
449 currentRemainder = next;
450 next = coefficientA - quotient * nextCoefficientA;
451 coefficientA = nextCoefficientA;
452 nextCoefficientA = next;
453 next = coefficientB - quotient * nextCoefficientB;
454 coefficientB = nextCoefficientB;
455 nextCoefficientB = next;
456 }
457 if (remainder < Integer(0)) {
458 remainder = -remainder;
459 coefficientA = -coefficientA;
460 coefficientB = -coefficientB;
461 }
462 return {remainder, coefficientA, coefficientB};
463}
464
469template <class Integer>
470inline Integer nearestQuotient(const Integer& numerator, const Integer& divisor) {
471 Integer quotient = numerator / divisor; // truncates toward zero
472 Integer remainder = numerator - quotient * divisor;
473 if (remainder < Integer(0)) { // make it a floor division
474 quotient -= Integer(1);
475 remainder += divisor;
476 }
477 if (Integer(2) * remainder >= divisor) {
478 quotient += Integer(1);
479 }
480 return quotient;
481}
482
490template <class Target, class Integer>
491inline bool representableAs(const Integer& value) {
492 if constexpr (!numeric_limits<Target>::is_bounded) {
493 return true;
494 } else {
495 return value >= Integer(numeric_limits<Target>::lowest())
496 && value <= Integer(numeric_limits<Target>::max());
497 }
498}
499
508template <class Target, class Integer>
509inline Target narrowTo(const Integer& value) {
510 if constexpr (std::same_as<Target, Integer>) {
511 return value;
512 } else {
513 return static_cast<Target>(static_cast<pgl::int128>(value));
514 }
515}
516
520constexpr auto abs(extended_integral auto value) {
521 using T = decltype(value);
522 if constexpr (std::is_unsigned_v<T>) {
523 return value;
524 } else {
525 return value < 0 ? -value : value;
526 }
527}
528
532constexpr auto abs(std::floating_point auto value) {
533 return std::abs(value);
534}
535
543inline constexpr auto abs(auto value) {
544 return value >= 0 ? value : -value;
545}
546
550template <typename Int>
551constexpr pgl::Rational<Int> abs(pgl::Rational<Int> value) {
552 // The parameter is a copy, so simplify it in place and read both parts from
553 // the reduced form; numerator() and denominator() would each reduce it anew.
554 value.simplify();
555 return pgl::Rational(pgl::detail::abs(value.numerator()), value.denominator(), true);
556}
557
568template <class To, class From>
569[[nodiscard]] constexpr To convertCoordinate(From value) {
570 return static_cast<To>(value);
571}
572
573} // namespace pgl::detail
Arbitrary precision signed integer.
Definition bigint.hpp:157
Exact rational number class template.
Definition rational.hpp:106
constexpr Int numerator() const noexcept
Get numerator (in lowest terms).
Definition rational.hpp:359
constexpr void simplify()
Reduces the stored fraction to lowest terms in place (den stays > 0).
Definition rational.hpp:388
constexpr Int denominator() const noexcept
Get denominator (in lowest terms).
Definition rational.hpp:365
Strongly typed index handles shared by the topological data structures.
Definition numeric.hpp:152
std::partial_ordering operator<=>(const pgl::int128 &a, double b)
Definition numeric.hpp:153
double operator/(const pgl::int128 &a, double b)
Definition numeric.hpp:165
double operator-(const pgl::int128 &a, double b)
Definition numeric.hpp:161
double operator+(const pgl::int128 &a, double b)
Definition numeric.hpp:159
bool operator==(const pgl::int128 &a, double b)
Definition numeric.hpp:156
double operator*(const pgl::int128 &a, double b)
Definition numeric.hpp:163
Definition arrangement.hpp:67
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 >
std::ostream & operator<<(std::ostream &stream, const Point< Number, Label > &point)
Streams a point as (x,y) or label:(x,y).
Definition io.hpp:27