Sourcemeta Core 0.0.0
Loading...
Searching...
No Matches
numeric_uint128.h
1#ifndef SOURCEMETA_CORE_NUMERIC_UINT128_H_
2#define SOURCEMETA_CORE_NUMERIC_UINT128_H_
3
4#ifndef SOURCEMETA_CORE_NUMERIC_EXPORT
5#include <sourcemeta/core/numeric_export.h>
6#endif
7
8#include <cassert> // assert
9#include <climits> // UINT64_MAX
10#include <cstdint> // std::uint64_t, std::int64_t
11
12#if defined(_MSC_VER)
13#include <intrin.h> // _umul128, _udiv128
14#endif
15
16namespace sourcemeta::core {
17
20#if defined(__SIZEOF_INT128__) || defined(__clang__)
21using uint128_t = __uint128_t;
22#else
23// We keep the full implementation header-only to allow the compiler to better
24// inline
25struct uint128_t {
27 std::uint64_t low;
29 std::uint64_t high;
30
31 constexpr uint128_t() noexcept : low{0}, high{0} {}
33 constexpr uint128_t(int value) noexcept
34 : low{static_cast<std::uint64_t>(value)},
35 high{value < 0 ? UINT64_MAX : 0} {}
36
37 constexpr uint128_t(unsigned int value) noexcept
38 : low{static_cast<std::uint64_t>(value)}, high{0} {}
39
40 constexpr uint128_t(std::uint64_t value) noexcept : low{value}, high{0} {}
42 constexpr uint128_t(std::int64_t value) noexcept
43 : low{static_cast<std::uint64_t>(value)},
44 high{value < 0 ? UINT64_MAX : 0} {}
45
46 constexpr uint128_t(std::uint64_t high_part, std::uint64_t low_part) noexcept
47 : low{low_part}, high{high_part} {}
48
49 constexpr explicit operator std::uint64_t() const noexcept {
50 return this->low;
51 }
52 constexpr explicit operator std::int64_t() const noexcept {
53 return static_cast<std::int64_t>(this->low);
54 }
55
56 constexpr explicit operator bool() const noexcept {
57 return this->low != 0 || this->high != 0;
58 }
59
60 constexpr auto operator+=(const uint128_t &other) noexcept -> uint128_t & {
61 auto old_low = this->low;
62 this->low += other.low;
63 this->high += other.high + (this->low < old_low ? 1 : 0);
64 return *this;
65 }
66
67 constexpr auto operator-=(const uint128_t &other) noexcept -> uint128_t & {
68 const auto old_low = this->low;
69 this->low -= other.low;
70 this->high -= other.high + (old_low < other.low ? 1 : 0);
71 return *this;
72 }
73
74 constexpr auto operator*=(const uint128_t &other) noexcept -> uint128_t & {
75 *this = *this * other;
76 return *this;
77 }
78
79 constexpr auto operator%=(const uint128_t &other) noexcept -> uint128_t & {
80 *this = *this % other;
81 return *this;
82 }
83
84 friend constexpr auto operator+(uint128_t left,
85 const uint128_t &right) noexcept
86 -> uint128_t {
87 left += right;
88 return left;
89 }
90
91 friend constexpr auto operator-(uint128_t left,
92 const uint128_t &right) noexcept
93 -> uint128_t {
94 left -= right;
95 return left;
96 }
97
98 friend constexpr auto operator*(const uint128_t &left,
99 const uint128_t &right) noexcept
100 -> uint128_t {
101 std::uint64_t result_high;
102 std::uint64_t result_low;
103 // The intrinsic is not usable during constant evaluation, so fall through
104 // to the portable computation, which yields the same value
105#if defined(_MSC_VER)
106 if !consteval {
107 result_low = _umul128(left.low, right.low, &result_high);
108 } else
109#endif
110 {
111 const std::uint64_t left_low{left.low & 0xFFFFFFFF};
112 const std::uint64_t left_high{left.low >> 32};
113 const std::uint64_t right_low{right.low & 0xFFFFFFFF};
114 const std::uint64_t right_high{right.low >> 32};
115 const std::uint64_t cross_1{left_high * right_low};
116 const std::uint64_t cross_2{left_low * right_high};
117 const std::uint64_t mid{cross_1 + (left_low * right_low >> 32)};
118 const std::uint64_t mid_2{(mid & 0xFFFFFFFF) + cross_2};
119 result_high = left_high * right_high + (mid >> 32) + (mid_2 >> 32);
120 result_low = (mid_2 << 32) | ((left_low * right_low) & 0xFFFFFFFF);
121 }
122 result_high += left.low * right.high + left.high * right.low;
123 return {result_high, result_low};
124 }
125
126 friend constexpr auto operator/(const uint128_t &dividend,
127 std::uint64_t divisor) noexcept -> uint128_t {
128 if (dividend.high == 0) {
129 return {0, dividend.low / divisor};
130 }
131
132 auto quotient_high = dividend.high / divisor;
133 auto remainder_high = dividend.high % divisor;
134 std::uint64_t quotient_low;
135 // The high half of the partial dividend must stay below the divisor, or the
136 // hardware 128-by-64 division would overflow its 64-bit quotient. The
137 // remainder of the earlier division guarantees this
138 assert(remainder_high < divisor);
139 // The intrinsic is not usable during constant evaluation, so fall through
140 // to the portable computation, which yields the same value
141#if defined(_MSC_VER)
142 if !consteval {
143 std::uint64_t remainder;
144 quotient_low =
145 _udiv128(remainder_high, dividend.low, divisor, &remainder);
146 } else
147#endif
148 {
149 quotient_low = 0;
150 auto current_remainder = remainder_high;
151 for (int bit = 63; bit >= 0; --bit) {
152 const auto carry = current_remainder >> 63;
153 current_remainder =
154 (current_remainder << 1) | ((dividend.low >> bit) & 1);
155 if (carry || current_remainder >= divisor) {
156 current_remainder -= divisor;
157 quotient_low |= static_cast<std::uint64_t>(1) << bit;
158 }
159 }
160 }
161 return {quotient_high, quotient_low};
162 }
163
164 friend constexpr auto operator/(const uint128_t &dividend,
165 const uint128_t &divisor) noexcept
166 -> uint128_t {
167 assert(divisor.high == 0);
168 return dividend / divisor.low;
169 }
170
171 friend constexpr auto operator%(const uint128_t &dividend,
172 std::uint64_t divisor) noexcept -> uint128_t {
173 if (dividend.high == 0) {
174 return {0, dividend.low % divisor};
175 }
176
177 auto remainder_high = dividend.high % divisor;
178 std::uint64_t remainder;
179 // The high half of the partial dividend must stay below the divisor, or the
180 // hardware 128-by-64 division would overflow its 64-bit quotient. The
181 // remainder of the earlier division guarantees this
182 assert(remainder_high < divisor);
183 // The intrinsic is not usable during constant evaluation, so fall through
184 // to the portable computation, which yields the same value
185#if defined(_MSC_VER)
186 if !consteval {
187 _udiv128(remainder_high, dividend.low, divisor, &remainder);
188 } else
189#endif
190 {
191 remainder = remainder_high;
192 for (int bit = 63; bit >= 0; --bit) {
193 const auto carry = remainder >> 63;
194 remainder = (remainder << 1) | ((dividend.low >> bit) & 1);
195 if (carry || remainder >= divisor) {
196 remainder -= divisor;
197 }
198 }
199 }
200 return {0, remainder};
201 }
202
203 friend constexpr auto operator%(const uint128_t &dividend,
204 const uint128_t &divisor) noexcept
205 -> uint128_t {
206 assert(divisor.high == 0);
207 return dividend % divisor.low;
208 }
209
210 friend constexpr auto operator<(const uint128_t &left,
211 const uint128_t &right) noexcept -> bool {
212 return left.high < right.high ||
213 (left.high == right.high && left.low < right.low);
214 }
215
216 friend constexpr auto operator>(const uint128_t &left,
217 const uint128_t &right) noexcept -> bool {
218 return right < left;
219 }
220
221 friend constexpr auto operator<=(const uint128_t &left,
222 const uint128_t &right) noexcept -> bool {
223 return !(right < left);
224 }
225
226 friend constexpr auto operator>=(const uint128_t &left,
227 const uint128_t &right) noexcept -> bool {
228 return !(left < right);
229 }
230
231 friend constexpr auto operator==(const uint128_t &left,
232 const uint128_t &right) noexcept -> bool {
233 return left.high == right.high && left.low == right.low;
234 }
235
236 friend constexpr auto operator!=(const uint128_t &left,
237 const uint128_t &right) noexcept -> bool {
238 return !(left == right);
239 }
240
241 friend constexpr auto operator<<(const uint128_t &value, int shift) noexcept
242 -> uint128_t {
243 if (shift == 0) {
244 return value;
245 } else if (shift >= 128) {
246 return {0, 0};
247 } else if (shift >= 64) {
248 return {value.low << (shift - 64), 0};
249 } else {
250 return {(value.high << shift) | (value.low >> (64 - shift)),
251 value.low << shift};
252 }
253 }
254
255 friend constexpr auto operator>>(const uint128_t &value, int shift) noexcept
256 -> uint128_t {
257 if (shift == 0) {
258 return value;
259 } else if (shift >= 128) {
260 return {0, 0};
261 } else if (shift >= 64) {
262 return {0, value.high >> (shift - 64)};
263 } else {
264 return {value.high >> shift,
265 (value.low >> shift) | (value.high << (64 - shift))};
266 }
267 }
268
269 friend constexpr auto operator|(const uint128_t &left,
270 const uint128_t &right) noexcept
271 -> uint128_t {
272 return {left.high | right.high, left.low | right.low};
273 }
274
275 constexpr auto operator|=(const uint128_t &other) noexcept -> uint128_t & {
276 this->high |= other.high;
277 this->low |= other.low;
278 return *this;
279 }
280
281 friend constexpr auto operator&(const uint128_t &left,
282 const uint128_t &right) noexcept
283 -> uint128_t {
284 return {left.high & right.high, left.low & right.low};
285 }
286};
287#endif
288
289} // namespace sourcemeta::core
290
291#endif
constexpr uint128_t(std::uint64_t high_part, std::uint64_t low_part) noexcept
Construct from separate upper and lower 64-bit halves.
Definition numeric_uint128.h:46
constexpr uint128_t(std::int64_t value) noexcept
Construct from a 64-bit signed integer, sign-extending negative values.
Definition numeric_uint128.h:42
constexpr uint128_t(std::uint64_t value) noexcept
Construct from a 64-bit unsigned integer.
Definition numeric_uint128.h:40
std::uint64_t low
The lower 64 bits of the value.
Definition numeric_uint128.h:27
std::uint64_t high
The upper 64 bits of the value.
Definition numeric_uint128.h:29
constexpr uint128_t(int value) noexcept
Construct from a signed integer, sign-extending negative values.
Definition numeric_uint128.h:33
constexpr uint128_t(unsigned int value) noexcept
Construct from an unsigned integer.
Definition numeric_uint128.h:37
Definition numeric_uint128.h:25