tess 1.0.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 {
10
11// Public because TileKey::value is spelled with it: any shape needing more
12// than 64 tile-key bits gets a TileKey whose `value` is this type, and a
13// consumer reading that member must be able to name it. Smaller shapes get
14// a plain std::uint64_t, so most consumers never see this at all.
15//
16// It is a BIT CARRIER, not a general 128-bit integer, and the operator set
17// is deliberately partial: implicit construction from an unsigned integer,
18// comparison, the multiply/subtract/bitwise/shift operations key packing
19// needs, and EXPLICIT narrowing to std::uint64_t. Widening is implicit so
20// integer literals keep working as counts; narrowing is not, because an
21// implicit one would silently drop the high half at any call taking a
22// std::uint64_t. There is no addition, division, modulo or increment, and
23// none is coming as a side effect -- tests/tess_uint128_surface_test.cc
24// pins the set in both directions, so widening it means editing that file.
25//
26// Portable 128-bit unsigned integer used unconditionally on every compiler,
27// so Clang and GCC CI exercise exactly the code MSVC compiles (MSVC has no
28// unsigned __int128). It provides only the operations shape.h needs:
29// multiplication, subtraction, bitwise and/or, shifts, comparisons, and
30// explicit narrowing to std::uint64_t. Arithmetic wraps modulo 2^128 like
31// the builtin. Shifts define counts >= 128 as zero and never shift a
32// std::uint64_t by 64, so no operation has undefined behavior.
34struct UInt128 {
35 // hi is declared first so the defaulted comparisons order lexicographically
36 // by (hi, lo), which matches numeric order.
37 std::uint64_t hi = 0;
38 std::uint64_t lo = 0;
39
40 constexpr UInt128() noexcept = default;
41
42 // Implicit, so existing unsigned expressions keep working as counts.
43 // Templated over all unsigned integral types because std::uint64_t is
44 // unsigned long on Linux LP64 but unsigned long long on macOS/Windows;
45 // a single std::uint64_t overload leaves `1ull` literals ambiguous
46 // against the int constructor on whichever platform differs.
47 template <typename T>
48 requires std::unsigned_integral<T>
49 // NOLINTNEXTLINE(google-explicit-constructor)
50 constexpr UInt128(T value) noexcept : lo(value) {}
51
52 // Implicit, so integer literals in existing code keep working. Templated
53 // over signed integral types rather than taking a plain `int`: an `int`
54 // parameter accepted any wider signed value through a silent narrowing
55 // conversion, so `UInt128 v = std::int64_t{1} << 32` was zero. Negative
56 // values remain a precondition violation rather than a wrap.
57 template <typename T>
58 requires std::signed_integral<T>
59 // NOLINTNEXTLINE(google-explicit-constructor)
60 constexpr UInt128(T value) noexcept
61 : lo((TESS_ASSERT(value >= 0), static_cast<std::uint64_t>(value))) {}
62
63 [[nodiscard]] static constexpr auto from_parts(std::uint64_t high,
64 std::uint64_t low) noexcept
65 -> UInt128 {
66 auto result = UInt128{};
67 result.hi = high;
68 result.lo = low;
69 return result;
70 }
71
72 [[nodiscard]] explicit constexpr operator std::uint64_t() const noexcept {
73 return lo;
74 }
75
76 friend constexpr auto operator==(UInt128 lhs, UInt128 rhs) noexcept
77 -> bool = default;
78 friend constexpr auto operator<=>(UInt128 lhs, UInt128 rhs) noexcept
79 -> std::strong_ordering = default;
80
81 friend constexpr auto operator*(UInt128 lhs, UInt128 rhs) noexcept
82 -> UInt128 {
83 const std::uint64_t mask32 = 0xffffffffULL;
84 const std::uint64_t a_lo = lhs.lo & mask32;
85 const std::uint64_t a_hi = lhs.lo >> 32U;
86 const std::uint64_t b_lo = rhs.lo & mask32;
87 const std::uint64_t b_hi = rhs.lo >> 32U;
88
89 // Full 128-bit product of the two low words via 32-bit partials.
90 const std::uint64_t p0 = a_lo * b_lo;
91 const std::uint64_t p1 = a_lo * b_hi;
92 const std::uint64_t p2 = a_hi * b_lo;
93 const std::uint64_t p3 = a_hi * b_hi;
94
95 const std::uint64_t mid = p1 + (p0 >> 32U);
96 const std::uint64_t mid2 = p2 + (mid & mask32);
97
98 const std::uint64_t low = (mid2 << 32U) | (p0 & mask32);
99 std::uint64_t high = p3 + (mid >> 32U) + (mid2 >> 32U);
100
101 // Cross terms only affect the high word (wrap-around semantics).
102 high += lhs.hi * rhs.lo + lhs.lo * rhs.hi;
103 return from_parts(high, low);
104 }
105
106 friend constexpr auto operator-(UInt128 lhs, UInt128 rhs) noexcept
107 -> UInt128 {
108 const std::uint64_t borrow = lhs.lo < rhs.lo ? 1U : 0U;
109 return from_parts(lhs.hi - rhs.hi - borrow, lhs.lo - rhs.lo);
110 }
111
112 friend constexpr auto operator&(UInt128 lhs, UInt128 rhs) noexcept
113 -> UInt128 {
114 return from_parts(lhs.hi & rhs.hi, lhs.lo & rhs.lo);
115 }
116
117 friend constexpr auto operator|(UInt128 lhs, UInt128 rhs) noexcept
118 -> UInt128 {
119 return from_parts(lhs.hi | rhs.hi, lhs.lo | rhs.lo);
120 }
121
122 friend constexpr auto operator<<(UInt128 value, std::uint32_t count) noexcept
123 -> UInt128 {
124 if (count == 0U) {
125 return value;
126 }
127 if (count >= 128U) {
128 return UInt128{};
129 }
130 if (count >= 64U) {
131 return from_parts(value.lo << (count - 64U), 0U);
132 }
133 return from_parts((value.hi << count) | (value.lo >> (64U - count)),
134 value.lo << count);
135 }
136
137 friend constexpr auto operator>>(UInt128 value, std::uint32_t count) noexcept
138 -> UInt128 {
139 if (count == 0U) {
140 return value;
141 }
142 if (count >= 128U) {
143 return UInt128{};
144 }
145 if (count >= 64U) {
146 return from_parts(0U, value.hi >> (count - 64U));
147 }
148 return from_parts(value.hi >> count,
149 (value.lo >> count) | (value.hi << (64U - count)));
150 }
151
152 constexpr auto operator<<=(std::uint32_t count) noexcept -> UInt128& {
153 *this = *this << count;
154 return *this;
155 }
156
157 constexpr auto operator>>=(std::uint32_t count) noexcept -> UInt128& {
158 *this = *this >> count;
159 return *this;
160 }
161};
162
163} // namespace tess