tess 0.4.0
Performance-first tile and path simulation substrate
Loading...
Searching...
No Matches
uint128.h
1#pragma once
2
3#include <tess/core/assert.h>
4
5#include <compare>
6#include <concepts>
7#include <cstdint>
8
9namespace tess::detail {
10
11// Portable 128-bit unsigned integer used unconditionally on every compiler,
12// so Clang and GCC CI exercise exactly the code MSVC compiles (MSVC has no
13// unsigned __int128). It provides only the operations shape.h needs:
14// multiplication, subtraction, bitwise and/or, shifts, comparisons, and
15// explicit narrowing to std::uint64_t. Arithmetic wraps modulo 2^128 like
16// the builtin. Shifts define counts >= 128 as zero and never shift a
17// std::uint64_t by 64, so no operation has undefined behavior.
18struct UInt128 {
19 // hi is declared first so the defaulted comparisons order lexicographically
20 // by (hi, lo), which matches numeric order.
21 std::uint64_t hi = 0;
22 std::uint64_t lo = 0;
23
24 constexpr UInt128() noexcept = default;
25
26 // Implicit, so existing unsigned expressions keep working as counts.
27 // Templated over all unsigned integral types because std::uint64_t is
28 // unsigned long on Linux LP64 but unsigned long long on macOS/Windows;
29 // a single std::uint64_t overload leaves `1ull` literals ambiguous
30 // against the int constructor on whichever platform differs.
31 template <typename T>
32 requires std::unsigned_integral<T>
33 // NOLINTNEXTLINE(google-explicit-constructor)
34 constexpr UInt128(T value) noexcept : lo(value) {}
35
36 // Implicit, so integer literals in existing code keep working.
37 // NOLINTNEXTLINE(google-explicit-constructor)
38 constexpr UInt128(int value) noexcept
39 : lo((TESS_ASSERT(value >= 0), static_cast<std::uint64_t>(value))) {}
40
41 [[nodiscard]] static constexpr auto from_parts(std::uint64_t high,
42 std::uint64_t low) noexcept
43 -> UInt128 {
44 auto result = UInt128{};
45 result.hi = high;
46 result.lo = low;
47 return result;
48 }
49
50 [[nodiscard]] explicit constexpr operator std::uint64_t() const noexcept {
51 return lo;
52 }
53
54 friend constexpr auto operator==(UInt128 lhs, UInt128 rhs) noexcept
55 -> bool = default;
56 friend constexpr auto operator<=>(UInt128 lhs, UInt128 rhs) noexcept
57 -> std::strong_ordering = default;
58
59 friend constexpr auto operator*(UInt128 lhs, UInt128 rhs) noexcept
60 -> UInt128 {
61 const std::uint64_t mask32 = 0xffffffffULL;
62 const std::uint64_t a_lo = lhs.lo & mask32;
63 const std::uint64_t a_hi = lhs.lo >> 32U;
64 const std::uint64_t b_lo = rhs.lo & mask32;
65 const std::uint64_t b_hi = rhs.lo >> 32U;
66
67 // Full 128-bit product of the two low words via 32-bit partials.
68 const std::uint64_t p0 = a_lo * b_lo;
69 const std::uint64_t p1 = a_lo * b_hi;
70 const std::uint64_t p2 = a_hi * b_lo;
71 const std::uint64_t p3 = a_hi * b_hi;
72
73 const std::uint64_t mid = p1 + (p0 >> 32U);
74 const std::uint64_t mid2 = p2 + (mid & mask32);
75
76 const std::uint64_t low = (mid2 << 32U) | (p0 & mask32);
77 std::uint64_t high = p3 + (mid >> 32U) + (mid2 >> 32U);
78
79 // Cross terms only affect the high word (wrap-around semantics).
80 high += lhs.hi * rhs.lo + lhs.lo * rhs.hi;
81 return from_parts(high, low);
82 }
83
84 friend constexpr auto operator-(UInt128 lhs, UInt128 rhs) noexcept
85 -> UInt128 {
86 const std::uint64_t borrow = lhs.lo < rhs.lo ? 1U : 0U;
87 return from_parts(lhs.hi - rhs.hi - borrow, lhs.lo - rhs.lo);
88 }
89
90 friend constexpr auto operator&(UInt128 lhs, UInt128 rhs) noexcept
91 -> UInt128 {
92 return from_parts(lhs.hi & rhs.hi, lhs.lo & rhs.lo);
93 }
94
95 friend constexpr auto operator|(UInt128 lhs, UInt128 rhs) noexcept
96 -> UInt128 {
97 return from_parts(lhs.hi | rhs.hi, lhs.lo | rhs.lo);
98 }
99
100 friend constexpr auto operator<<(UInt128 value, std::uint32_t count) noexcept
101 -> UInt128 {
102 if (count == 0U) {
103 return value;
104 }
105 if (count >= 128U) {
106 return UInt128{};
107 }
108 if (count >= 64U) {
109 return from_parts(value.lo << (count - 64U), 0U);
110 }
111 return from_parts((value.hi << count) | (value.lo >> (64U - count)),
112 value.lo << count);
113 }
114
115 friend constexpr auto operator>>(UInt128 value, std::uint32_t count) noexcept
116 -> UInt128 {
117 if (count == 0U) {
118 return value;
119 }
120 if (count >= 128U) {
121 return UInt128{};
122 }
123 if (count >= 64U) {
124 return from_parts(0U, value.hi >> (count - 64U));
125 }
126 return from_parts(value.hi >> count,
127 (value.lo >> count) | (value.hi << (64U - count)));
128 }
129
130 constexpr auto operator<<=(std::uint32_t count) noexcept -> UInt128& {
131 *this = *this << count;
132 return *this;
133 }
134
135 constexpr auto operator>>=(std::uint32_t count) noexcept -> UInt128& {
136 *this = *this >> count;
137 return *this;
138 }
139};
140
141} // namespace tess::detail