diff --git a/include/intx/intx.hpp b/include/intx/intx.hpp index 20967468..99f8f090 100644 --- a/include/intx/intx.hpp +++ b/include/intx/intx.hpp @@ -79,6 +79,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 @@ -87,6 +88,9 @@ using builtin_uint128 = unsigned __int128; template struct uint; +template +struct sint; + /// Contains result of add/sub/etc with a carry flag. template struct result_with_carry @@ -215,7 +219,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: @@ -420,7 +424,6 @@ struct uint<128> using uint128 = uint<128>; - /// Optimized addition. /// /// This keeps the multiprecision addition until CodeGen so the pattern is not @@ -893,7 +896,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: @@ -920,6 +923,7 @@ struct uint : words_{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 uint(std::span words) noexcept @@ -1926,6 +1930,370 @@ inline void store(uint8_t* dst, const uint256& x) noexcept } // namespace be +// Signed types +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_; + + constexpr explicit sint(const internal& u) noexcept : internal{u} {} + +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 + { + return sint{addc(static_cast(x), static_cast(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 + { + 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 + { + 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 + { + return sint{sdivrem(static_cast(x), static_cast(y)).quot}; + } + + friend constexpr sint operator%(const sint& x, const sint& y) noexcept + { + return sint{sdivrem(static_cast(x), static_cast(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 + { + sint 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 + { + sint 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 + { + sint 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 + { + sint 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 + { + 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); } + 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 + { + 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))); + + if (shift >= num_bits) [[unlikely]] + { + sint r; + for (size_t i = 0; i < num_words; ++i) + r[i] = sign_fill; + return r; + } + + const auto s = shift % word_bits; + const auto skip = static_cast(shift / word_bits); + + 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 + { + static_assert(sizeof(shift) <= sizeof(uint64_t)); + return x >> static_cast(shift); + } + + friend constexpr sint operator>>(const sint& x, const sint& shift) noexcept + { + 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]] + { + // 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))); + sint r; + for (size_t i = 0; i < num_words; ++i) + r[i] = sign_fill; + return r; + } + + return x >> shift[0]; + } + + constexpr sint& operator<<=(sint shift) noexcept { return *this = *this << shift; } + 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 #ifdef _MSC_VER 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..f08369c7 --- /dev/null +++ b/test/benchmarks/bench_sint128.cpp @@ -0,0 +1,117 @@ +// 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..4c71e1c5 --- /dev/null +++ b/test/unittests/test_sint.cpp @@ -0,0 +1,330 @@ +// 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); // NOLINT(misc-redundant-expression): part of the test +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);