diff --git a/include/intx/intx.hpp b/include/intx/intx.hpp index 139fad4..d8b54a6 100644 --- a/include/intx/intx.hpp +++ b/include/intx/intx.hpp @@ -84,63 +84,10 @@ using builtin_uint128 = unsigned __int128; #pragma GCC diagnostic pop #endif + template struct uint; -/// The 128-bit unsigned integer. -/// -/// This type is defined as a specialization of uint<> to easier integration with full intx package, -/// however, uint128 may be used independently. -template <> -struct uint<128> -{ - using word_type = uint64_t; - static constexpr auto word_num_bits = sizeof(word_type) * 8; - static constexpr unsigned num_bits = 128; - static constexpr auto num_words = num_bits / word_num_bits; - -private: - uint64_t words_[2]{}; - -public: - constexpr uint() noexcept = default; - - constexpr uint(uint64_t low, uint64_t high) noexcept : words_{low, high} {} - - template - constexpr explicit(false) uint(T x) noexcept - requires std::is_convertible_v - : words_{static_cast(x), 0} - {} - -#if INTX_HAS_BUILTIN_INT128 - constexpr explicit(false) uint(builtin_uint128 x) noexcept - : words_{uint64_t(x), uint64_t(x >> 64)} - {} - - constexpr explicit operator builtin_uint128() const noexcept - { - return (builtin_uint128{words_[1]} << 64) | words_[0]; - } -#endif - - constexpr uint64_t& operator[](size_t i) noexcept { return words_[i]; } - constexpr const uint64_t& operator[](size_t i) const noexcept { return words_[i]; } - - constexpr explicit operator bool() const noexcept { return (words_[0] | words_[1]) != 0; } - - /// Explicit converting operator for all builtin integral types. - template - constexpr explicit operator Int() const noexcept - requires std::is_integral_v - { - return static_cast(words_[0]); - } -}; - -using uint128 = uint<128>; - - /// Contains result of add/sub/etc with a carry flag. template struct result_with_carry @@ -152,9 +99,17 @@ struct result_with_carry constexpr explicit(false) operator std::tuple() noexcept { return {value, carry}; } }; +template +struct div_result +{ + QuotT quot; + RemT rem; -/// Linear arithmetic operators. -/// @{ + bool operator==(const div_result&) const = default; + + /// Conversion to tuple of references, to allow usage with std::tie(). + constexpr explicit(false) operator std::tuple() noexcept { return {quot, rem}; } +}; /// Addition with carry. inline constexpr result_with_carry addc( @@ -189,7 +144,7 @@ inline constexpr result_with_carry addc( inline constexpr result_with_carry subc( uint64_t x, uint64_t y, bool carry = false) noexcept { - // Use __builtin_subcll if available (except buggy Xcode 14.3.1 on arm64). +// Use __builtin_subcll if available (except buggy Xcode 14.3.1 on arm64). #if __has_builtin(__builtin_subcll) && __apple_build_version__ != 14030022 if (!std::is_constant_evaluated()) { @@ -231,16 +186,6 @@ inline constexpr result_with_carry> addc( return {s, k}; } -inline constexpr uint128 operator+(uint128 x, uint128 y) noexcept -{ - return addc(x, y).value; -} - -inline constexpr uint128 operator+(uint128 x) noexcept -{ - return x; -} - /// Performs subtraction of two unsigned numbers and returns the difference /// and the carry bit (aka borrow, overflow). template @@ -258,176 +203,197 @@ inline constexpr result_with_carry> subc( return {z, k}; } -inline constexpr uint128 operator-(uint128 x, uint128 y) noexcept -{ - return subc(x, y).value; -} +constexpr uint<128> umul(uint64_t x, uint64_t y) noexcept; -inline constexpr uint128 operator-(uint128 x) noexcept -{ - // Implementing as subtraction is better than ~x + 1. - // Clang9: Perfect. - // GCC8: Does something weird. - return 0 - x; -} -inline constexpr uint128& operator++(uint128& x) noexcept +/// The 128-bit unsigned integer. +/// +/// This type is defined as a specialization of uint<> to easier integration with full intx package, +/// however, uint128 may be used independently. +template <> +struct uint<128> { - return x = x + 1; -} + using word_type = uint64_t; + static constexpr auto word_num_bits = sizeof(word_type) * 8; + static constexpr unsigned num_bits = 128; + static constexpr auto num_words = num_bits / word_num_bits; -inline constexpr uint128& operator--(uint128& x) noexcept -{ - return x = x - 1; -} +private: + uint64_t words_[2]{}; -inline constexpr const uint128 operator++(uint128& x, int) noexcept // NOLINT(*-const-return-type) -{ - const auto ret = x; - ++x; - return ret; -} +public: + constexpr uint() noexcept = default; -inline constexpr const uint128 operator--(uint128& x, int) noexcept // NOLINT(*-const-return-type) -{ - const auto ret = x; - --x; - return ret; -} + constexpr uint(uint64_t low, uint64_t high) noexcept : words_{low, high} {} + + template + constexpr explicit(false) uint(T x) noexcept + requires std::is_convertible_v + : words_{static_cast(x), 0} + {} -/// Optimized addition. -/// -/// This keeps the multiprecision addition until CodeGen so the pattern is not -/// broken during other optimizations. -inline constexpr uint128 fast_add(uint128 x, uint128 y) noexcept -{ #if INTX_HAS_BUILTIN_INT128 - return builtin_uint128{x} + builtin_uint128{y}; -#else - return x + y; // Fallback to generic addition. + constexpr explicit(false) uint(builtin_uint128 x) noexcept + : words_{uint64_t(x), uint64_t(x >> 64)} + {} + + constexpr explicit operator builtin_uint128() const noexcept + { + return (builtin_uint128{words_[1]} << 64) | words_[0]; + } #endif -} -/// @} + constexpr uint64_t& operator[](size_t i) noexcept { return words_[i]; } + constexpr const uint64_t& operator[](size_t i) const noexcept { return words_[i]; } + constexpr explicit operator bool() const noexcept { return (words_[0] | words_[1]) != 0; } -/// Comparison operators. -/// -/// In all implementations bitwise operators are used instead of logical ones -/// to avoid branching. -/// -/// @{ + /// Explicit converting operator for all builtin integral types. + template + constexpr explicit operator Int() const noexcept + requires std::is_integral_v + { + return static_cast(words_[0]); + } -inline constexpr bool operator==(uint128 x, uint128 y) noexcept -{ - return ((x[0] ^ y[0]) | (x[1] ^ y[1])) == 0; -} + friend constexpr uint operator+(uint x, uint y) noexcept { return addc(x, y).value; } -inline constexpr bool operator!=(uint128 x, uint128 y) noexcept -{ - return !(x == y); -} + constexpr uint operator+() const noexcept { return *this; } -inline constexpr bool operator<(uint128 x, uint128 y) noexcept -{ - // OPT: This should be implemented by checking the borrow of x - y, - // but compilers (GCC8, Clang7) - // have problem with properly optimizing subtraction. -#if INTX_HAS_BUILTIN_INT128 - return builtin_uint128{x} < builtin_uint128{y}; -#else - return (unsigned{x[1] < y[1]} | (unsigned{x[1] == y[1]} & unsigned{x[0] < y[0]})) != 0; -#endif -} + friend constexpr uint operator-(uint x, uint y) noexcept { return subc(x, y).value; } -inline constexpr bool operator<=(uint128 x, uint128 y) noexcept -{ - return !(y < x); -} + constexpr uint operator-() const noexcept + { + // Implementing as subtraction is better than ~x + 1. + // Clang9: Perfect. + // GCC8: Does something weird. + return 0 - *this; + } -inline constexpr bool operator>(uint128 x, uint128 y) noexcept -{ - return y < x; -} + constexpr uint& operator+=(uint y) noexcept { return *this = *this + y; } -inline constexpr bool operator>=(uint128 x, uint128 y) noexcept -{ - return !(x < y); -} + constexpr uint& operator-=(uint y) noexcept { return *this = *this - y; } -/// @} + constexpr uint& operator++() noexcept { return *this += 1; } + constexpr uint& operator--() noexcept { return *this -= 1; } -/// Bitwise operators. -/// @{ + constexpr const uint operator++(int) noexcept // NOLINT(*-const-return-type) + { + const auto ret = *this; + *this += 1; + return ret; + } -inline constexpr uint128 operator~(uint128 x) noexcept -{ - return {~x[0], ~x[1]}; -} + constexpr const uint operator--(int) noexcept // NOLINT(*-const-return-type) + { + const auto ret = *this; + *this -= 1; + return ret; + } -inline constexpr uint128 operator|(uint128 x, uint128 y) noexcept -{ - // Clang: perfect. - // GCC 8-12: stupidly uses a vector instruction in all bitwise operators. - return {x[0] | y[0], x[1] | y[1]}; -} + friend constexpr bool operator==(uint x, uint y) noexcept + { + return ((x[0] ^ y[0]) | (x[1] ^ y[1])) == 0; + } -inline constexpr uint128 operator&(uint128 x, uint128 y) noexcept -{ - return {x[0] & y[0], x[1] & y[1]}; -} + friend constexpr bool operator<(uint x, uint y) noexcept + { + // OPT: This should be implemented by checking the borrow of x - y, + // but compilers (GCC8, Clang7) + // have problem with properly optimizing subtraction. -inline constexpr uint128 operator^(uint128 x, uint128 y) noexcept -{ - return {x[0] ^ y[0], x[1] ^ y[1]}; -} +#if INTX_HAS_BUILTIN_INT128 + return builtin_uint128{x} < builtin_uint128{y}; +#else + return (unsigned{x[1] < y[1]} | (unsigned{x[1] == y[1]} & unsigned{x[0] < y[0]})) != 0; +#endif + } + friend constexpr bool operator<=(uint x, uint y) noexcept { return !(y < x); } + friend constexpr bool operator>(uint x, uint y) noexcept { return y < x; } + friend constexpr bool operator>=(uint x, uint y) noexcept { return !(x < y); } -inline constexpr uint128 operator<<(uint128 x, uint64_t shift) noexcept -{ - return (shift < 64) ? - // Find the part moved from lo to hi. - // For shift == 0 right shift by (64 - shift) is invalid so - // split it into 2 shifts by 1 and (63 - shift). - uint128{x[0] << shift, (x[1] << shift) | ((x[0] >> 1) >> (63 - shift))} : + friend constexpr uint operator~(uint x) noexcept { return {~x[0], ~x[1]}; } + friend constexpr uint operator|(uint x, uint y) noexcept { return {x[0] | y[0], x[1] | y[1]}; } + friend constexpr uint operator&(uint x, uint y) noexcept { return {x[0] & y[0], x[1] & y[1]}; } + friend constexpr uint operator^(uint x, uint y) noexcept { return {x[0] ^ y[0], x[1] ^ y[1]}; } - // Guarantee "defined" behavior for shifts larger than 128. - (shift < 128) ? uint128{0, x[0] << (shift - 64)} : 0; -} + friend constexpr uint operator<<(uint x, uint64_t shift) noexcept + { + return (shift < 64) ? + // Find the part moved from lo to hi. + // For shift == 0 right shift by (64 - shift) is invalid so + // split it into 2 shifts by 1 and (63 - shift). + uint{x[0] << shift, (x[1] << shift) | ((x[0] >> 1) >> (63 - shift))} : + + // Guarantee "defined" behavior for shifts larger than 128. + (shift < 128) ? uint{0, x[0] << (shift - 64)} : 0; + } -inline constexpr uint128 operator<<(uint128 x, uint128 shift) noexcept -{ - if (INTX_UNLIKELY(shift[1] != 0)) - return 0; + friend constexpr uint operator<<(uint x, uint shift) noexcept + { + if (shift[1] != 0) [[unlikely]] + return 0; - return x << shift[0]; -} + return x << shift[0]; + } -inline constexpr uint128 operator>>(uint128 x, uint64_t shift) noexcept -{ - return (shift < 64) ? - // Find the part moved from lo to hi. - // For shift == 0 left shift by (64 - shift) is invalid so - // split it into 2 shifts by 1 and (63 - shift). - uint128{(x[0] >> shift) | ((x[1] << 1) << (63 - shift)), x[1] >> shift} : + friend constexpr uint operator>>(uint x, uint64_t shift) noexcept + { + return (shift < 64) ? + // Find the part moved from lo to hi. + // For shift == 0 left shift by (64 - shift) is invalid so + // split it into 2 shifts by 1 and (63 - shift). + uint{(x[0] >> shift) | ((x[1] << 1) << (63 - shift)), x[1] >> shift} : + + // Guarantee "defined" behavior for shifts larger than 128. + (shift < 128) ? uint{x[1] >> (shift - 64)} : 0; + } - // Guarantee "defined" behavior for shifts larger than 128. - (shift < 128) ? uint128{x[1] >> (shift - 64)} : 0; -} + friend constexpr uint operator>>(uint x, uint shift) noexcept + { + if (shift[1] != 0) [[unlikely]] + return 0; -inline constexpr uint128 operator>>(uint128 x, uint128 shift) noexcept -{ - if (INTX_UNLIKELY(shift[1] != 0)) - return 0; + return x >> shift[0]; + } - return x >> static_cast(shift); -} + friend constexpr uint operator*(uint x, uint y) noexcept + { + auto p = umul(x[0], y[0]); + p[1] += (x[0] * y[1]) + (x[1] * y[0]); + return {p[0], p[1]}; + } -/// @} + friend constexpr div_result udivrem(uint x, uint y) noexcept; + friend constexpr uint operator/(uint x, uint y) noexcept { return udivrem(x, y).quot; } + friend constexpr uint operator%(uint x, uint y) noexcept { return udivrem(x, y).rem; } + + constexpr uint& operator*=(uint y) noexcept { return *this = *this * y; } + constexpr uint& operator|=(uint y) noexcept { return *this = *this | y; } + constexpr uint& operator&=(uint y) noexcept { return *this = *this & y; } + constexpr uint& operator^=(uint y) noexcept { return *this = *this ^ y; } + constexpr uint& operator<<=(uint64_t shift) noexcept { return *this = *this << shift; } + constexpr uint& operator>>=(uint64_t shift) noexcept { return *this = *this >> shift; } + constexpr uint& operator/=(uint y) noexcept { return *this = *this / y; } + constexpr uint& operator%=(uint y) noexcept { return *this = *this % y; } +}; +using uint128 = uint<128>; -/// Multiplication -/// @{ + +/// Optimized addition. +/// +/// This keeps the multiprecision addition until CodeGen so the pattern is not +/// broken during other optimizations. +inline constexpr uint128 fast_add(uint128 x, uint128 y) noexcept +{ +#if INTX_HAS_BUILTIN_INT128 + return builtin_uint128{x} + builtin_uint128{y}; +#else + return x + y; // Fallback to generic addition. +#endif +} /// Full unsigned multiplication 64 x 64 -> 128. inline constexpr uint128 umul(uint64_t x, uint64_t y) noexcept @@ -463,61 +429,6 @@ inline constexpr uint128 umul(uint64_t x, uint64_t y) noexcept return {lo, hi}; } -inline constexpr uint128 operator*(uint128 x, uint128 y) noexcept -{ - auto p = umul(x[0], y[0]); - p[1] += (x[0] * y[1]) + (x[1] * y[0]); - return {p[0], p[1]}; -} - -/// @} - - -/// Assignment operators. -/// @{ - -inline constexpr uint128& operator+=(uint128& x, uint128 y) noexcept -{ - return x = x + y; -} - -inline constexpr uint128& operator-=(uint128& x, uint128 y) noexcept -{ - return x = x - y; -} - -inline constexpr uint128& operator*=(uint128& x, uint128 y) noexcept -{ - return x = x * y; -} - -inline constexpr uint128& operator|=(uint128& x, uint128 y) noexcept -{ - return x = x | y; -} - -inline constexpr uint128& operator&=(uint128& x, uint128 y) noexcept -{ - return x = x & y; -} - -inline constexpr uint128& operator^=(uint128& x, uint128 y) noexcept -{ - return x = x ^ y; -} - -inline constexpr uint128& operator<<=(uint128& x, uint64_t shift) noexcept -{ - return x = x << shift; -} - -inline constexpr uint128& operator>>=(uint128& x, uint64_t shift) noexcept -{ - return x = x >> shift; -} - -/// @} - inline constexpr unsigned clz(std::unsigned_integral auto x) noexcept { return static_cast(std::countl_zero(x)); @@ -588,18 +499,6 @@ inline constexpr uint128 bswap(uint128 x) noexcept /// Division. /// @{ -template -struct div_result -{ - QuotT quot; - RemT rem; - - bool operator==(const div_result&) const = default; - - /// Conversion to tuple of references, to allow usage with std::tie(). - constexpr operator std::tuple() noexcept { return {quot, rem}; } -}; - namespace internal { /// Reciprocal lookup table. @@ -781,26 +680,6 @@ inline constexpr div_result sdivrem(uint128 x, uint128 y) noexcept return {q_is_neg ? -res.quot : res.quot, x_is_neg ? -res.rem : res.rem}; } -inline constexpr uint128 operator/(uint128 x, uint128 y) noexcept -{ - return udivrem(x, y).quot; -} - -inline constexpr uint128 operator%(uint128 x, uint128 y) noexcept -{ - return udivrem(x, y).rem; -} - -inline constexpr uint128& operator/=(uint128& x, uint128 y) noexcept -{ - return x = x / y; -} - -inline constexpr uint128& operator%=(uint128& x, uint128 y) noexcept -{ - return x = x % y; -} - /// @} } // namespace intx