From cc9d76dbb68cc4182aab72e79e80e4989613f7cd Mon Sep 17 00:00:00 2001 From: Diego Garcia Date: Thu, 9 Apr 2026 17:19:30 -0600 Subject: [PATCH 01/13] initial draft --- include/intx/intx.hpp | 372 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 370 insertions(+), 2 deletions(-) diff --git a/include/intx/intx.hpp b/include/intx/intx.hpp index 20967468..a326d0e5 100644 --- a/include/intx/intx.hpp +++ b/include/intx/intx.hpp @@ -22,6 +22,9 @@ #include #include #include +#if defined(__cpp_lib_unreachable) +#include // std::unreachable +#endif #ifdef _MSC_VER #pragma warning(push) @@ -63,10 +66,11 @@ namespace intx { /// Mark a possible code path as unreachable (invokes undefined behavior). -/// TODO(C++23): Use std::unreachable(). [[noreturn]] inline void unreachable() noexcept { -#if __has_builtin(__builtin_unreachable) +#if defined(__cpp_lib_unreachable) + std::unreachable(); +#elif __has_builtin(__builtin_unreachable) __builtin_unreachable(); #elif defined(_MSC_VER) __assume(false); @@ -79,6 +83,7 @@ namespace intx /// Alias for the compiler supported unsigned __int128 type. using builtin_uint128 = unsigned __int128; +using builtin_int128 = __int128; #pragma GCC diagnostic pop #endif @@ -1926,6 +1931,369 @@ inline void store(uint8_t* dst, const uint256& x) noexcept } // namespace be +// Signed types +template +struct sint : private uint { + using internal = uint; + using internal::word_type; + using internal::word_num_bits; + using internal::num_bits; + using internal::num_words; + +private: + using internal::words_; + +public: + constexpr sint() noexcept = default; + + // Implicit converting constructor for any smaller int type + template + constexpr explicit(false) sint(const sint& x) noexcept + requires(M < N) + { + for (size_t i = 0; i < sint::num_words; ++i) + words_[i] = x[i]; + } + +#if INTX_HAS_BUILTIN_INT128 + constexpr explicit(false) sint(builtin_int128 x) noexcept + : internal{uint64_t(x), uint64_t(x >> 64)} + {} +#endif + + template + constexpr explicit(false) sint(T... v) noexcept + requires std::conjunction_v...> + : internal{static_cast(v)...} + {} + + /// Constructs from words with words[0] being the least significant word. + /// The size of the span must be less than or equal to num_words. + constexpr explicit sint(std::span words) noexcept + { + INTX_REQUIRE(words.size() <= num_words); + std::ranges::copy(words, words_); + } + + // This is just to get each words, it won't mean much by themselves since it's + // part of a signed type + 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 *this != sint{}; } + + /// Explicit converting operator to smaller sint types. + template + constexpr explicit operator sint() const noexcept + requires(M < N) + { + sint r; + for (size_t i = 0; i < sint::num_words; ++i) + r[i] = words_[i]; + return r; + } + + /// Explicit converting operator for all builtin integral types. + template + constexpr explicit operator Int() const noexcept + requires(std::is_integral_v) + { + static_assert(sizeof(Int) <= sizeof(uint64_t)); + return static_cast(words_[0]); + } + + constexpr sint& operator=(uint64_t v) noexcept + { + words_[0] = v; + for (size_t i = 1; i < num_words; ++i) + words_[i] = 0; + return *this; + } + + template + constexpr sint& operator=(const sint& x) noexcept + requires(M <= N) + { + for (size_t i = 0; i < sint::num_words; ++i) + words_[i] = x[i]; + for (size_t i = sint::num_words; i < num_words; ++i) + words_[i] = 0; + return *this; + } + + friend constexpr sint operator+(const sint& x, const sint& y) noexcept + { + static_assert(false, "TODO"); + // return addc(x, y).value; + } + + constexpr sint& operator+=(const sint& y) noexcept { return *this = *this + y; } + + constexpr sint operator-() const noexcept { return ~*this + sint{1}; } + + friend constexpr sint operator-(const sint& x, const sint& y) noexcept + { + static_assert(false, "TODO"); + // return subc(x, y).value; + } + + constexpr sint& operator-=(const sint& y) noexcept { return *this = *this - y; } + + /// Multiplication implementation using word access + /// and discarding the high part of the result product. + friend constexpr sint operator*(const sint& x, const sint& y) noexcept + { + static_assert(false, "TODO"); + + // uint p; + // for (size_t j = 0; j < num_words; j++) + // { + // uint64_t k = 0; + // for (size_t i = 0; i < (num_words - j - 1); i++) + // { + // auto a = addc(p[i + j], k); + // auto t = umul(x[i], y[j]) + uint128{a.value, a.carry}; + // p[i + j] = t[0]; + // k = t[1]; + // } + // p[num_words - 1] += x[num_words - j - 1] * y[j] + k; + // } + // return p; + } + + constexpr sint& operator*=(const sint& y) noexcept { return *this = *this * y; } + + friend constexpr sint operator/(const sint& x, const sint& y) noexcept + { + static_assert(false, "TODO"); + // return udivrem(x, y).quot; + } + + friend constexpr sint operator%(const sint& x, const sint& y) noexcept + { + static_assert(false, "TODO"); + // return udivrem(x, y).rem; + } + + constexpr sint& operator/=(const sint& y) noexcept { return *this = *this / y; } + + constexpr sint& operator%=(const sint& y) noexcept { return *this = *this % y; } + + + constexpr sint operator~() const noexcept + { + uint z; + for (size_t i = 0; i < num_words; ++i) + z[i] = ~words_[i]; + return z; + } + + friend constexpr sint operator|(const sint& x, const sint& y) noexcept + { + uint z; + for (size_t i = 0; i < num_words; ++i) + z[i] = x[i] | y[i]; + return z; + } + + constexpr sint& operator|=(const sint& y) noexcept { return *this = *this | y; } + + friend constexpr sint operator&(const sint& x, const sint& y) noexcept + { + uint z; + for (size_t i = 0; i < num_words; ++i) + z[i] = x[i] & y[i]; + return z; + } + + constexpr sint& operator&=(const sint& y) noexcept { return *this = *this & y; } + + friend constexpr sint operator^(const sint& x, const sint& y) noexcept + { + uint z; + for (size_t i = 0; i < num_words; ++i) + z[i] = x[i] ^ y[i]; + return z; + } + + constexpr sint& operator^=(const sint& y) noexcept { return *this = *this ^ y; } + + friend constexpr bool operator==(const sint& x, const sint& y) noexcept + { + uint64_t folded = 0; + for (size_t i = 0; i < num_words; ++i) + folded |= (x[i] ^ y[i]); + return folded == 0; + } + + friend constexpr bool operator<(const sint& x, const sint& y) noexcept + { + static_assert(false, "TODO"); + // if constexpr (N == 256) + // { + // auto xp = uint128{x[2], x[3]}; + // auto yp = uint128{y[2], y[3]}; + // if (xp == yp) + // { + // xp = uint128{x[0], x[1]}; + // yp = uint128{y[0], y[1]}; + // } + // return xp < yp; + // } + // else + // return subc(x, y).carry; + } + friend constexpr bool operator>(const sint& x, const sint& y) noexcept { return y < x; } + friend constexpr bool operator>=(const sint& x, const sint& y) noexcept { return !(x < y); } + friend constexpr bool operator<=(const sint& x, const sint& y) noexcept { return !(y < x); } + + friend constexpr std::strong_ordering operator<=>(const sint& x, const sint& y) noexcept + { + if (x == y) + return std::strong_ordering::equal; + + return (x < y) ? std::strong_ordering::less : std::strong_ordering::greater; + } + + friend constexpr sint operator<<(const sint& x, uint64_t shift) noexcept + { + if (shift >= num_bits) [[unlikely]] + return 0; + + if constexpr (N == 256) + { + constexpr auto half_bits = num_bits / 2; + + const auto xlo = uint128{x[0], x[1]}; + + if (shift < half_bits) + { + const auto lo = xlo << shift; + + const auto xhi = uint128{x[2], x[3]}; + + // Find the part moved from lo to hi. + // The shift right here can be invalid: + // for shift == 0 => rshift == half_bits. + // Split it into 2 valid shifts by (rshift - 1) and 1. + const auto rshift = half_bits - shift; + const auto lo_overflow = (xlo >> (rshift - 1)) >> 1; + const auto hi = (xhi << shift) | lo_overflow; + return {lo[0], lo[1], hi[0], hi[1]}; + } + + const auto hi = xlo << (shift - half_bits); + return {0, 0, hi[0], hi[1]}; + } + else + { + constexpr auto word_bits = sizeof(uint64_t) * 8; + + const auto s = shift % word_bits; + const auto skip = static_cast(shift / word_bits); + + sint r; + uint64_t carry = 0; + for (size_t i = 0; i < (num_words - skip); ++i) + { + r[i + skip] = (x[i] << s) | carry; + carry = (x[i] >> (word_bits - s - 1)) >> 1; + } + return r; + } + } + + friend constexpr sint operator<<(const sint& x, std::integral auto shift) noexcept + { + static_assert(sizeof(shift) <= sizeof(uint64_t)); + return x << static_cast(shift); + } + + friend constexpr sint operator<<(const sint& x, const sint& shift) noexcept + { + // TODO: This optimisation should be handled by operator<. + uint64_t high_words_fold = 0; + for (size_t i = 1; i < num_words; ++i) + high_words_fold |= shift[i]; + + if (high_words_fold != 0) [[unlikely]] + return 0; + + return x << shift[0]; + } + + friend constexpr sint operator>>(const sint& x, uint64_t shift) noexcept + { + if (shift >= num_bits) [[unlikely]] + return 0; + + if constexpr (N == 256) + { + constexpr auto half_bits = num_bits / 2; + + const auto xhi = uint128{x[2], x[3]}; + + if (shift < half_bits) + { + const auto hi = xhi >> shift; + + const auto xlo = uint128{x[0], x[1]}; + + // Find the part moved from hi to lo. + // The shift left here can be invalid: + // for shift == 0 => lshift == half_bits. + // Split it into 2 valid shifts by (lshift - 1) and 1. + const auto lshift = half_bits - shift; + const auto hi_overflow = (xhi << (lshift - 1)) << 1; + const auto lo = (xlo >> shift) | hi_overflow; + return {lo[0], lo[1], hi[0], hi[1]}; + } + + const auto lo = xhi >> (shift - half_bits); + return {lo[0], lo[1], 0, 0}; + } + else + { + constexpr auto word_bits = sizeof(uint64_t) * 8; + + const auto s = shift % word_bits; + const auto skip = static_cast(shift / word_bits); + + sint r; + uint64_t carry = 0; + for (size_t i = 0; i < (num_words - skip); ++i) + { + r[num_words - 1 - i - skip] = (x[num_words - 1 - i] >> s) | carry; + carry = (x[num_words - 1 - i] << (word_bits - s - 1)) << 1; + } + return r; + } + } + + friend constexpr sint operator>>(const sint& x, std::integral auto shift) noexcept + { + static_assert(sizeof(shift) <= sizeof(uint64_t)); + return x >> static_cast(shift); + } + + friend constexpr sint operator>>(const sint& x, const sint& shift) noexcept + { + // TODO check if correct + uint64_t high_words_fold = 0; + for (size_t i = 1; i < num_words; ++i) + high_words_fold |= shift[i]; + + if (high_words_fold != 0) [[unlikely]] + return 0; + + return x >> shift[0]; + } + + constexpr sint& operator<<=(sint shift) noexcept { return *this = *this << shift; } + constexpr sint& operator>>=(sint shift) noexcept { return *this = *this >> shift; } +}; + } // namespace intx #ifdef _MSC_VER From fb709e4637ea595871515d17cde441911dced865 Mon Sep 17 00:00:00 2001 From: Diego Garcia Date: Thu, 9 Apr 2026 17:21:39 -0600 Subject: [PATCH 02/13] add using for int128 --- include/intx/intx.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/intx/intx.hpp b/include/intx/intx.hpp index a326d0e5..09dcdf8d 100644 --- a/include/intx/intx.hpp +++ b/include/intx/intx.hpp @@ -2293,6 +2293,7 @@ struct sint : private uint { constexpr sint& operator<<=(sint shift) noexcept { return *this = *this << shift; } constexpr sint& operator>>=(sint shift) noexcept { return *this = *this >> shift; } }; +using int128 = sint<128>; } // namespace intx From 94bb564275f29a8afdcde8c06ef476d763558c41 Mon Sep 17 00:00:00 2001 From: Diego Garcia Date: Fri, 10 Apr 2026 00:19:03 -0600 Subject: [PATCH 03/13] add method implementations --- include/intx/intx.hpp | 128 +++++++++++++++--------------------------- 1 file changed, 46 insertions(+), 82 deletions(-) diff --git a/include/intx/intx.hpp b/include/intx/intx.hpp index 09dcdf8d..76606be4 100644 --- a/include/intx/intx.hpp +++ b/include/intx/intx.hpp @@ -1943,6 +1943,8 @@ struct sint : private uint { private: using internal::words_; + constexpr explicit sint(const internal& u) noexcept : internal{u} {} + public: constexpr sint() noexcept = default; @@ -2024,8 +2026,7 @@ struct sint : private uint { friend constexpr sint operator+(const sint& x, const sint& y) noexcept { - static_assert(false, "TODO"); - // return addc(x, y).value; + return sint{addc(static_cast(x), static_cast(y)).value}; } constexpr sint& operator+=(const sint& y) noexcept { return *this = *this + y; } @@ -2034,46 +2035,29 @@ struct sint : private uint { friend constexpr sint operator-(const sint& x, const sint& y) noexcept { - static_assert(false, "TODO"); - // return subc(x, y).value; + return sint{subc(static_cast(x), static_cast(y)).value}; } constexpr sint& operator-=(const sint& y) noexcept { return *this = *this - y; } /// Multiplication implementation using word access /// and discarding the high part of the result product. + /// For two's complement, the low N bits of the product are the same as unsigned. friend constexpr sint operator*(const sint& x, const sint& y) noexcept { - static_assert(false, "TODO"); - - // uint p; - // for (size_t j = 0; j < num_words; j++) - // { - // uint64_t k = 0; - // for (size_t i = 0; i < (num_words - j - 1); i++) - // { - // auto a = addc(p[i + j], k); - // auto t = umul(x[i], y[j]) + uint128{a.value, a.carry}; - // p[i + j] = t[0]; - // k = t[1]; - // } - // p[num_words - 1] += x[num_words - j - 1] * y[j] + k; - // } - // return p; + return sint{static_cast(x) * static_cast(y)}; } constexpr sint& operator*=(const sint& y) noexcept { return *this = *this * y; } friend constexpr sint operator/(const sint& x, const sint& y) noexcept { - static_assert(false, "TODO"); - // return udivrem(x, y).quot; + return sint{sdivrem(static_cast(x), static_cast(y)).quot}; } friend constexpr sint operator%(const sint& x, const sint& y) noexcept { - static_assert(false, "TODO"); - // return udivrem(x, y).rem; + return sint{sdivrem(static_cast(x), static_cast(y)).rem}; } constexpr sint& operator/=(const sint& y) noexcept { return *this = *this / y; } @@ -2083,7 +2067,7 @@ struct sint : private uint { constexpr sint operator~() const noexcept { - uint z; + sint z; for (size_t i = 0; i < num_words; ++i) z[i] = ~words_[i]; return z; @@ -2091,7 +2075,7 @@ struct sint : private uint { friend constexpr sint operator|(const sint& x, const sint& y) noexcept { - uint z; + sint z; for (size_t i = 0; i < num_words; ++i) z[i] = x[i] | y[i]; return z; @@ -2101,7 +2085,7 @@ struct sint : private uint { friend constexpr sint operator&(const sint& x, const sint& y) noexcept { - uint z; + sint z; for (size_t i = 0; i < num_words; ++i) z[i] = x[i] & y[i]; return z; @@ -2111,7 +2095,7 @@ struct sint : private uint { friend constexpr sint operator^(const sint& x, const sint& y) noexcept { - uint z; + sint z; for (size_t i = 0; i < num_words; ++i) z[i] = x[i] ^ y[i]; return z; @@ -2129,20 +2113,7 @@ struct sint : private uint { friend constexpr bool operator<(const sint& x, const sint& y) noexcept { - static_assert(false, "TODO"); - // if constexpr (N == 256) - // { - // auto xp = uint128{x[2], x[3]}; - // auto yp = uint128{y[2], y[3]}; - // if (xp == yp) - // { - // xp = uint128{x[0], x[1]}; - // yp = uint128{y[0], y[1]}; - // } - // return xp < yp; - // } - // else - // return subc(x, y).carry; + return slt(static_cast(x), static_cast(y)); } friend constexpr bool operator>(const sint& x, const sint& y) noexcept { return y < x; } friend constexpr bool operator>=(const sint& x, const sint& y) noexcept { return !(x < y); } @@ -2225,50 +2196,36 @@ struct sint : private uint { friend constexpr sint operator>>(const sint& x, uint64_t shift) noexcept { - if (shift >= num_bits) [[unlikely]] - return 0; + constexpr auto word_bits = sizeof(uint64_t) * 8; + // Replicate the sign bit across a full word: 0 for positive, all-ones for negative. + const auto sign_fill = -(x[num_words - 1] >> (word_bits - 1)); - if constexpr (N == 256) + if (shift >= num_bits) [[unlikely]] { - constexpr auto half_bits = num_bits / 2; - - const auto xhi = uint128{x[2], x[3]}; - - if (shift < half_bits) - { - const auto hi = xhi >> shift; - - const auto xlo = uint128{x[0], x[1]}; - - // Find the part moved from hi to lo. - // The shift left here can be invalid: - // for shift == 0 => lshift == half_bits. - // Split it into 2 valid shifts by (lshift - 1) and 1. - const auto lshift = half_bits - shift; - const auto hi_overflow = (xhi << (lshift - 1)) << 1; - const auto lo = (xlo >> shift) | hi_overflow; - return {lo[0], lo[1], hi[0], hi[1]}; - } - - const auto lo = xhi >> (shift - half_bits); - return {lo[0], lo[1], 0, 0}; + sint r; + for (size_t i = 0; i < num_words; ++i) + r[i] = sign_fill; + return r; } - else - { - constexpr auto word_bits = sizeof(uint64_t) * 8; - const auto s = shift % word_bits; - const auto skip = static_cast(shift / word_bits); + const auto s = shift % word_bits; + const auto skip = static_cast(shift / word_bits); - sint r; - uint64_t carry = 0; - for (size_t i = 0; i < (num_words - skip); ++i) - { - r[num_words - 1 - i - skip] = (x[num_words - 1 - i] >> s) | carry; - carry = (x[num_words - 1 - i] << (word_bits - s - 1)) << 1; - } - return r; + sint r; + // For s > 0: prime the carry with sign bits so the top s bits of the MSW are filled. + // For s == 0: no sub-word carry is needed (sign_fill << word_bits would be UB). + uint64_t carry = s != 0 ? (sign_fill << (word_bits - s)) : 0; + for (size_t i = 0; i < (num_words - skip); ++i) + { + r[num_words - 1 - i - skip] = (x[num_words - 1 - i] >> s) | carry; + // The trick (x << (word_bits - s - 1)) << 1 avoids UB when s == 0 + // (equivalent to x << word_bits, which yields 0). + carry = (x[num_words - 1 - i] << (word_bits - s - 1)) << 1; } + // Fill the vacated high words with the sign. + for (size_t i = num_words - skip; i < num_words; ++i) + r[i] = sign_fill; + return r; } friend constexpr sint operator>>(const sint& x, std::integral auto shift) noexcept @@ -2279,13 +2236,20 @@ struct sint : private uint { friend constexpr sint operator>>(const sint& x, const sint& shift) noexcept { - // TODO check if correct uint64_t high_words_fold = 0; for (size_t i = 1; i < num_words; ++i) high_words_fold |= shift[i]; if (high_words_fold != 0) [[unlikely]] - return 0; + { + // Shift amount >= 2^64 >= num_bits: result is all sign bits. + constexpr auto word_bits = sizeof(uint64_t) * 8; + const auto sign_fill = -(x[num_words - 1] >> (word_bits - 1)); + sint r; + for (size_t i = 0; i < num_words; ++i) + r[i] = sign_fill; + return r; + } return x >> shift[0]; } From 99da772b7206924995ae156d6dc4ba7d238d25b2 Mon Sep 17 00:00:00 2001 From: Diego Garcia Date: Fri, 10 Apr 2026 13:03:10 -0600 Subject: [PATCH 04/13] add unit tests and benchmarks for signed integers --- include/intx/intx.hpp | 38 +++- test/benchmarks/CMakeLists.txt | 1 + test/benchmarks/bench_sint128.cpp | 112 +++++++++++ test/benchmarks/benchmarks.cpp | 3 + test/unittests/CMakeLists.txt | 1 + test/unittests/test_sint.cpp | 316 ++++++++++++++++++++++++++++++ test/unittests/test_suite.hpp | 18 ++ 7 files changed, 486 insertions(+), 3 deletions(-) create mode 100644 test/benchmarks/bench_sint128.cpp create mode 100644 test/unittests/test_sint.cpp diff --git a/include/intx/intx.hpp b/include/intx/intx.hpp index 76606be4..c41ce22e 100644 --- a/include/intx/intx.hpp +++ b/include/intx/intx.hpp @@ -220,7 +220,7 @@ struct uint<128> static constexpr unsigned num_bits = 128; static constexpr auto num_words = num_bits / word_num_bits; -private: +protected: uint64_t words_[2]{}; public: @@ -881,6 +881,7 @@ inline std::string to_string(uint x, int base = 10) return s; } + template inline std::string hex(uint x) { @@ -898,7 +899,7 @@ struct uint static_assert(N >= 2 * word_num_bits, "Number of bits must be at lest 128"); static_assert(N % word_num_bits == 0, "Number of bits must be a multiply of 64"); -private: +protected: uint64_t words_[num_words]{}; public: @@ -1935,7 +1936,7 @@ inline void store(uint8_t* dst, const uint256& x) noexcept template struct sint : private uint { using internal = uint; - using internal::word_type; + using typename internal::word_type; using internal::word_num_bits; using internal::num_bits; using internal::num_words; @@ -2258,6 +2259,37 @@ struct sint : private uint { constexpr sint& operator>>=(sint shift) noexcept { return *this = *this >> shift; } }; using int128 = sint<128>; +using int256 = sint<256>; +using int512 = sint<512>; + +template +inline std::string to_string(sint x, int base = 10) +{ + // Handle the positive case and zero directly by converting to uint + if (x >= 0) + { + uint ux; + for (size_t i = 0; i < sint::num_words; ++i) + ux[i] = x[i]; + return to_string(ux, base); + } + + // For negative numbers, calculate the absolute value magnitude. + // In two's complement, -x for sint::min() results in the same bit pattern, + // which correctly represents the magnitude 2^(N-1) when stored in uint. + auto abs_x = -x; + uint ux; + for (size_t i = 0; i < sint::num_words; ++i) + ux[i] = abs_x[i]; + + return "-" + to_string(ux, base); +} + +template +inline std::string hex(sint x) +{ + return to_string(x, 16); +} } // namespace intx diff --git a/test/benchmarks/CMakeLists.txt b/test/benchmarks/CMakeLists.txt index f605eb0b..1a9e3734 100644 --- a/test/benchmarks/CMakeLists.txt +++ b/test/benchmarks/CMakeLists.txt @@ -11,6 +11,7 @@ add_executable(intx-bench ../experimental/addmod.hpp bench_div.cpp bench_int128.cpp + bench_sint128.cpp benchmarks.cpp ) target_link_libraries(intx-bench PRIVATE intx intx::experimental intx::testutils benchmark::benchmark GMP::gmp) diff --git a/test/benchmarks/bench_sint128.cpp b/test/benchmarks/bench_sint128.cpp new file mode 100644 index 00000000..6d4364a4 --- /dev/null +++ b/test/benchmarks/bench_sint128.cpp @@ -0,0 +1,112 @@ +// intx: extended precision integer library. +// Copyright 2019-2020 Pawel Bylica. +// Licensed under the Apache License, Version 2.0. + +#include +#include +#include +#include + +using namespace intx; + +namespace +{ +#if INTX_HAS_BUILTIN_INT128 +inline div_result gcc_(int128 x, int128 y) noexcept +{ + const auto bx = (static_cast(static_cast(x[1])) << 64) | x[0]; + const auto by = (static_cast(static_cast(y[1])) << 64) | y[0]; + return {int128{bx / by}, 0}; +} +#endif + +inline div_result gmp_(int128 x, int128 y) noexcept +{ + const auto x_is_neg = x < 0; + const auto y_is_neg = y < 0; + const auto x_abs = x_is_neg ? -x : x; + const auto y_abs = y_is_neg ? -y : y; + + // Convert to uint128 to use gmp::udivrem + const auto res = gmp::udivrem(uint128{x_abs[0], x_abs[1]}, uint128{y_abs[0], y_abs[1]}); + + const auto q_is_neg = x_is_neg ^ y_is_neg; + const int128 q{res.quot[0], res.quot[1]}; + const int128 r{res.rem[0], res.rem[1]}; + + return {q_is_neg ? -q : q, x_is_neg ? -r : r}; +} + +[[gnu::noinline]] auto intx_(int128 x, int128 y) noexcept +{ + return div_result{x / y, x % y}; +} + +template +void sdiv128(benchmark::State& state) +{ + int128 inputs[][2] = { + {{0x537e3fbc5318dbc0e7e47d96b32ef2d5_u128[0], 0x537e3fbc5318dbc0e7e47d96b32ef2d5_u128[1]}, + {0x395df916dfd1b5e38ae7c47ce8a620f_u128[0], 0x395df916dfd1b5e38ae7c47ce8a620f_u128[1]}}, + {-int128{0x837e3fbc5318dbc0e7e47d96b32ef2d5_u128[0], 0x837e3fbc5318dbc0e7e47d96b32ef2d5_u128[1]}, + {0x895df916dfd1b5e38ae7c47ce8a620f_u128[0], 0x895df916dfd1b5e38ae7c47ce8a620f_u128[1]}}, + {{0xee657725ff64cd48b8fe188a09dc4f78_u128[0], 0xee657725ff64cd48b8fe188a09dc4f78_u128[1]}, -3}, // worst shift + {-int128{0x0e657725ff64cd48b8fe188a09dc4f78_u128[0], 0x0e657725ff64cd48b8fe188a09dc4f78_u128[1]}, + {0xe7e47d96b32ef2d5}}, // single long normalized + {{0x0e657725ff64cd48b8fe188a09dc4f78_u128[0], 0x0e657725ff64cd48b8fe188a09dc4f78_u128[1]}, + -int128{0x77e47d96b32ef2d5}}, // single long + }; + benchmark::DoNotOptimize(inputs); + benchmark::ClobberMemory(); + + const auto idx = static_cast(state.range(0)); + int128 x = inputs[idx][0]; + int128 y = inputs[idx][1]; + benchmark::DoNotOptimize(x); + benchmark::DoNotOptimize(y); + + for ([[maybe_unused]] auto _ : state) + { + auto q = DivFn(x, y); + benchmark::DoNotOptimize(q); + } +} +#if INTX_HAS_BUILTIN_INT128 +BENCHMARK(sdiv128)->DenseRange(0, 4); +#endif +BENCHMARK(sdiv128)->DenseRange(0, 4); +BENCHMARK(sdiv128)->DenseRange(0, 4); + + +template +void smul128(benchmark::State& state) +{ + const auto inputs = test::gen_uniform_seq(1000); + benchmark::ClobberMemory(); + + while (state.KeepRunningBatch(static_cast(inputs.size()))) + { + uint64_t alo = 0; + uint64_t ahi = 0; + for (size_t i = 0; i < inputs.size() - 1; ++i) + { + auto p = MulFn(static_cast(inputs[i]), static_cast(inputs[i + 1])); + alo ^= p[0]; + ahi ^= p[1]; + } + benchmark::DoNotOptimize(alo); + benchmark::DoNotOptimize(ahi); + } +} + +inline int128 smul(int64_t x, int64_t y) noexcept +{ +#if INTX_HAS_BUILTIN_INT128 + return int128{static_cast(x) * static_cast(y)}; +#else + return int128{x} * int128{y}; +#endif +} + +BENCHMARK(smul128); +} // namespace diff --git a/test/benchmarks/benchmarks.cpp b/test/benchmarks/benchmarks.cpp index 4297930e..9a0cbfba 100644 --- a/test/benchmarks/benchmarks.cpp +++ b/test/benchmarks/benchmarks.cpp @@ -576,6 +576,9 @@ void to_string(benchmark::State& state) BENCHMARK(to_string); BENCHMARK(to_string); BENCHMARK(to_string); +BENCHMARK(to_string); +BENCHMARK(to_string); +BENCHMARK(to_string); template diff --git a/test/unittests/CMakeLists.txt b/test/unittests/CMakeLists.txt index 7f8d4e2e..6c657a07 100644 --- a/test/unittests/CMakeLists.txt +++ b/test/unittests/CMakeLists.txt @@ -15,6 +15,7 @@ add_executable(intx-unittests test_int128.cpp test_intx.cpp test_intx_api.cpp + test_sint.cpp test_suite.hpp test_uint256.cpp ) diff --git a/test/unittests/test_sint.cpp b/test/unittests/test_sint.cpp new file mode 100644 index 00000000..d16da679 --- /dev/null +++ b/test/unittests/test_sint.cpp @@ -0,0 +1,316 @@ +// intx: extended precision integer library. +// Copyright 2019 Pawel Bylica. +// Licensed under the Apache License, Version 2.0. + +#include "test_suite.hpp" + +using namespace intx; + +TYPED_TEST(sint_test, comparison) { + // Signed order: min < ... < -1 < 0 < 1 < ... < max + constexpr auto zero = TypeParam{}; + constexpr auto one = TypeParam{1}; + constexpr auto neg_one = -TypeParam{1}; + constexpr auto min = TypeParam{1} + << (TypeParam::num_bits - 1); // 100...0, most negative + constexpr auto max = ~min; // 011...1, most positive + + EXPECT_EQ(zero, zero); + EXPECT_EQ(one, one); + EXPECT_EQ(neg_one, neg_one); + EXPECT_EQ(min, min); + EXPECT_EQ(max, max); + + EXPECT_NE(zero, one); + EXPECT_NE(zero, neg_one); + EXPECT_NE(min, max); + + // Negative values are less than positive/zero + EXPECT_LT(min, neg_one); + EXPECT_LT(min, zero); + EXPECT_LT(min, one); + EXPECT_LT(min, max); + EXPECT_LT(neg_one, zero); + EXPECT_LT(neg_one, one); + EXPECT_LT(neg_one, max); + EXPECT_LT(zero, one); + EXPECT_LT(zero, max); + EXPECT_LT(one, max); + + EXPECT_GT(max, one); + EXPECT_GT(max, zero); + EXPECT_GT(max, neg_one); + EXPECT_GT(max, min); + EXPECT_GT(one, zero); + EXPECT_GT(one, neg_one); + EXPECT_GT(zero, neg_one); + EXPECT_GT(neg_one, min); + + EXPECT_LE(min, min); + EXPECT_LE(min, zero); + EXPECT_LE(neg_one, neg_one); + EXPECT_LE(neg_one, zero); + EXPECT_LE(zero, zero); + EXPECT_LE(zero, one); + EXPECT_LE(max, max); + + EXPECT_GE(max, max); + EXPECT_GE(max, zero); + EXPECT_GE(one, zero); + EXPECT_GE(zero, zero); + EXPECT_GE(zero, neg_one); + EXPECT_GE(neg_one, neg_one); + EXPECT_GE(neg_one, min); + EXPECT_GE(min, min); +} + +TYPED_TEST(sint_test, negation_overflow) { + // -INT_MIN == INT_MIN (two's complement overflow) + constexpr auto min = TypeParam{1} << (TypeParam::num_bits - 1); + EXPECT_EQ(-min, min); + + EXPECT_EQ(-TypeParam{}, TypeParam{}); + EXPECT_EQ(-(-TypeParam{1}), TypeParam{1}); +} + +TYPED_TEST(sint_test, arithmetic) { + constexpr auto zero = TypeParam{}; + constexpr auto one = TypeParam{1}; + constexpr auto neg_one = -TypeParam{1}; + constexpr auto min = TypeParam{1} << (TypeParam::num_bits - 1); + constexpr auto max = ~min; + + EXPECT_EQ(zero + zero, zero); + EXPECT_EQ(one + zero, one); + EXPECT_EQ(neg_one + one, zero); + EXPECT_EQ(neg_one + neg_one, -TypeParam{2}); + EXPECT_EQ(max + one, min); // INT_MAX + 1 wraps to INT_MIN + + EXPECT_EQ(zero - zero, zero); + EXPECT_EQ(one - one, zero); + EXPECT_EQ(zero - one, neg_one); + EXPECT_EQ(neg_one - neg_one, zero); + EXPECT_EQ(min - one, max); // INT_MIN - 1 wraps to INT_MAX + + EXPECT_EQ(zero * one, zero); + EXPECT_EQ(one * one, one); + EXPECT_EQ(neg_one * one, neg_one); + EXPECT_EQ(neg_one * neg_one, one); + EXPECT_EQ(TypeParam{3} * neg_one, -TypeParam{3}); + EXPECT_EQ(-TypeParam{3} * TypeParam{2}, -TypeParam{6}); +} + +TYPED_TEST(sint_test, assignment_operators) { + auto x = TypeParam{5}; + x += TypeParam{3}; + EXPECT_EQ(x, TypeParam{8}); + x -= TypeParam{3}; + EXPECT_EQ(x, TypeParam{5}); + x *= TypeParam{2}; + EXPECT_EQ(x, TypeParam{10}); + x /= TypeParam{3}; + EXPECT_EQ(x, TypeParam{3}); + x %= TypeParam{2}; + EXPECT_EQ(x, TypeParam{1}); + + auto y = TypeParam{3}; + y |= TypeParam{5}; + EXPECT_EQ(y, TypeParam{7}); + y &= TypeParam{5}; + EXPECT_EQ(y, TypeParam{5}); + y ^= TypeParam{3}; + EXPECT_EQ(y, TypeParam{6}); + + auto z = TypeParam{2}; + z <<= TypeParam{2}; + EXPECT_EQ(z, TypeParam{8}); + z >>= TypeParam{1}; + EXPECT_EQ(z, TypeParam{4}); + + // Arithmetic right shift via assignment + auto w = -TypeParam{8}; + w >>= TypeParam{1}; + EXPECT_EQ(w, -TypeParam{4}); +} + +TYPED_TEST(sint_test, division) { + // Truncation toward zero + EXPECT_EQ(TypeParam{7} / TypeParam{3}, TypeParam{2}); + EXPECT_EQ(TypeParam{7} % TypeParam{3}, TypeParam{1}); + EXPECT_EQ(-TypeParam{7} / TypeParam{3}, -TypeParam{2}); + EXPECT_EQ(-TypeParam{7} % TypeParam{3}, -TypeParam{1}); + EXPECT_EQ(TypeParam{7} / -TypeParam{3}, -TypeParam{2}); + EXPECT_EQ(TypeParam{7} % -TypeParam{3}, TypeParam{1}); + EXPECT_EQ(-TypeParam{7} / -TypeParam{3}, TypeParam{2}); + EXPECT_EQ(-TypeParam{7} % -TypeParam{3}, -TypeParam{1}); + + EXPECT_EQ(TypeParam{42} / TypeParam{1}, TypeParam{42}); + EXPECT_EQ(-TypeParam{42} / TypeParam{1}, -TypeParam{42}); + EXPECT_EQ(TypeParam{42} % TypeParam{42}, TypeParam{}); + + EXPECT_EQ(TypeParam{} / TypeParam{7}, TypeParam{}); + EXPECT_EQ(TypeParam{} % TypeParam{7}, TypeParam{}); + + EXPECT_EQ(TypeParam{3} / TypeParam{7}, TypeParam{}); + EXPECT_EQ(-TypeParam{3} / TypeParam{7}, TypeParam{}); + EXPECT_EQ(TypeParam{3} % TypeParam{7}, TypeParam{3}); + EXPECT_EQ(-TypeParam{3} % TypeParam{7}, -TypeParam{3}); +} + +TYPED_TEST(sint_test, arithmetic_right_shift) { + constexpr auto neg_one = ~TypeParam{}; // -1, all ones + constexpr auto min = TypeParam{1} << (TypeParam::num_bits - 1); // 100...0 + constexpr auto min_shr1 = TypeParam{3} << (TypeParam::num_bits - 2); // 110...0 + + // Negative: fill with 1s + EXPECT_EQ(neg_one >> 1u, neg_one); + EXPECT_EQ(neg_one >> uint64_t{TypeParam::num_bits - 1}, neg_one); + EXPECT_EQ(min >> 1u, min_shr1); + EXPECT_EQ(min >> uint64_t{TypeParam::num_bits - 1}, neg_one); + + // Positive: fill with 0s + EXPECT_EQ(TypeParam{8} >> 1u, TypeParam{4}); + EXPECT_EQ(TypeParam{1} >> 1u, TypeParam{}); + + // Overflow (shift >= N): return all sign bits + EXPECT_EQ(neg_one >> uint64_t{TypeParam::num_bits}, neg_one); + EXPECT_EQ(TypeParam{1} >> uint64_t{TypeParam::num_bits}, TypeParam{}); + EXPECT_EQ(min >> uint64_t{TypeParam::num_bits}, neg_one); +} + +TYPED_TEST(sint_test, left_shift) { + EXPECT_EQ(TypeParam{1} << 1u, TypeParam{2}); + EXPECT_EQ(TypeParam{1} << 0u, TypeParam{1}); + + // Overflow: returns 0 + EXPECT_EQ(TypeParam{1} << uint64_t{TypeParam::num_bits}, TypeParam{}); + EXPECT_EQ(~TypeParam{} << uint64_t{TypeParam::num_bits}, TypeParam{}); +} + +TYPED_TEST(sint_test, bitwise) { + constexpr auto zero = TypeParam{}; + constexpr auto neg_one = ~TypeParam{}; + constexpr auto one = TypeParam{1}; + + EXPECT_EQ(zero & neg_one, zero); + EXPECT_EQ(neg_one & neg_one, neg_one); + EXPECT_EQ(zero | neg_one, neg_one); + EXPECT_EQ(zero | zero, zero); + EXPECT_EQ(neg_one ^ neg_one, zero); + EXPECT_EQ(zero ^ neg_one, neg_one); + EXPECT_EQ(~zero, neg_one); + EXPECT_EQ(~neg_one, zero); + EXPECT_EQ(one & neg_one, one); + EXPECT_EQ(one | neg_one, neg_one); + EXPECT_EQ(one ^ neg_one, ~one); +} + +TYPED_TEST(sint_test, convert_to_bool) { + constexpr auto min = TypeParam{1} << (TypeParam::num_bits - 1); + EXPECT_FALSE((TypeParam{})); + EXPECT_TRUE((TypeParam{1})); + EXPECT_TRUE((-TypeParam{1})); + EXPECT_TRUE(min); +} + +// int128-specific static and runtime tests + +namespace static_test_sint128_comparison { +constexpr int128 zero; +constexpr int128 one = 1; +constexpr int128 neg_one = -int128{1}; +constexpr int128 min = int128{1} << 127; +constexpr int128 max = ~min; + +static_assert(zero == 0); +static_assert(zero != 1); +static_assert(one > 0); +static_assert(zero < 1); + +static_assert(neg_one < zero); +static_assert(neg_one < one); +static_assert(min < neg_one); +static_assert(min < zero); +static_assert(max > zero); +static_assert(max > neg_one); +static_assert(max > min); + +static_assert((int128{1} <=> int128{2}) == std::strong_ordering::less); +static_assert((int128{2} <=> int128{1}) == std::strong_ordering::greater); +static_assert((int128{1} <=> int128{1}) == std::strong_ordering::equal); +static_assert((neg_one <=> zero) == std::strong_ordering::less); +static_assert((zero <=> neg_one) == std::strong_ordering::greater); +} // namespace static_test_sint128_comparison + +namespace static_test_sint128_arith { +constexpr int128 one = 1; +constexpr int128 neg_one = -int128{1}; + +static_assert(neg_one + one == 0); +static_assert(one - one == 0); +static_assert(neg_one * neg_one == one); +static_assert(neg_one * one == neg_one); +static_assert(int128{3} * int128{4} == int128{12}); +static_assert(-int128{3} * int128{4} == -int128{12}); +static_assert((int128{1} << 127) + (-int128{1} << 127) == 0); +} // namespace static_test_sint128_arith + +TEST(sint128, comparison) { + constexpr auto neg_one = -int128{1}; + constexpr auto min = int128{1} << 127; + constexpr auto max = ~min; + + EXPECT_LT(neg_one, int128{0}); + EXPECT_LT(min, neg_one); + EXPECT_GT(max, int128{0}); + EXPECT_GT(int128{0}, neg_one); + EXPECT_GT(max, min); +} + +TEST(sint128, negation) { + constexpr auto min = int128{1} << 127; + EXPECT_EQ(-min, min); + EXPECT_EQ(-int128{0}, int128{0}); + EXPECT_EQ(-(-int128{42}), int128{42}); + EXPECT_EQ(-int128{1}, ~int128{0}); +} + +TEST(sint128, division) { + // Use the same values as the sdivrem test in test_int128.cpp. + // Read words from the uint128 literals to avoid manual hex decomposition errors. + constexpr auto xu = 0x83017fa6deecda0063b1977_u128; + constexpr auto yu = 0x1bc83504ea8f7_u128; + constexpr int128 x{xu[0], xu[1]}; + constexpr int128 y{yu[0], yu[1]}; + + EXPECT_EQ(x / y, int128{0x4b729f5338f}); + EXPECT_EQ(x % y, int128{0x13e5e3b3e827e}); + + EXPECT_EQ((-x) / (-y), int128{0x4b729f5338f}); + EXPECT_EQ((-x) % (-y), -int128{0x13e5e3b3e827e}); + + EXPECT_EQ((-x) / y, -int128{0x4b729f5338f}); + EXPECT_EQ((-x) % y, -int128{0x13e5e3b3e827e}); + + EXPECT_EQ(x / (-y), -int128{0x4b729f5338f}); + EXPECT_EQ(x % (-y), int128{0x13e5e3b3e827e}); +} + +TEST(sint128, arithmetic_right_shift) { + constexpr auto neg_one = ~int128{0}; + constexpr auto min = int128{1} << 127; + + static_assert((neg_one >> 1) == neg_one); + static_assert((neg_one >> 127) == neg_one); + static_assert((neg_one >> 128) == neg_one); + static_assert((int128{4} >> 1) == int128{2}); + static_assert((min >> 127) == neg_one); + + EXPECT_EQ(neg_one >> 1, neg_one); + EXPECT_EQ(neg_one >> 127, neg_one); + EXPECT_EQ(neg_one >> 128, neg_one); + EXPECT_EQ(int128{4} >> 1, int128{2}); + EXPECT_EQ(min >> 1, int128{3} << 126); + EXPECT_EQ(min >> 127, neg_one); + EXPECT_EQ(min >> 128, neg_one); +} diff --git a/test/unittests/test_suite.hpp b/test/unittests/test_suite.hpp index 5a30d32f..71c1db36 100644 --- a/test/unittests/test_suite.hpp +++ b/test/unittests/test_suite.hpp @@ -40,3 +40,21 @@ class uint_test : public testing::Test using test_types = testing::Types; TYPED_TEST_SUITE(uint_test, test_types, type_to_name); + +struct sint_type_to_name +{ + template + static std::string GetName([[maybe_unused]] int i) + { + return "sint" + std::to_string(T::num_bits); + } +}; + +template +class sint_test : public testing::Test +{ +}; + +using sint_test_types = testing::Types, intx::sint<192>, intx::sint<256>, + intx::sint<384>, intx::sint<512>>; +TYPED_TEST_SUITE(sint_test, sint_test_types, sint_type_to_name); From 74638709c3a42b86ac9bc0370ef42a0f3eb1f35f Mon Sep 17 00:00:00 2001 From: Diego Garcia Date: Fri, 10 Apr 2026 13:19:52 -0600 Subject: [PATCH 05/13] reliability check: cast to signed type before doing unary minux operator --- include/intx/intx.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/intx/intx.hpp b/include/intx/intx.hpp index c41ce22e..822c1f0f 100644 --- a/include/intx/intx.hpp +++ b/include/intx/intx.hpp @@ -2199,7 +2199,7 @@ struct sint : private uint { { constexpr auto word_bits = sizeof(uint64_t) * 8; // Replicate the sign bit across a full word: 0 for positive, all-ones for negative. - const auto sign_fill = -(x[num_words - 1] >> (word_bits - 1)); + const auto sign_fill = -static_cast(x[num_words - 1] >> (word_bits - 1)); if (shift >= num_bits) [[unlikely]] { @@ -2245,7 +2245,7 @@ struct sint : private uint { { // Shift amount >= 2^64 >= num_bits: result is all sign bits. constexpr auto word_bits = sizeof(uint64_t) * 8; - const auto sign_fill = -(x[num_words - 1] >> (word_bits - 1)); + const auto sign_fill = -static_cast(x[num_words - 1] >> (word_bits - 1)); sint r; for (size_t i = 0; i < num_words; ++i) r[i] = sign_fill; From b79140ecb4946596dd68de6a2d4f3fe1639eac89 Mon Sep 17 00:00:00 2001 From: Diego Garcia Date: Fri, 10 Apr 2026 13:35:50 -0600 Subject: [PATCH 06/13] fix: static_cast sign_fill back to uint64 --- include/intx/intx.hpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/include/intx/intx.hpp b/include/intx/intx.hpp index 822c1f0f..b99bad93 100644 --- a/include/intx/intx.hpp +++ b/include/intx/intx.hpp @@ -2199,7 +2199,9 @@ struct sint : private uint { { constexpr auto word_bits = sizeof(uint64_t) * 8; // Replicate the sign bit across a full word: 0 for positive, all-ones for negative. - const auto sign_fill = -static_cast(x[num_words - 1] >> (word_bits - 1)); + const auto sign_fill = static_cast( + -static_cast(x[num_words - 1] >> (word_bits - 1)) + ); if (shift >= num_bits) [[unlikely]] { @@ -2245,7 +2247,9 @@ struct sint : private uint { { // Shift amount >= 2^64 >= num_bits: result is all sign bits. constexpr auto word_bits = sizeof(uint64_t) * 8; - const auto sign_fill = -static_cast(x[num_words - 1] >> (word_bits - 1)); + const auto sign_fill = static_cast( + -static_cast(x[num_words - 1] >> (word_bits - 1)) + ); sint r; for (size_t i = 0; i < num_words; ++i) r[i] = sign_fill; From aa3c551df0dac5bf4b8cd099ccd43c4505bcac03 Mon Sep 17 00:00:00 2001 From: Diego Garcia Date: Fri, 10 Apr 2026 13:39:12 -0600 Subject: [PATCH 07/13] apply clang-format --- include/intx/intx.hpp | 53 ++-- test/benchmarks/bench_sint128.cpp | 29 +- test/unittests/test_sint.cpp | 494 +++++++++++++++--------------- 3 files changed, 297 insertions(+), 279 deletions(-) diff --git a/include/intx/intx.hpp b/include/intx/intx.hpp index b99bad93..a14d5d71 100644 --- a/include/intx/intx.hpp +++ b/include/intx/intx.hpp @@ -23,7 +23,7 @@ #include #include #if defined(__cpp_lib_unreachable) -#include // std::unreachable + #include // std::unreachable #endif #ifdef _MSC_VER @@ -1933,30 +1933,31 @@ inline void store(uint8_t* dst, const uint256& x) noexcept } // namespace be // Signed types -template -struct sint : private uint { - using internal = uint; - using typename internal::word_type; - using internal::word_num_bits; - using internal::num_bits; - using internal::num_words; +template +struct sint : private uint +{ + using internal = uint; + using internal::num_bits; + using internal::num_words; + using internal::word_num_bits; + using typename internal::word_type; private: - using internal::words_; + using internal::words_; - constexpr explicit sint(const internal& u) noexcept : internal{u} {} + constexpr explicit sint(const internal& u) noexcept : internal{u} {} public: - constexpr sint() noexcept = default; + constexpr sint() noexcept = default; - // Implicit converting constructor for any smaller int type - template - constexpr explicit(false) sint(const sint& x) noexcept - requires(M < N) - { - for (size_t i = 0; i < sint::num_words; ++i) - words_[i] = x[i]; - } + // Implicit converting constructor for any smaller int type + template + constexpr explicit(false) sint(const sint& x) noexcept + requires(M < N) + { + for (size_t i = 0; i < sint::num_words; ++i) + words_[i] = x[i]; + } #if INTX_HAS_BUILTIN_INT128 constexpr explicit(false) sint(builtin_int128 x) noexcept @@ -2199,9 +2200,8 @@ struct sint : private uint { { constexpr auto word_bits = sizeof(uint64_t) * 8; // Replicate the sign bit across a full word: 0 for positive, all-ones for negative. - const auto sign_fill = static_cast( - -static_cast(x[num_words - 1] >> (word_bits - 1)) - ); + const auto sign_fill = + static_cast(-static_cast(x[num_words - 1] >> (word_bits - 1))); if (shift >= num_bits) [[unlikely]] { @@ -2247,9 +2247,8 @@ struct sint : private uint { { // Shift amount >= 2^64 >= num_bits: result is all sign bits. constexpr auto word_bits = sizeof(uint64_t) * 8; - const auto sign_fill = static_cast( - -static_cast(x[num_words - 1] >> (word_bits - 1)) - ); + const auto sign_fill = + static_cast(-static_cast(x[num_words - 1] >> (word_bits - 1))); sint r; for (size_t i = 0; i < num_words; ++i) r[i] = sign_fill; @@ -2273,7 +2272,7 @@ inline std::string to_string(sint x, int base = 10) if (x >= 0) { uint ux; - for (size_t i = 0; i < sint::num_words; ++i) + for (size_t i = 0; i < sint::num_words; ++i) ux[i] = x[i]; return to_string(ux, base); } @@ -2283,7 +2282,7 @@ inline std::string to_string(sint x, int base = 10) // which correctly represents the magnitude 2^(N-1) when stored in uint. auto abs_x = -x; uint ux; - for (size_t i = 0; i < sint::num_words; ++i) + for (size_t i = 0; i < sint::num_words; ++i) ux[i] = abs_x[i]; return "-" + to_string(ux, base); diff --git a/test/benchmarks/bench_sint128.cpp b/test/benchmarks/bench_sint128.cpp index 6d4364a4..f08369c7 100644 --- a/test/benchmarks/bench_sint128.cpp +++ b/test/benchmarks/bench_sint128.cpp @@ -26,14 +26,14 @@ inline div_result gmp_(int128 x, int128 y) noexcept const auto y_is_neg = y < 0; const auto x_abs = x_is_neg ? -x : x; const auto y_abs = y_is_neg ? -y : y; - + // Convert to uint128 to use gmp::udivrem const auto res = gmp::udivrem(uint128{x_abs[0], x_abs[1]}, uint128{y_abs[0], y_abs[1]}); - + const auto q_is_neg = x_is_neg ^ y_is_neg; const int128 q{res.quot[0], res.quot[1]}; const int128 r{res.rem[0], res.rem[1]}; - + return {q_is_neg ? -q : q, x_is_neg ? -r : r}; } @@ -46,15 +46,20 @@ template void sdiv128(benchmark::State& state) { int128 inputs[][2] = { - {{0x537e3fbc5318dbc0e7e47d96b32ef2d5_u128[0], 0x537e3fbc5318dbc0e7e47d96b32ef2d5_u128[1]}, - {0x395df916dfd1b5e38ae7c47ce8a620f_u128[0], 0x395df916dfd1b5e38ae7c47ce8a620f_u128[1]}}, - {-int128{0x837e3fbc5318dbc0e7e47d96b32ef2d5_u128[0], 0x837e3fbc5318dbc0e7e47d96b32ef2d5_u128[1]}, - {0x895df916dfd1b5e38ae7c47ce8a620f_u128[0], 0x895df916dfd1b5e38ae7c47ce8a620f_u128[1]}}, - {{0xee657725ff64cd48b8fe188a09dc4f78_u128[0], 0xee657725ff64cd48b8fe188a09dc4f78_u128[1]}, -3}, // worst shift - {-int128{0x0e657725ff64cd48b8fe188a09dc4f78_u128[0], 0x0e657725ff64cd48b8fe188a09dc4f78_u128[1]}, - {0xe7e47d96b32ef2d5}}, // single long normalized - {{0x0e657725ff64cd48b8fe188a09dc4f78_u128[0], 0x0e657725ff64cd48b8fe188a09dc4f78_u128[1]}, - -int128{0x77e47d96b32ef2d5}}, // single long + {{0x537e3fbc5318dbc0e7e47d96b32ef2d5_u128 [0], 0x537e3fbc5318dbc0e7e47d96b32ef2d5_u128 [1]}, + {0x395df916dfd1b5e38ae7c47ce8a620f_u128 [0], + 0x395df916dfd1b5e38ae7c47ce8a620f_u128 [1]}}, + {-int128{0x837e3fbc5318dbc0e7e47d96b32ef2d5_u128 [0], + 0x837e3fbc5318dbc0e7e47d96b32ef2d5_u128 [1]}, + {0x895df916dfd1b5e38ae7c47ce8a620f_u128 [0], + 0x895df916dfd1b5e38ae7c47ce8a620f_u128 [1]}}, + {{0xee657725ff64cd48b8fe188a09dc4f78_u128 [0], 0xee657725ff64cd48b8fe188a09dc4f78_u128 [1]}, + -3}, // worst shift + {-int128{0x0e657725ff64cd48b8fe188a09dc4f78_u128 [0], + 0x0e657725ff64cd48b8fe188a09dc4f78_u128 [1]}, + {0xe7e47d96b32ef2d5}}, // single long normalized + {{0x0e657725ff64cd48b8fe188a09dc4f78_u128 [0], 0x0e657725ff64cd48b8fe188a09dc4f78_u128 [1]}, + -int128{0x77e47d96b32ef2d5}}, // single long }; benchmark::DoNotOptimize(inputs); benchmark::ClobberMemory(); diff --git a/test/unittests/test_sint.cpp b/test/unittests/test_sint.cpp index d16da679..e14a1e72 100644 --- a/test/unittests/test_sint.cpp +++ b/test/unittests/test_sint.cpp @@ -6,216 +6,225 @@ using namespace intx; -TYPED_TEST(sint_test, comparison) { - // Signed order: min < ... < -1 < 0 < 1 < ... < max - constexpr auto zero = TypeParam{}; - constexpr auto one = TypeParam{1}; - constexpr auto neg_one = -TypeParam{1}; - constexpr auto min = TypeParam{1} - << (TypeParam::num_bits - 1); // 100...0, most negative - constexpr auto max = ~min; // 011...1, most positive - - EXPECT_EQ(zero, zero); - EXPECT_EQ(one, one); - EXPECT_EQ(neg_one, neg_one); - EXPECT_EQ(min, min); - EXPECT_EQ(max, max); - - EXPECT_NE(zero, one); - EXPECT_NE(zero, neg_one); - EXPECT_NE(min, max); - - // Negative values are less than positive/zero - EXPECT_LT(min, neg_one); - EXPECT_LT(min, zero); - EXPECT_LT(min, one); - EXPECT_LT(min, max); - EXPECT_LT(neg_one, zero); - EXPECT_LT(neg_one, one); - EXPECT_LT(neg_one, max); - EXPECT_LT(zero, one); - EXPECT_LT(zero, max); - EXPECT_LT(one, max); - - EXPECT_GT(max, one); - EXPECT_GT(max, zero); - EXPECT_GT(max, neg_one); - EXPECT_GT(max, min); - EXPECT_GT(one, zero); - EXPECT_GT(one, neg_one); - EXPECT_GT(zero, neg_one); - EXPECT_GT(neg_one, min); - - EXPECT_LE(min, min); - EXPECT_LE(min, zero); - EXPECT_LE(neg_one, neg_one); - EXPECT_LE(neg_one, zero); - EXPECT_LE(zero, zero); - EXPECT_LE(zero, one); - EXPECT_LE(max, max); - - EXPECT_GE(max, max); - EXPECT_GE(max, zero); - EXPECT_GE(one, zero); - EXPECT_GE(zero, zero); - EXPECT_GE(zero, neg_one); - EXPECT_GE(neg_one, neg_one); - EXPECT_GE(neg_one, min); - EXPECT_GE(min, min); +TYPED_TEST(sint_test, comparison) +{ + // Signed order: min < ... < -1 < 0 < 1 < ... < max + constexpr auto zero = TypeParam{}; + constexpr auto one = TypeParam{1}; + constexpr auto neg_one = -TypeParam{1}; + constexpr auto min = TypeParam{1} << (TypeParam::num_bits - 1); // 100...0, most negative + constexpr auto max = ~min; // 011...1, most positive + + EXPECT_EQ(zero, zero); + EXPECT_EQ(one, one); + EXPECT_EQ(neg_one, neg_one); + EXPECT_EQ(min, min); + EXPECT_EQ(max, max); + + EXPECT_NE(zero, one); + EXPECT_NE(zero, neg_one); + EXPECT_NE(min, max); + + // Negative values are less than positive/zero + EXPECT_LT(min, neg_one); + EXPECT_LT(min, zero); + EXPECT_LT(min, one); + EXPECT_LT(min, max); + EXPECT_LT(neg_one, zero); + EXPECT_LT(neg_one, one); + EXPECT_LT(neg_one, max); + EXPECT_LT(zero, one); + EXPECT_LT(zero, max); + EXPECT_LT(one, max); + + EXPECT_GT(max, one); + EXPECT_GT(max, zero); + EXPECT_GT(max, neg_one); + EXPECT_GT(max, min); + EXPECT_GT(one, zero); + EXPECT_GT(one, neg_one); + EXPECT_GT(zero, neg_one); + EXPECT_GT(neg_one, min); + + EXPECT_LE(min, min); + EXPECT_LE(min, zero); + EXPECT_LE(neg_one, neg_one); + EXPECT_LE(neg_one, zero); + EXPECT_LE(zero, zero); + EXPECT_LE(zero, one); + EXPECT_LE(max, max); + + EXPECT_GE(max, max); + EXPECT_GE(max, zero); + EXPECT_GE(one, zero); + EXPECT_GE(zero, zero); + EXPECT_GE(zero, neg_one); + EXPECT_GE(neg_one, neg_one); + EXPECT_GE(neg_one, min); + EXPECT_GE(min, min); } -TYPED_TEST(sint_test, negation_overflow) { - // -INT_MIN == INT_MIN (two's complement overflow) - constexpr auto min = TypeParam{1} << (TypeParam::num_bits - 1); - EXPECT_EQ(-min, min); +TYPED_TEST(sint_test, negation_overflow) +{ + // -INT_MIN == INT_MIN (two's complement overflow) + constexpr auto min = TypeParam{1} << (TypeParam::num_bits - 1); + EXPECT_EQ(-min, min); - EXPECT_EQ(-TypeParam{}, TypeParam{}); - EXPECT_EQ(-(-TypeParam{1}), TypeParam{1}); + EXPECT_EQ(-TypeParam{}, TypeParam{}); + EXPECT_EQ(-(-TypeParam{1}), TypeParam{1}); } -TYPED_TEST(sint_test, arithmetic) { - constexpr auto zero = TypeParam{}; - constexpr auto one = TypeParam{1}; - constexpr auto neg_one = -TypeParam{1}; - constexpr auto min = TypeParam{1} << (TypeParam::num_bits - 1); - constexpr auto max = ~min; - - EXPECT_EQ(zero + zero, zero); - EXPECT_EQ(one + zero, one); - EXPECT_EQ(neg_one + one, zero); - EXPECT_EQ(neg_one + neg_one, -TypeParam{2}); - EXPECT_EQ(max + one, min); // INT_MAX + 1 wraps to INT_MIN - - EXPECT_EQ(zero - zero, zero); - EXPECT_EQ(one - one, zero); - EXPECT_EQ(zero - one, neg_one); - EXPECT_EQ(neg_one - neg_one, zero); - EXPECT_EQ(min - one, max); // INT_MIN - 1 wraps to INT_MAX - - EXPECT_EQ(zero * one, zero); - EXPECT_EQ(one * one, one); - EXPECT_EQ(neg_one * one, neg_one); - EXPECT_EQ(neg_one * neg_one, one); - EXPECT_EQ(TypeParam{3} * neg_one, -TypeParam{3}); - EXPECT_EQ(-TypeParam{3} * TypeParam{2}, -TypeParam{6}); +TYPED_TEST(sint_test, arithmetic) +{ + constexpr auto zero = TypeParam{}; + constexpr auto one = TypeParam{1}; + constexpr auto neg_one = -TypeParam{1}; + constexpr auto min = TypeParam{1} << (TypeParam::num_bits - 1); + constexpr auto max = ~min; + + EXPECT_EQ(zero + zero, zero); + EXPECT_EQ(one + zero, one); + EXPECT_EQ(neg_one + one, zero); + EXPECT_EQ(neg_one + neg_one, -TypeParam{2}); + EXPECT_EQ(max + one, min); // INT_MAX + 1 wraps to INT_MIN + + EXPECT_EQ(zero - zero, zero); + EXPECT_EQ(one - one, zero); + EXPECT_EQ(zero - one, neg_one); + EXPECT_EQ(neg_one - neg_one, zero); + EXPECT_EQ(min - one, max); // INT_MIN - 1 wraps to INT_MAX + + EXPECT_EQ(zero * one, zero); + EXPECT_EQ(one * one, one); + EXPECT_EQ(neg_one * one, neg_one); + EXPECT_EQ(neg_one * neg_one, one); + EXPECT_EQ(TypeParam{3} * neg_one, -TypeParam{3}); + EXPECT_EQ(-TypeParam{3} * TypeParam{2}, -TypeParam{6}); } -TYPED_TEST(sint_test, assignment_operators) { - auto x = TypeParam{5}; - x += TypeParam{3}; - EXPECT_EQ(x, TypeParam{8}); - x -= TypeParam{3}; - EXPECT_EQ(x, TypeParam{5}); - x *= TypeParam{2}; - EXPECT_EQ(x, TypeParam{10}); - x /= TypeParam{3}; - EXPECT_EQ(x, TypeParam{3}); - x %= TypeParam{2}; - EXPECT_EQ(x, TypeParam{1}); - - auto y = TypeParam{3}; - y |= TypeParam{5}; - EXPECT_EQ(y, TypeParam{7}); - y &= TypeParam{5}; - EXPECT_EQ(y, TypeParam{5}); - y ^= TypeParam{3}; - EXPECT_EQ(y, TypeParam{6}); - - auto z = TypeParam{2}; - z <<= TypeParam{2}; - EXPECT_EQ(z, TypeParam{8}); - z >>= TypeParam{1}; - EXPECT_EQ(z, TypeParam{4}); - - // Arithmetic right shift via assignment - auto w = -TypeParam{8}; - w >>= TypeParam{1}; - EXPECT_EQ(w, -TypeParam{4}); +TYPED_TEST(sint_test, assignment_operators) +{ + auto x = TypeParam{5}; + x += TypeParam{3}; + EXPECT_EQ(x, TypeParam{8}); + x -= TypeParam{3}; + EXPECT_EQ(x, TypeParam{5}); + x *= TypeParam{2}; + EXPECT_EQ(x, TypeParam{10}); + x /= TypeParam{3}; + EXPECT_EQ(x, TypeParam{3}); + x %= TypeParam{2}; + EXPECT_EQ(x, TypeParam{1}); + + auto y = TypeParam{3}; + y |= TypeParam{5}; + EXPECT_EQ(y, TypeParam{7}); + y &= TypeParam{5}; + EXPECT_EQ(y, TypeParam{5}); + y ^= TypeParam{3}; + EXPECT_EQ(y, TypeParam{6}); + + auto z = TypeParam{2}; + z <<= TypeParam{2}; + EXPECT_EQ(z, TypeParam{8}); + z >>= TypeParam{1}; + EXPECT_EQ(z, TypeParam{4}); + + // Arithmetic right shift via assignment + auto w = -TypeParam{8}; + w >>= TypeParam{1}; + EXPECT_EQ(w, -TypeParam{4}); } -TYPED_TEST(sint_test, division) { - // Truncation toward zero - EXPECT_EQ(TypeParam{7} / TypeParam{3}, TypeParam{2}); - EXPECT_EQ(TypeParam{7} % TypeParam{3}, TypeParam{1}); - EXPECT_EQ(-TypeParam{7} / TypeParam{3}, -TypeParam{2}); - EXPECT_EQ(-TypeParam{7} % TypeParam{3}, -TypeParam{1}); - EXPECT_EQ(TypeParam{7} / -TypeParam{3}, -TypeParam{2}); - EXPECT_EQ(TypeParam{7} % -TypeParam{3}, TypeParam{1}); - EXPECT_EQ(-TypeParam{7} / -TypeParam{3}, TypeParam{2}); - EXPECT_EQ(-TypeParam{7} % -TypeParam{3}, -TypeParam{1}); - - EXPECT_EQ(TypeParam{42} / TypeParam{1}, TypeParam{42}); - EXPECT_EQ(-TypeParam{42} / TypeParam{1}, -TypeParam{42}); - EXPECT_EQ(TypeParam{42} % TypeParam{42}, TypeParam{}); - - EXPECT_EQ(TypeParam{} / TypeParam{7}, TypeParam{}); - EXPECT_EQ(TypeParam{} % TypeParam{7}, TypeParam{}); - - EXPECT_EQ(TypeParam{3} / TypeParam{7}, TypeParam{}); - EXPECT_EQ(-TypeParam{3} / TypeParam{7}, TypeParam{}); - EXPECT_EQ(TypeParam{3} % TypeParam{7}, TypeParam{3}); - EXPECT_EQ(-TypeParam{3} % TypeParam{7}, -TypeParam{3}); +TYPED_TEST(sint_test, division) +{ + // Truncation toward zero + EXPECT_EQ(TypeParam{7} / TypeParam{3}, TypeParam{2}); + EXPECT_EQ(TypeParam{7} % TypeParam{3}, TypeParam{1}); + EXPECT_EQ(-TypeParam{7} / TypeParam{3}, -TypeParam{2}); + EXPECT_EQ(-TypeParam{7} % TypeParam{3}, -TypeParam{1}); + EXPECT_EQ(TypeParam{7} / -TypeParam{3}, -TypeParam{2}); + EXPECT_EQ(TypeParam{7} % -TypeParam{3}, TypeParam{1}); + EXPECT_EQ(-TypeParam{7} / -TypeParam{3}, TypeParam{2}); + EXPECT_EQ(-TypeParam{7} % -TypeParam{3}, -TypeParam{1}); + + EXPECT_EQ(TypeParam{42} / TypeParam{1}, TypeParam{42}); + EXPECT_EQ(-TypeParam{42} / TypeParam{1}, -TypeParam{42}); + EXPECT_EQ(TypeParam{42} % TypeParam{42}, TypeParam{}); + + EXPECT_EQ(TypeParam{} / TypeParam{7}, TypeParam{}); + EXPECT_EQ(TypeParam{} % TypeParam{7}, TypeParam{}); + + EXPECT_EQ(TypeParam{3} / TypeParam{7}, TypeParam{}); + EXPECT_EQ(-TypeParam{3} / TypeParam{7}, TypeParam{}); + EXPECT_EQ(TypeParam{3} % TypeParam{7}, TypeParam{3}); + EXPECT_EQ(-TypeParam{3} % TypeParam{7}, -TypeParam{3}); } -TYPED_TEST(sint_test, arithmetic_right_shift) { - constexpr auto neg_one = ~TypeParam{}; // -1, all ones - constexpr auto min = TypeParam{1} << (TypeParam::num_bits - 1); // 100...0 - constexpr auto min_shr1 = TypeParam{3} << (TypeParam::num_bits - 2); // 110...0 - - // Negative: fill with 1s - EXPECT_EQ(neg_one >> 1u, neg_one); - EXPECT_EQ(neg_one >> uint64_t{TypeParam::num_bits - 1}, neg_one); - EXPECT_EQ(min >> 1u, min_shr1); - EXPECT_EQ(min >> uint64_t{TypeParam::num_bits - 1}, neg_one); - - // Positive: fill with 0s - EXPECT_EQ(TypeParam{8} >> 1u, TypeParam{4}); - EXPECT_EQ(TypeParam{1} >> 1u, TypeParam{}); - - // Overflow (shift >= N): return all sign bits - EXPECT_EQ(neg_one >> uint64_t{TypeParam::num_bits}, neg_one); - EXPECT_EQ(TypeParam{1} >> uint64_t{TypeParam::num_bits}, TypeParam{}); - EXPECT_EQ(min >> uint64_t{TypeParam::num_bits}, neg_one); +TYPED_TEST(sint_test, arithmetic_right_shift) +{ + constexpr auto neg_one = ~TypeParam{}; // -1, all ones + constexpr auto min = TypeParam{1} << (TypeParam::num_bits - 1); // 100...0 + constexpr auto min_shr1 = TypeParam{3} << (TypeParam::num_bits - 2); // 110...0 + + // Negative: fill with 1s + EXPECT_EQ(neg_one >> 1u, neg_one); + EXPECT_EQ(neg_one >> uint64_t{TypeParam::num_bits - 1}, neg_one); + EXPECT_EQ(min >> 1u, min_shr1); + EXPECT_EQ(min >> uint64_t{TypeParam::num_bits - 1}, neg_one); + + // Positive: fill with 0s + EXPECT_EQ(TypeParam{8} >> 1u, TypeParam{4}); + EXPECT_EQ(TypeParam{1} >> 1u, TypeParam{}); + + // Overflow (shift >= N): return all sign bits + EXPECT_EQ(neg_one >> uint64_t{TypeParam::num_bits}, neg_one); + EXPECT_EQ(TypeParam{1} >> uint64_t{TypeParam::num_bits}, TypeParam{}); + EXPECT_EQ(min >> uint64_t{TypeParam::num_bits}, neg_one); } -TYPED_TEST(sint_test, left_shift) { - EXPECT_EQ(TypeParam{1} << 1u, TypeParam{2}); - EXPECT_EQ(TypeParam{1} << 0u, TypeParam{1}); +TYPED_TEST(sint_test, left_shift) +{ + EXPECT_EQ(TypeParam{1} << 1u, TypeParam{2}); + EXPECT_EQ(TypeParam{1} << 0u, TypeParam{1}); - // Overflow: returns 0 - EXPECT_EQ(TypeParam{1} << uint64_t{TypeParam::num_bits}, TypeParam{}); - EXPECT_EQ(~TypeParam{} << uint64_t{TypeParam::num_bits}, TypeParam{}); + // Overflow: returns 0 + EXPECT_EQ(TypeParam{1} << uint64_t{TypeParam::num_bits}, TypeParam{}); + EXPECT_EQ(~TypeParam{} << uint64_t{TypeParam::num_bits}, TypeParam{}); } -TYPED_TEST(sint_test, bitwise) { - constexpr auto zero = TypeParam{}; - constexpr auto neg_one = ~TypeParam{}; - constexpr auto one = TypeParam{1}; - - EXPECT_EQ(zero & neg_one, zero); - EXPECT_EQ(neg_one & neg_one, neg_one); - EXPECT_EQ(zero | neg_one, neg_one); - EXPECT_EQ(zero | zero, zero); - EXPECT_EQ(neg_one ^ neg_one, zero); - EXPECT_EQ(zero ^ neg_one, neg_one); - EXPECT_EQ(~zero, neg_one); - EXPECT_EQ(~neg_one, zero); - EXPECT_EQ(one & neg_one, one); - EXPECT_EQ(one | neg_one, neg_one); - EXPECT_EQ(one ^ neg_one, ~one); +TYPED_TEST(sint_test, bitwise) +{ + constexpr auto zero = TypeParam{}; + constexpr auto neg_one = ~TypeParam{}; + constexpr auto one = TypeParam{1}; + + EXPECT_EQ(zero & neg_one, zero); + EXPECT_EQ(neg_one & neg_one, neg_one); + EXPECT_EQ(zero | neg_one, neg_one); + EXPECT_EQ(zero | zero, zero); + EXPECT_EQ(neg_one ^ neg_one, zero); + EXPECT_EQ(zero ^ neg_one, neg_one); + EXPECT_EQ(~zero, neg_one); + EXPECT_EQ(~neg_one, zero); + EXPECT_EQ(one & neg_one, one); + EXPECT_EQ(one | neg_one, neg_one); + EXPECT_EQ(one ^ neg_one, ~one); } -TYPED_TEST(sint_test, convert_to_bool) { - constexpr auto min = TypeParam{1} << (TypeParam::num_bits - 1); - EXPECT_FALSE((TypeParam{})); - EXPECT_TRUE((TypeParam{1})); - EXPECT_TRUE((-TypeParam{1})); - EXPECT_TRUE(min); +TYPED_TEST(sint_test, convert_to_bool) +{ + constexpr auto min = TypeParam{1} << (TypeParam::num_bits - 1); + EXPECT_FALSE((TypeParam{})); + EXPECT_TRUE((TypeParam{1})); + EXPECT_TRUE((-TypeParam{1})); + EXPECT_TRUE(min); } // int128-specific static and runtime tests -namespace static_test_sint128_comparison { +namespace static_test_sint128_comparison +{ constexpr int128 zero; constexpr int128 one = 1; constexpr int128 neg_one = -int128{1}; @@ -240,9 +249,10 @@ static_assert((int128{2} <=> int128{1}) == std::strong_ordering::greater); static_assert((int128{1} <=> int128{1}) == std::strong_ordering::equal); static_assert((neg_one <=> zero) == std::strong_ordering::less); static_assert((zero <=> neg_one) == std::strong_ordering::greater); -} // namespace static_test_sint128_comparison +} // namespace static_test_sint128_comparison -namespace static_test_sint128_arith { +namespace static_test_sint128_arith +{ constexpr int128 one = 1; constexpr int128 neg_one = -int128{1}; @@ -253,64 +263,68 @@ static_assert(neg_one * one == neg_one); static_assert(int128{3} * int128{4} == int128{12}); static_assert(-int128{3} * int128{4} == -int128{12}); static_assert((int128{1} << 127) + (-int128{1} << 127) == 0); -} // namespace static_test_sint128_arith - -TEST(sint128, comparison) { - constexpr auto neg_one = -int128{1}; - constexpr auto min = int128{1} << 127; - constexpr auto max = ~min; - - EXPECT_LT(neg_one, int128{0}); - EXPECT_LT(min, neg_one); - EXPECT_GT(max, int128{0}); - EXPECT_GT(int128{0}, neg_one); - EXPECT_GT(max, min); +} // namespace static_test_sint128_arith + +TEST(sint128, comparison) +{ + constexpr auto neg_one = -int128{1}; + constexpr auto min = int128{1} << 127; + constexpr auto max = ~min; + + EXPECT_LT(neg_one, int128{0}); + EXPECT_LT(min, neg_one); + EXPECT_GT(max, int128{0}); + EXPECT_GT(int128{0}, neg_one); + EXPECT_GT(max, min); } -TEST(sint128, negation) { - constexpr auto min = int128{1} << 127; - EXPECT_EQ(-min, min); - EXPECT_EQ(-int128{0}, int128{0}); - EXPECT_EQ(-(-int128{42}), int128{42}); - EXPECT_EQ(-int128{1}, ~int128{0}); +TEST(sint128, negation) +{ + constexpr auto min = int128{1} << 127; + EXPECT_EQ(-min, min); + EXPECT_EQ(-int128{0}, int128{0}); + EXPECT_EQ(-(-int128{42}), int128{42}); + EXPECT_EQ(-int128{1}, ~int128{0}); } -TEST(sint128, division) { - // Use the same values as the sdivrem test in test_int128.cpp. - // Read words from the uint128 literals to avoid manual hex decomposition errors. - constexpr auto xu = 0x83017fa6deecda0063b1977_u128; - constexpr auto yu = 0x1bc83504ea8f7_u128; - constexpr int128 x{xu[0], xu[1]}; - constexpr int128 y{yu[0], yu[1]}; +TEST(sint128, division) +{ + // Use the same values as the sdivrem test in test_int128.cpp. + // Read words from the uint128 literals to avoid manual hex decomposition errors. + constexpr auto xu = 0x83017fa6deecda0063b1977_u128; + constexpr auto yu = 0x1bc83504ea8f7_u128; + constexpr int128 x{xu[0], xu[1]}; + constexpr int128 y{yu[0], yu[1]}; - EXPECT_EQ(x / y, int128{0x4b729f5338f}); - EXPECT_EQ(x % y, int128{0x13e5e3b3e827e}); + EXPECT_EQ(x / y, int128{0x4b729f5338f}); + EXPECT_EQ(x % y, int128{0x13e5e3b3e827e}); - EXPECT_EQ((-x) / (-y), int128{0x4b729f5338f}); - EXPECT_EQ((-x) % (-y), -int128{0x13e5e3b3e827e}); + EXPECT_EQ((-x) / (-y), int128{0x4b729f5338f}); + EXPECT_EQ((-x) % (-y), -int128{0x13e5e3b3e827e}); - EXPECT_EQ((-x) / y, -int128{0x4b729f5338f}); - EXPECT_EQ((-x) % y, -int128{0x13e5e3b3e827e}); + EXPECT_EQ((-x) / y, -int128{0x4b729f5338f}); + EXPECT_EQ((-x) % y, -int128{0x13e5e3b3e827e}); - EXPECT_EQ(x / (-y), -int128{0x4b729f5338f}); - EXPECT_EQ(x % (-y), int128{0x13e5e3b3e827e}); + EXPECT_EQ(x / (-y), -int128{0x4b729f5338f}); + EXPECT_EQ(x % (-y), int128{0x13e5e3b3e827e}); } -TEST(sint128, arithmetic_right_shift) { - constexpr auto neg_one = ~int128{0}; - constexpr auto min = int128{1} << 127; - - static_assert((neg_one >> 1) == neg_one); - static_assert((neg_one >> 127) == neg_one); - static_assert((neg_one >> 128) == neg_one); - static_assert((int128{4} >> 1) == int128{2}); - static_assert((min >> 127) == neg_one); - - EXPECT_EQ(neg_one >> 1, neg_one); - EXPECT_EQ(neg_one >> 127, neg_one); - EXPECT_EQ(neg_one >> 128, neg_one); - EXPECT_EQ(int128{4} >> 1, int128{2}); - EXPECT_EQ(min >> 1, int128{3} << 126); - EXPECT_EQ(min >> 127, neg_one); - EXPECT_EQ(min >> 128, neg_one); +TEST(sint128, arithmetic_right_shift) +{ + constexpr auto neg_one = ~int128{0}; + constexpr auto min = int128{1} << 127; + + static_assert((neg_one >> 1) == neg_one); + static_assert((neg_one >> 127) == neg_one); + static_assert((neg_one >> 128) == neg_one); + static_assert((int128{4} >> 1) == int128{2}); + static_assert((min >> 127) == neg_one); + + EXPECT_EQ(neg_one >> 1, neg_one); + EXPECT_EQ(neg_one >> 127, neg_one); + EXPECT_EQ(neg_one >> 128, neg_one); + EXPECT_EQ(int128{4} >> 1, int128{2}); + EXPECT_EQ(min >> 1, int128{3} << 126); + EXPECT_EQ(min >> 127, neg_one); + EXPECT_EQ(min >> 128, neg_one); } From f36d9190fdb966ebb6f3a9f99e4c4e5fa1bfdc26 Mon Sep 17 00:00:00 2001 From: Diego Garcia Date: Fri, 10 Apr 2026 14:23:43 -0600 Subject: [PATCH 08/13] (code quality check) add templated "intshift" function to reduce code duplication --- include/intx/intx.hpp | 151 ++++++++++++++++++------------------------ 1 file changed, 63 insertions(+), 88 deletions(-) diff --git a/include/intx/intx.hpp b/include/intx/intx.hpp index a14d5d71..8b96b104 100644 --- a/include/intx/intx.hpp +++ b/include/intx/intx.hpp @@ -92,6 +92,9 @@ using builtin_int128 = __int128; template struct uint; +template +struct sint; + /// Contains result of add/sub/etc with a carry flag. template struct result_with_carry @@ -425,6 +428,55 @@ struct uint<128> using uint128 = uint<128>; +template