VLink  2.0.0
A high-performance communication middleware
uint128.h
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2026 by Thun Lu. All rights reserved.
3  * Author: Thun Lu <thun.lu@zohomail.cn>
4  * Repo: https://github.com/thun-res/vlink
5  * _ __ __ _ __
6  * | | / / / / (_) ____ / /__
7  * | | / / / / / / / __ \ / //_/
8  * | |/ / / /___ / / / / / / / ,<
9  * |___/ /_____/ /_/ /_/ /_/ /_/|_|
10  *
11  * Licensed under the Apache License, Version 2.0 (the "License");
12  * you may not use this file except in compliance with the License.
13  * You may obtain a copy of the License at
14  *
15  * http://www.apache.org/licenses/LICENSE-2.0
16  *
17  * Unless required by applicable law or agreed to in writing, software
18  * distributed under the License is distributed on an "AS IS" BASIS,
19  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20  * See the License for the specific language governing permissions and
21  * limitations under the License.
22  */
23 
24 /**
25  * @file uint128.h
26  * @brief Portable 128-bit unsigned integer with native-fastpath multiplication.
27  *
28  * @details
29  * @c vlink::Uint128 stores the value as a pair of @c uint64_t halves (@c high_, @c low_).
30  * On GCC and Clang 64-bit targets the multiplication, division and modulo paths delegate
31  * to the compiler-native @c __uint128_t for maximum performance; on platforms without
32  * that builtin the portable fallback (@c mul_u128_fallback / @c u128_divmod) is used.
33  *
34  * Supported operator set:
35  *
36  * | Category | Operators | Notes |
37  * | ---------- | -------------------------------------- | ------------------------------------ |
38  * | Arithmetic | + - * / % += -= *= /= %= | Division throws on divisor @c 0 |
39  * | Bitwise | OR AND XOR NOT shift compound-assign | Shifts clamp to @c [0, @c 128] |
40  * | Comparison | == != < > <= >= | Lexicographic over (high, low) |
41  * | Increment | ++ -- (prefix and postfix) | Carry/borrow crosses the 64-bit line |
42  * | Stream | operator<<(std::ostream&) | Uppercase hexadecimal output |
43  *
44  * @par Implicit construction from integral types
45  * The single-argument constructor is intentionally non-explicit so integral literals can
46  * flow into @c Uint128 transparently. Signed source values are sign-extended, unsigned
47  * values are zero-extended, and @c __uint128_t source values are split into halves.
48  *
49  * @par Conversion back to @c __uint128_t
50  * An explicit @c operator @c __uint128_t() is provided on platforms that expose the type.
51  *
52  * @par std::hash specialisation
53  * A @c std::hash<vlink::Uint128> specialisation at the bottom of this file enables use
54  * inside @c std::unordered_map and @c std::unordered_set.
55  *
56  * @par Example
57  * @code
58  * vlink::Uint128 a(0, UINT64_MAX);
59  * vlink::Uint128 b(1, 0);
60  * vlink::Uint128 c = a + vlink::Uint128(0, 1);
61  * assert(c == b);
62  *
63  * std::unordered_map<vlink::uint128_t, std::string> map;
64  * map[vlink::Uint128(0xDEAD, 0xBEEF)] = "key";
65  * @endcode
66  */
67 
68 #pragma once
69 
70 #include <cstdint>
71 #include <iostream>
72 #include <type_traits>
73 #include <utility>
74 
75 #include "./macros.h"
76 
77 namespace vlink {
78 
79 /**
80  * @class Uint128
81  * @brief 128-bit unsigned integer represented as two 64-bit halves with full operator support.
82  *
83  * @details
84  * Logical layout is @c (high_ @c << @c 64) @c | @c low_. All arithmetic propagates carry
85  * and borrow across the 64-bit boundary so the observable behaviour matches a true
86  * 128-bit unsigned integer.
87  */
88 class Uint128 final {
89  public:
90  /**
91  * @brief Default-constructs a zero-valued @c Uint128.
92  */
93  Uint128() noexcept = default;
94 
95  /**
96  * @brief Constructs from an integral-like type @p T (implicit on purpose).
97  *
98  * @details
99  * - When @c T is @c __uint128_t (where available) the source is split into halves.
100  * - When @c T is signed the source is sign-extended: a negative value yields
101  * @c high_ @c = @c ~uint64_t{0} and @c low_ @c = the two's-complement bit pattern.
102  * - When @c T is unsigned the source is zero-extended into @c low_ with @c high_ @c = @c 0.
103  * - For any other @p T both halves keep their default value of @c 0; no diagnostic is
104  * emitted because integrality is intentionally not asserted here.
105  *
106  * @tparam T Source type (integral or @c __uint128_t).
107  * @param v Source value.
108  *
109  * @note Non-explicit by design so integral literals interoperate naturally.
110  */
111  template <typename T>
112  constexpr Uint128(T v) noexcept; // NOLINT(google-explicit-constructor)
113 
114  /**
115  * @brief Constructs from explicit high and low halves.
116  *
117  * @param high Upper 64 bits.
118  * @param low Lower 64 bits.
119  */
120  explicit Uint128(uint64_t high, uint64_t low) noexcept;
121 
122 #if defined(__SIZEOF_INT128__)
123  /**
124  * @brief Converts to the compiler-native @c __uint128_t type.
125  *
126  * @details
127  * Only available where @c __uint128_t exists (GCC/Clang 64-bit targets).
128  *
129  * @return Reconstructed native 128-bit value.
130  */
131  explicit operator __uint128_t() const noexcept;
132 #endif
133 
134  /**
135  * @brief Returns the sum of @c *this and @p other.
136  *
137  * @param other Right operand.
138  * @return New value equal to @c *this + @p other (wraps on overflow).
139  */
140  Uint128 operator+(const Uint128& other) const noexcept;
141 
142  /**
143  * @brief Returns the difference of @c *this and @p other.
144  *
145  * @param other Right operand.
146  * @return New value equal to @c *this - @p other (wraps on underflow).
147  */
148  Uint128 operator-(const Uint128& other) const noexcept;
149 
150  /**
151  * @brief Returns the product of @c *this and @p other.
152  *
153  * @details
154  * Delegates to native @c __uint128_t multiplication when available; otherwise falls
155  * back to @c mul_u128_fallback.
156  *
157  * @param other Right operand.
158  * @return Low 128 bits of the true product.
159  */
160  Uint128 operator*(const Uint128& other) const noexcept;
161 
162  /**
163  * @brief Returns the quotient of @c *this divided by @p other.
164  *
165  * @param other Divisor.
166  * @return Quotient.
167  *
168  * @throws std::domain_error when @p other is zero.
169  */
170  Uint128 operator/(const Uint128& other) const;
171 
172  /**
173  * @brief Returns the remainder of @c *this divided by @p other.
174  *
175  * @param other Divisor.
176  * @return Remainder.
177  *
178  * @throws std::domain_error when @p other is zero.
179  */
180  Uint128 operator%(const Uint128& other) const;
181 
182  /**
183  * @brief Adds @p other to @c *this in-place with carry propagation.
184  *
185  * @param other Right operand.
186  * @return Reference to @c *this.
187  */
188  Uint128& operator+=(const Uint128& other) noexcept;
189 
190  /**
191  * @brief Subtracts @p other from @c *this in-place with borrow propagation.
192  *
193  * @param other Right operand.
194  * @return Reference to @c *this.
195  */
196  Uint128& operator-=(const Uint128& other) noexcept;
197 
198  /**
199  * @brief Multiplies @c *this by @p other in-place.
200  *
201  * @param other Right operand.
202  * @return Reference to @c *this.
203  */
204  Uint128& operator*=(const Uint128& other) noexcept;
205 
206  /**
207  * @brief Divides @c *this by @p other in-place.
208  *
209  * @param other Divisor.
210  * @return Reference to @c *this.
211  *
212  * @throws std::domain_error when @p other is zero.
213  */
214  Uint128& operator/=(const Uint128& other);
215 
216  /**
217  * @brief Computes @c *this modulo @p other in-place.
218  *
219  * @param other Divisor.
220  * @return Reference to @c *this.
221  *
222  * @throws std::domain_error when @p other is zero.
223  */
224  Uint128& operator%=(const Uint128& other);
225 
226  /**
227  * @brief Returns the bitwise OR of @c *this and @p other.
228  *
229  * @param other Right operand.
230  * @return New value with each bit set if it is set in either operand.
231  */
232  Uint128 operator|(const Uint128& other) const noexcept;
233 
234  /**
235  * @brief Returns the bitwise AND of @c *this and @p other.
236  *
237  * @param other Right operand.
238  * @return New value with each bit set only when set in both operands.
239  */
240  Uint128 operator&(const Uint128& other) const noexcept;
241 
242  /**
243  * @brief Returns the bitwise XOR of @c *this and @p other.
244  *
245  * @param other Right operand.
246  * @return New value with each bit set when the bits differ between operands.
247  */
248  Uint128 operator^(const Uint128& other) const noexcept;
249 
250  /**
251  * @brief Returns the bitwise complement of @c *this.
252  *
253  * @return New value with all 128 bits inverted.
254  */
255  Uint128 operator~() const noexcept;
256 
257  /**
258  * @brief Returns @c *this shifted left by @p shift bits.
259  *
260  * @details
261  * Shift values @c <= @c 0 leave the value unchanged; @c >= @c 128 produce zero; values
262  * in @c [64, @c 127] move bits across the 64-bit boundary.
263  *
264  * @param shift Bit positions to shift.
265  * @return Shifted value.
266  */
267  Uint128 operator<<(int shift) const noexcept;
268 
269  /**
270  * @brief Returns @c *this shifted right by @p shift bits (logical, zero-fill).
271  *
272  * @details
273  * Same edge-case rules as @c operator<<.
274  *
275  * @param shift Bit positions to shift.
276  * @return Shifted value.
277  */
278  Uint128 operator>>(int shift) const noexcept;
279 
280  /**
281  * @brief Applies bitwise OR with @p other in-place.
282  *
283  * @param other Right operand.
284  * @return Reference to @c *this.
285  */
286  Uint128& operator|=(const Uint128& other) noexcept;
287 
288  /**
289  * @brief Applies bitwise AND with @p other in-place.
290  *
291  * @param other Right operand.
292  * @return Reference to @c *this.
293  */
294  Uint128& operator&=(const Uint128& other) noexcept;
295 
296  /**
297  * @brief Applies bitwise XOR with @p other in-place.
298  *
299  * @param other Right operand.
300  * @return Reference to @c *this.
301  */
302  Uint128& operator^=(const Uint128& other) noexcept;
303 
304  /**
305  * @brief Shifts @c *this left by @p shift bits in-place.
306  *
307  * @param shift Bit positions to shift.
308  * @return Reference to @c *this.
309  */
310  Uint128& operator<<=(int shift) noexcept;
311 
312  /**
313  * @brief Shifts @c *this right by @p shift bits in-place (logical, zero-fill).
314  *
315  * @param shift Bit positions to shift.
316  * @return Reference to @c *this.
317  */
318  Uint128& operator>>=(int shift) noexcept;
319 
320  /**
321  * @brief Returns @c true when both halves of @c *this and @p other are equal.
322  *
323  * @param other Right operand.
324  * @return Equality result.
325  */
326  [[nodiscard]] bool operator==(const Uint128& other) const noexcept;
327 
328  /**
329  * @brief Returns @c true when any half differs.
330  *
331  * @param other Right operand.
332  * @return Inequality result.
333  */
334  [[nodiscard]] bool operator!=(const Uint128& other) const noexcept;
335 
336  /**
337  * @brief Returns @c true when @c *this is strictly less than @p other.
338  *
339  * @details
340  * Compares the high half first; ties are broken by the low half.
341  *
342  * @param other Right operand.
343  * @return Comparison result.
344  */
345  [[nodiscard]] bool operator<(const Uint128& other) const noexcept;
346 
347  /**
348  * @brief Returns @c true when @c *this is strictly greater than @p other.
349  *
350  * @param other Right operand.
351  * @return Comparison result.
352  */
353  [[nodiscard]] bool operator>(const Uint128& other) const noexcept;
354 
355  /**
356  * @brief Returns @c true when @c *this is less than or equal to @p other.
357  *
358  * @param other Right operand.
359  * @return Comparison result.
360  */
361  [[nodiscard]] bool operator<=(const Uint128& other) const noexcept;
362 
363  /**
364  * @brief Returns @c true when @c *this is greater than or equal to @p other.
365  *
366  * @param other Right operand.
367  * @return Comparison result.
368  */
369  [[nodiscard]] bool operator>=(const Uint128& other) const noexcept;
370 
371  /**
372  * @brief Pre-increment with carry across the 64-bit boundary.
373  *
374  * @return Reference to the incremented value.
375  */
376  Uint128& operator++() noexcept;
377 
378  /**
379  * @brief Post-increment returning a copy of the value prior to incrementing.
380  *
381  * @return Pre-increment value.
382  */
383  Uint128 operator++(int) noexcept;
384 
385  /**
386  * @brief Pre-decrement with borrow across the 64-bit boundary.
387  *
388  * @return Reference to the decremented value.
389  */
390  Uint128& operator--() noexcept;
391 
392  /**
393  * @brief Post-decrement returning a copy of the value prior to decrementing.
394  *
395  * @return Pre-decrement value.
396  */
397  Uint128 operator--(int) noexcept;
398 
399  /**
400  * @brief Returns the upper 64 bits of the stored value.
401  *
402  * @return High half.
403  */
404  [[nodiscard]] uint64_t get_high() const noexcept;
405 
406  /**
407  * @brief Returns the lower 64 bits of the stored value.
408  *
409  * @return Low half.
410  */
411  [[nodiscard]] uint64_t get_low() const noexcept;
412 
413  /**
414  * @brief Writes the uppercase hexadecimal representation of @p value to @p os.
415  *
416  * @param os Output stream.
417  * @param value Value to print.
418  * @return Reference to @p os.
419  */
420  VLINK_EXPORT friend std::ostream& operator<<(std::ostream& os, const Uint128& value) noexcept;
421 
422  private:
423  VLINK_EXPORT static int clz64(uint64_t x) noexcept;
424 
425  VLINK_EXPORT static void mul_64_128(uint64_t a, uint64_t b, uint64_t& hi, uint64_t& lo) noexcept;
426 
427  VLINK_EXPORT static uint64_t add64_carry(uint64_t a, uint64_t b, uint64_t& carry_out) noexcept;
428 
429  VLINK_EXPORT static void add_128_with64(uint64_t& high, uint64_t& low, uint64_t add_low,
430  uint64_t add_high = 0) noexcept;
431 
432  VLINK_EXPORT static Uint128 mul_u128_fallback(const Uint128& x, const Uint128& y) noexcept;
433 
434  VLINK_EXPORT static std::pair<Uint128, Uint128> u128_divmod(const Uint128& dividend, const Uint128& divisor);
435 
436  uint64_t high_{0};
437  uint64_t low_{0};
438 };
439 
440 /**
441  * @brief Convenience alias matching the lowercase fixed-width style of the standard integer types.
442  */
444 
445 ////////////////////////////////////////////////////////////////
446 /// Details
447 ////////////////////////////////////////////////////////////////
448 
449 inline Uint128::Uint128(uint64_t high, uint64_t low) noexcept : high_(high), low_(low) {}
450 
451 #if defined(__SIZEOF_INT128__)
452 inline Uint128::operator __uint128_t() const noexcept { return (static_cast<__uint128_t>(high_) << 64) | low_; }
453 #endif
454 
455 inline Uint128 Uint128::operator+(const Uint128& other) const noexcept {
456  Uint128 r = *this;
457  r += other;
458 
459  return r;
460 }
461 
462 inline Uint128 Uint128::operator-(const Uint128& other) const noexcept {
463  Uint128 r = *this;
464  r -= other;
465 
466  return r;
467 }
468 
469 inline Uint128 Uint128::operator*(const Uint128& other) const noexcept {
470  Uint128 r = *this;
471  r *= other;
472 
473  return r;
474 }
475 
476 inline Uint128 Uint128::operator/(const Uint128& other) const { return u128_divmod(*this, other).first; }
477 
478 inline Uint128 Uint128::operator%(const Uint128& other) const { return u128_divmod(*this, other).second; }
479 
480 inline Uint128& Uint128::operator+=(const Uint128& other) noexcept {
481  uint64_t old_low = low_;
482 
483  low_ += other.low_;
484 
485  uint64_t carry = (low_ < old_low) ? 1 : 0;
486 
487  high_ += other.high_ + carry;
488 
489  return *this;
490 }
491 
492 inline Uint128& Uint128::operator-=(const Uint128& other) noexcept {
493  uint64_t borrow = (low_ < other.low_) ? 1 : 0;
494 
495  low_ -= other.low_;
496  high_ = high_ - other.high_ - borrow;
497 
498  return *this;
499 }
500 
501 inline Uint128& Uint128::operator*=(const Uint128& other) noexcept {
502 #if defined(__SIZEOF_INT128__)
503  __uint128_t lhs = (static_cast<__uint128_t>(high_) << 64) | low_;
504  __uint128_t rhs = (static_cast<__uint128_t>(other.high_) << 64) | other.low_;
505  __uint128_t res = lhs * rhs;
506 
507  high_ = static_cast<uint64_t>(res >> 64);
508  low_ = static_cast<uint64_t>(res);
509 #else
510  Uint128 r = mul_u128_fallback(*this, other);
511 
512  high_ = r.high_;
513  low_ = r.low_;
514 #endif
515  return *this;
516 }
517 
518 inline Uint128& Uint128::operator/=(const Uint128& other) {
519  auto qr = u128_divmod(*this, other);
520 
521  high_ = qr.first.high_;
522  low_ = qr.first.low_;
523 
524  return *this;
525 }
526 
527 inline Uint128& Uint128::operator%=(const Uint128& other) {
528  auto qr = u128_divmod(*this, other);
529 
530  high_ = qr.second.high_;
531  low_ = qr.second.low_;
532 
533  return *this;
534 }
535 
536 inline Uint128 Uint128::operator|(const Uint128& other) const noexcept {
537  return Uint128(high_ | other.high_, low_ | other.low_);
538 }
539 
540 inline Uint128 Uint128::operator&(const Uint128& other) const noexcept {
541  return Uint128(high_ & other.high_, low_ & other.low_);
542 }
543 
544 inline Uint128 Uint128::operator^(const Uint128& other) const noexcept {
545  return Uint128(high_ ^ other.high_, low_ ^ other.low_);
546 }
547 
548 inline Uint128 Uint128::operator~() const noexcept { return Uint128(~high_, ~low_); }
549 
550 inline Uint128 Uint128::operator<<(int shift) const noexcept {
551  if (shift <= 0) {
552  return *this;
553  }
554 
555  if (shift >= 128) {
556  return Uint128(0, 0);
557  }
558 
559  if (shift >= 64) {
560  return Uint128(low_ << (shift - 64), 0);
561  }
562 
563  uint64_t new_high = (high_ << shift) | (low_ >> (64 - shift));
564  uint64_t new_low = low_ << shift;
565 
566  return Uint128(new_high, new_low);
567 }
568 
569 inline Uint128 Uint128::operator>>(int shift) const noexcept {
570  if (shift <= 0) {
571  return *this;
572  }
573 
574  if (shift >= 128) {
575  return Uint128(0, 0);
576  }
577 
578  if (shift >= 64) {
579  return Uint128(0, high_ >> (shift - 64));
580  }
581 
582  uint64_t new_low = (low_ >> shift) | (high_ << (64 - shift));
583  uint64_t new_high = high_ >> shift;
584 
585  return Uint128(new_high, new_low);
586 }
587 
588 inline Uint128& Uint128::operator|=(const Uint128& other) noexcept {
589  high_ |= other.high_;
590  low_ |= other.low_;
591 
592  return *this;
593 }
594 
595 inline Uint128& Uint128::operator&=(const Uint128& other) noexcept {
596  high_ &= other.high_;
597  low_ &= other.low_;
598 
599  return *this;
600 }
601 
602 inline Uint128& Uint128::operator^=(const Uint128& other) noexcept {
603  high_ ^= other.high_;
604  low_ ^= other.low_;
605 
606  return *this;
607 }
608 
609 inline Uint128& Uint128::operator<<=(int shift) noexcept {
610  if (shift <= 0) {
611  return *this;
612  }
613 
614  if (shift >= 128) {
615  high_ = 0;
616  low_ = 0;
617 
618  return *this;
619  }
620 
621  if (shift >= 64) {
622  high_ = (low_ << (shift - 64));
623  low_ = 0;
624  } else {
625  uint64_t nh = (high_ << shift) | (low_ >> (64 - shift));
626  uint64_t nl = low_ << shift;
627 
628  high_ = nh;
629  low_ = nl;
630  }
631 
632  return *this;
633 }
634 
635 inline Uint128& Uint128::operator>>=(int shift) noexcept {
636  if (shift <= 0) {
637  return *this;
638  }
639 
640  if (shift >= 128) {
641  high_ = 0;
642  low_ = 0;
643  return *this;
644  }
645 
646  if (shift >= 64) {
647  low_ = (high_ >> (shift - 64));
648  high_ = 0;
649  } else {
650  uint64_t nl = (low_ >> shift) | (high_ << (64 - shift));
651  uint64_t nh = high_ >> shift;
652 
653  high_ = nh;
654  low_ = nl;
655  }
656 
657  return *this;
658 }
659 
660 inline bool Uint128::operator==(const Uint128& other) const noexcept {
661  return high_ == other.high_ && low_ == other.low_;
662 }
663 
664 inline bool Uint128::operator!=(const Uint128& other) const noexcept { return !(*this == other); }
665 
666 inline bool Uint128::operator<(const Uint128& other) const noexcept {
667  return (high_ < other.high_) || (high_ == other.high_ && low_ < other.low_);
668 }
669 
670 inline bool Uint128::operator>(const Uint128& other) const noexcept { return other < *this; }
671 
672 inline bool Uint128::operator<=(const Uint128& other) const noexcept { return !(other < *this); }
673 
674 inline bool Uint128::operator>=(const Uint128& other) const noexcept { return !(*this < other); }
675 
676 inline Uint128& Uint128::operator++() noexcept {
677  uint64_t old_low = low_;
678 
679  low_ += 1;
680 
681  if (low_ < old_low) {
682  ++high_;
683  }
684 
685  return *this;
686 }
687 
688 inline Uint128 Uint128::operator++(int) noexcept {
689  Uint128 tmp = *this;
690 
691  ++(*this);
692 
693  return tmp;
694 }
695 
696 inline Uint128& Uint128::operator--() noexcept {
697  uint64_t old_low = low_;
698  low_ -= 1;
699 
700  if (old_low == 0) {
701  --high_;
702  }
703 
704  return *this;
705 }
706 
707 inline Uint128 Uint128::operator--(int) noexcept {
708  Uint128 tmp = *this;
709 
710  --(*this);
711 
712  return tmp;
713 }
714 
715 inline uint64_t Uint128::get_high() const noexcept { return high_; }
716 
717 inline uint64_t Uint128::get_low() const noexcept { return low_; }
718 
719 template <typename T>
720 inline constexpr Uint128::Uint128(T v) noexcept {
721  // static_assert(std::is_integral_v<T>, "Uint128(T): T must be an integral type");
722 
723 #if defined(__SIZEOF_INT128__)
724  if constexpr (std::is_same_v<__uint128_t, T>) {
725  high_ = static_cast<uint64_t>(v >> 64);
726  low_ = static_cast<uint64_t>(v);
727 
728  return;
729  }
730 #endif
731 
732  if constexpr (std::is_constructible_v<uint64_t, T>) {
733  if constexpr (std::is_signed_v<T>) {
734  high_ = (v < 0) ? ~uint64_t{0} : uint64_t{0};
735  low_ = static_cast<uint64_t>(static_cast<int64_t>(v));
736  } else {
737  high_ = 0;
738  low_ = v;
739  }
740  }
741 }
742 
743 } // namespace vlink
744 
745 namespace std {
746 
747 /**
748  * @brief @c std::hash specialisation enabling @c vlink::Uint128 keys in unordered containers.
749  *
750  * @details
751  * The hash combines both 64-bit halves so @c std::unordered_map, @c std::unordered_set and
752  * similar containers can key on a @c vlink::Uint128 directly.
753  */
754 template <>
755 struct hash<vlink::Uint128> {
756  /**
757  * @brief Hashes @p value into a @c size_t.
758  *
759  * @param value Value to hash.
760  * @return Hash derived from both halves.
761  */
762  VLINK_EXPORT size_t operator()(const vlink::Uint128& value) const noexcept;
763 };
764 
765 } // namespace std
Cross-platform macros for visibility, branch hints, copy prevention, singletons and string helpers.
#define VLINK_EXPORT
Definition: macros.h:81