diff --git a/doc/modules/ROOT/pages/examples.adoc b/doc/modules/ROOT/pages/examples.adoc index 5b9d955..0d199b8 100644 --- a/doc/modules/ROOT/pages/examples.adoc +++ b/doc/modules/ROOT/pages/examples.adoc @@ -361,6 +361,40 @@ byteswap(0x12345678) = 0x78563412 ---- ==== +[#examples_bitwise_ops] +== Bitwise Operations + +The library provides bitwise operators (`~`, `&`, `|`, `^`, `<<`, `>>`) and their compound assignment forms (`&=`, `|=`, `^=`, `<<=`, `>>=`). +The NOT, AND, OR, and XOR operators are `noexcept`, while the shift operators throw `std::overflow_error` if bits would be shifted past the type width. + +.This https://github.com/boostorg/safe_numbers/blob/develop/examples/bitwise_ops.cpp[example] demonstrates the bitwise operators and shift overflow detection. +==== +[source, c++] +---- +include::example$bitwise_ops.cpp[] +---- + +Output: +---- +~a = 0xff00ff +a & b = 0xf000f00 +a | b = 0xff0fff0f +a ^ b = 0xf00ff00f + +u8(1) << 4 = 16 +u8(128) >> 4 = 8 + +x &= 0x0F0F -> 0xf00 +x |= 0xF000 -> 0xff00 +x ^= 0x00FF -> 0xffff +y <<= 8 -> 256 +y >>= 4 -> 16 + +Left shift error: Left shift past the end of the type width +Right shift error: Right shift past the end of the type width +---- +==== + [#examples_policy_comparison] == Policy Comparison diff --git a/doc/modules/ROOT/pages/unsigned_integers.adoc b/doc/modules/ROOT/pages/unsigned_integers.adoc index 1a1480f..cb37d57 100644 --- a/doc/modules/ROOT/pages/unsigned_integers.adoc +++ b/doc/modules/ROOT/pages/unsigned_integers.adoc @@ -59,7 +59,7 @@ public: friend constexpr auto operator<=>(unsigned_integer_basis lhs, unsigned_integer_basis rhs) noexcept -> std::strong_ordering = default; - // Compound assignment operators + // Compound assignment operators (arithmetic) template constexpr auto operator+=(unsigned_integer_basis rhs) -> unsigned_integer_basis&; @@ -75,6 +75,13 @@ public: template constexpr auto operator%=(unsigned_integer_basis rhs) -> unsigned_integer_basis&; + // Compound assignment operators (bitwise) + constexpr auto operator&=(unsigned_integer_basis rhs) noexcept -> unsigned_integer_basis&; + constexpr auto operator|=(unsigned_integer_basis rhs) noexcept -> unsigned_integer_basis&; + constexpr auto operator^=(unsigned_integer_basis rhs) noexcept -> unsigned_integer_basis&; + constexpr auto operator<<=(unsigned_integer_basis rhs) -> unsigned_integer_basis&; + constexpr auto operator>>=(unsigned_integer_basis rhs) -> unsigned_integer_basis&; + // Increment and decrement operators constexpr auto operator++() -> unsigned_integer_basis&; constexpr auto operator++(int) -> unsigned_integer_basis; @@ -104,6 +111,36 @@ template constexpr auto operator%(unsigned_integer_basis lhs, unsigned_integer_basis rhs) -> unsigned_integer_basis; +// Bitwise operators +template +constexpr auto operator~(unsigned_integer_basis lhs) noexcept + -> unsigned_integer_basis; + +template +constexpr auto operator&(unsigned_integer_basis lhs, + unsigned_integer_basis rhs) noexcept + -> unsigned_integer_basis; + +template +constexpr auto operator|(unsigned_integer_basis lhs, + unsigned_integer_basis rhs) noexcept + -> unsigned_integer_basis; + +template +constexpr auto operator^(unsigned_integer_basis lhs, + unsigned_integer_basis rhs) noexcept + -> unsigned_integer_basis; + +template +constexpr auto operator<<(unsigned_integer_basis lhs, + unsigned_integer_basis rhs) + -> unsigned_integer_basis; + +template +constexpr auto operator>>(unsigned_integer_basis lhs, + unsigned_integer_basis rhs) + -> unsigned_integer_basis; + // Saturating arithmetic (clamp to min/max on overflow/underflow) template constexpr T saturating_add(T lhs, T rhs) noexcept; @@ -313,6 +350,62 @@ constexpr auto operator%=(unsigned_integer_basis rhs) -> unsigned_in Compound assignment operators follow the same exception behavior as their corresponding arithmetic operators. +=== Bitwise Operators + +[source,c++] +---- +template +constexpr auto operator~(unsigned_integer_basis lhs) noexcept + -> unsigned_integer_basis; + +template +constexpr auto operator&(unsigned_integer_basis lhs, + unsigned_integer_basis rhs) noexcept + -> unsigned_integer_basis; + +template +constexpr auto operator|(unsigned_integer_basis lhs, + unsigned_integer_basis rhs) noexcept + -> unsigned_integer_basis; + +template +constexpr auto operator^(unsigned_integer_basis lhs, + unsigned_integer_basis rhs) noexcept + -> unsigned_integer_basis; + +template +constexpr auto operator<<(unsigned_integer_basis lhs, + unsigned_integer_basis rhs) + -> unsigned_integer_basis; + +template +constexpr auto operator>>(unsigned_integer_basis lhs, + unsigned_integer_basis rhs) + -> unsigned_integer_basis; +---- + +The bitwise NOT, AND, OR, and XOR operators (`~`, `&`, `|`, `^`) are `noexcept` and operate directly on the underlying values, since these operations cannot overflow. + +The shift operators (`<<`, `>>`) perform runtime bounds checking: + +- `<<`: Throws `std::overflow_error` if the left shift would move bits past the type width. Specifically, this occurs when `bit_width(lhs) + rhs >= std::numeric_limits::digits`. +- `>>`: Throws `std::overflow_error` if the shift amount is greater than or equal to the type width (i.e., `rhs >= std::numeric_limits::digits`). + +=== Compound Bitwise Assignment Operators + +[source,c++] +---- +constexpr auto operator&=(unsigned_integer_basis rhs) noexcept -> unsigned_integer_basis&; +constexpr auto operator|=(unsigned_integer_basis rhs) noexcept -> unsigned_integer_basis&; +constexpr auto operator^=(unsigned_integer_basis rhs) noexcept -> unsigned_integer_basis&; +constexpr auto operator<<=(unsigned_integer_basis rhs) -> unsigned_integer_basis&; +constexpr auto operator>>=(unsigned_integer_basis rhs) -> unsigned_integer_basis&; +---- + +Compound bitwise assignment operators delegate to the corresponding free-function bitwise operators and follow the same exception behavior. +`&=`, `|=`, and `^=` are `noexcept`. +`<<=` and `>>=` throw `std::overflow_error` under the same conditions as `<<` and `>>`. + === Increment and Decrement Operators [source,c++] diff --git a/examples/bitwise_ops.cpp b/examples/bitwise_ops.cpp new file mode 100644 index 0000000..1c37171 --- /dev/null +++ b/examples/bitwise_ops.cpp @@ -0,0 +1,88 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include +#include +#include + +int main() +{ + using namespace boost::safe_numbers; + + const auto a = u32{0xFF00FF00u}; + const auto b = u32{0x0F0F0F0Fu}; + + // Bitwise NOT + std::cout << std::hex; + std::cout << "~a = 0x" << ~a << '\n'; + + // Bitwise AND + std::cout << "a & b = 0x" << (a & b) << '\n'; + + // Bitwise OR + std::cout << "a | b = 0x" << (a | b) << '\n'; + + // Bitwise XOR + std::cout << "a ^ b = 0x" << (a ^ b) << '\n'; + + std::cout << std::dec << '\n'; + + // Left shift (throws if bits would be shifted past the type width) + const auto one = u8{1}; + const auto shift = u8{4}; + std::cout << "u8(1) << 4 = " << static_cast(one << shift) << '\n'; + + // Right shift (throws if the shift amount >= type width) + const auto val = u8{0x80}; + std::cout << "u8(128) >> 4 = " << static_cast(val >> shift) << '\n'; + + std::cout << '\n'; + + // Compound assignment operators + auto x = u32{0xFF00u}; + x &= u32{0x0F0Fu}; + std::cout << std::hex; + std::cout << "x &= 0x0F0F -> 0x" << x << '\n'; + + x |= u32{0xF000u}; + std::cout << "x |= 0xF000 -> 0x" << x << '\n'; + + x ^= u32{0x00FFu}; + std::cout << "x ^= 0x00FF -> 0x" << x << '\n'; + + std::cout << std::dec; + auto y = u32{1}; + y <<= u32{8}; + std::cout << "y <<= 8 -> " << y << '\n'; + + y >>= u32{4}; + std::cout << "y >>= 4 -> " << y << '\n'; + + std::cout << '\n'; + + // Shift overflow detection + try + { + const auto big = u8{0xFF}; + const auto result = big << u8{1}; // Would shift bits past width + std::cout << "Should not reach here: " << static_cast(result) << '\n'; + } + catch (const std::overflow_error& e) + { + std::cerr << "Left shift error: " << e.what() << '\n'; + } + + try + { + const auto val2 = u8{1}; + const auto result = val2 >> u8{8}; // Shift amount >= type width + std::cout << "Should not reach here: " << static_cast(result) << '\n'; + } + catch (const std::overflow_error& e) + { + std::cerr << "Right shift error: " << e.what() << '\n'; + } + + return 0; +} diff --git a/include/boost/safe_numbers/bounded_integers.hpp b/include/boost/safe_numbers/bounded_integers.hpp index ba31b12..b571a6c 100644 --- a/include/boost/safe_numbers/bounded_integers.hpp +++ b/include/boost/safe_numbers/bounded_integers.hpp @@ -43,7 +43,7 @@ class bounded_uint private: using underlying_type = detail::underlying_type_t; - basis_type basis_ {static_cast(detail::raw_value(Min))}; + basis_type basis_ {}; public: diff --git a/include/boost/safe_numbers/detail/fwd.hpp b/include/boost/safe_numbers/detail/fwd.hpp index d0945f5..009154e 100644 --- a/include/boost/safe_numbers/detail/fwd.hpp +++ b/include/boost/safe_numbers/detail/fwd.hpp @@ -78,7 +78,7 @@ concept valid_bound = !std::is_same_v && (is_unsigned_library_type_v template requires valid_bound -consteval auto raw_value(T val) noexcept +constexpr auto raw_value(T val) noexcept { if constexpr (is_unsigned_library_type_v) { diff --git a/include/boost/safe_numbers/detail/int128/detail/uint128_imp.hpp b/include/boost/safe_numbers/detail/int128/detail/uint128_imp.hpp index c2f57cd..16b76fe 100644 --- a/include/boost/safe_numbers/detail/int128/detail/uint128_imp.hpp +++ b/include/boost/safe_numbers/detail/int128/detail/uint128_imp.hpp @@ -25,8 +25,8 @@ namespace boost { namespace int128 { -BOOST_SAFE_NUMBERS_DETAIL_INT128_EXPORT struct - #ifdef BOOST_SAFE_NUMBERS_DETAIL_INT128_HAS_INT128 +struct + #if defined(BOOST_SAFE_NUMBERS_DETAIL_INT128_HAS_INT128) || defined(BOOST_SAFE_NUMBERS_DETAIL_INT128_HAS_MSVC_INT128) alignas(alignof(detail::builtin_u128)) #else alignas(16) @@ -402,8 +402,8 @@ BOOST_SAFE_NUMBERS_DETAIL_INT128_EXPORT constexpr bool operator==(const uint128_ } else { - __m128i a = _mm_load_si128(reinterpret_cast(&lhs)); - __m128i b = _mm_load_si128(reinterpret_cast(&rhs)); + __m128i a = _mm_loadu_si128(reinterpret_cast(&lhs)); + __m128i b = _mm_loadu_si128(reinterpret_cast(&rhs)); __m128i cmp = _mm_cmpeq_epi32(a, b); return _mm_movemask_ps(_mm_castsi128_ps(cmp)) == 0xF; @@ -538,8 +538,8 @@ BOOST_SAFE_NUMBERS_DETAIL_INT128_EXPORT constexpr bool operator!=(const uint128_ } else { - __m128i a = _mm_load_si128(reinterpret_cast(&lhs)); - __m128i b = _mm_load_si128(reinterpret_cast(&rhs)); + __m128i a = _mm_loadu_si128(reinterpret_cast(&lhs)); + __m128i b = _mm_loadu_si128(reinterpret_cast(&rhs)); __m128i cmp = _mm_cmpeq_epi32(a, b); return _mm_movemask_ps(_mm_castsi128_ps(cmp)) != 0xF; diff --git a/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp b/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp index 0ebc47a..0a3e4d3 100644 --- a/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp +++ b/include/boost/safe_numbers/detail/unsigned_integer_basis.hpp @@ -9,10 +9,12 @@ #include #include #include +#include #ifndef BOOST_SAFE_NUMBERS_BUILD_MODULE #include +#include #include #include #include @@ -74,6 +76,16 @@ class unsigned_integer_basis template constexpr auto operator%=(unsigned_integer_basis rhs) -> unsigned_integer_basis&; + constexpr auto operator&=(unsigned_integer_basis rhs) noexcept -> unsigned_integer_basis&; + + constexpr auto operator|=(unsigned_integer_basis rhs) noexcept -> unsigned_integer_basis&; + + constexpr auto operator^=(unsigned_integer_basis rhs) noexcept -> unsigned_integer_basis&; + + constexpr auto operator<<=(unsigned_integer_basis rhs) -> unsigned_integer_basis&; + + constexpr auto operator>>=(unsigned_integer_basis rhs) -> unsigned_integer_basis&; + constexpr auto operator++() -> unsigned_integer_basis&; constexpr auto operator++(int) -> unsigned_integer_basis; @@ -1873,6 +1885,117 @@ template } } +template +constexpr auto operator~(const detail::unsigned_integer_basis lhs) noexcept +{ + using return_type = detail::unsigned_integer_basis; + return return_type{static_cast(~detail::raw_value(lhs))}; +} + +template +constexpr auto operator&(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept +{ + using return_type = detail::unsigned_integer_basis; + return return_type{static_cast(detail::raw_value(lhs) & detail::raw_value(rhs))}; +} + +template +constexpr auto operator|(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept +{ + using return_type = detail::unsigned_integer_basis; + return return_type{static_cast(detail::raw_value(lhs) | detail::raw_value(rhs))}; +} + +template +constexpr auto operator^(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) noexcept +{ + using return_type = detail::unsigned_integer_basis; + return return_type{static_cast(detail::raw_value(lhs) ^ detail::raw_value(rhs))}; +} + +template +constexpr auto operator<<(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) +{ + using return_type = detail::unsigned_integer_basis; + using core::bit_width; + + const auto raw_lhs {detail::raw_value(lhs)}; + const auto raw_rhs {detail::raw_value(rhs)}; + + const auto lhs_width {static_cast(bit_width(raw_lhs))}; + + if (lhs_width + raw_rhs >= std::numeric_limits::digits) + { + BOOST_THROW_EXCEPTION(std::overflow_error("Left shift past the end of the type width")); + } + + return return_type{static_cast(raw_lhs << raw_rhs)}; +} + +template +constexpr auto operator>>(const detail::unsigned_integer_basis lhs, + const detail::unsigned_integer_basis rhs) +{ + using return_type = detail::unsigned_integer_basis; + const auto raw_lhs {detail::raw_value(lhs)}; + const auto raw_rhs {detail::raw_value(rhs)}; + + if (raw_rhs >= static_cast(std::numeric_limits::digits)) + { + BOOST_THROW_EXCEPTION(std::overflow_error("Right shift past the end of the type width")); + } + + return return_type{static_cast(raw_lhs >> raw_rhs)}; +} + +// ------------------------------ +// Compound bitwise operators +// ------------------------------ + +template +constexpr auto detail::unsigned_integer_basis::operator&=(const unsigned_integer_basis rhs) noexcept + -> unsigned_integer_basis& +{ + *this = *this & rhs; + return *this; +} + +template +constexpr auto detail::unsigned_integer_basis::operator|=(const unsigned_integer_basis rhs) noexcept + -> unsigned_integer_basis& +{ + *this = *this | rhs; + return *this; +} + +template +constexpr auto detail::unsigned_integer_basis::operator^=(const unsigned_integer_basis rhs) noexcept + -> unsigned_integer_basis& +{ + *this = *this ^ rhs; + return *this; +} + +template +constexpr auto detail::unsigned_integer_basis::operator<<=(const unsigned_integer_basis rhs) + -> unsigned_integer_basis& +{ + *this = boost::safe_numbers::operator<<(*this, rhs); + return *this; +} + +template +constexpr auto detail::unsigned_integer_basis::operator>>=(const unsigned_integer_basis rhs) + -> unsigned_integer_basis& +{ + *this = boost::safe_numbers::operator>>(*this, rhs); + return *this; +} + } // namespace boost::safe_numbers #undef BOOST_SAFE_NUMBERS_DEFINE_MIXED_UNSIGNED_INTEGER_OP diff --git a/test/Jamfile b/test/Jamfile index 2ec2e0f..9d40b75 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -71,6 +71,7 @@ run test_unsigned_std_format.cpp ; run test_unsigned_fmt_format.cpp ; run test_unsigned_charconv.cpp ; run test_bit.cpp ; +run test_unsigned_bitwise_ops.cpp ; run test_unsigned_saturating_addition.cpp ; run test_unsigned_comparisons.cpp ; run test_unsigned_saturating_subtraction.cpp ; @@ -140,3 +141,4 @@ run ../examples/iostream.cpp ; run ../examples/charconv.cpp ; run ../examples/bit.cpp ; run ../examples/bounded_limits.cpp ; +run ../examples/bitwise_ops.cpp ; diff --git a/test/test_unsigned_bitwise_ops.cpp b/test/test_unsigned_bitwise_ops.cpp new file mode 100644 index 0000000..1157c4e --- /dev/null +++ b/test/test_unsigned_bitwise_ops.cpp @@ -0,0 +1,718 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wold-style-cast" +# pragma clang diagnostic ignored "-Wundef" +# pragma clang diagnostic ignored "-Wconversion" +# pragma clang diagnostic ignored "-Wsign-conversion" +# pragma clang diagnostic ignored "-Wfloat-equal" +# pragma clang diagnostic ignored "-Wsign-compare" +# pragma clang diagnostic ignored "-Woverflow" + +# if (__clang_major__ >= 10 && !defined(__APPLE__)) || __clang_major__ >= 13 +# pragma clang diagnostic ignored "-Wdeprecated-copy" +# endif + +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wold-style-cast" +# pragma GCC diagnostic ignored "-Wundef" +# pragma GCC diagnostic ignored "-Wconversion" +# pragma GCC diagnostic ignored "-Wsign-conversion" +# pragma GCC diagnostic ignored "-Wsign-compare" +# pragma GCC diagnostic ignored "-Wfloat-equal" +# pragma GCC diagnostic ignored "-Woverflow" + +#elif defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable : 4389) +# pragma warning(disable : 4127) +# pragma warning(disable : 4305) +# pragma warning(disable : 4309) +#endif + +#define BOOST_SAFE_NUMBERS_DETAIL_INT128_ALLOW_SIGN_COMPARE +#define BOOST_SAFE_NUMBERS_DETAIL_INT128_ALLOW_SIGN_CONVERSION + +#include + +#ifdef __clang__ +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#elif defined(_MSC_VER) +# pragma warning(pop) +#endif + +// Ignore [[nodiscard]] on the test that we know are going to throw +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-result" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-result" +#elif defined(_MSC_VER) +# pragma warning (push) +# pragma warning (disable: 4834) +#endif + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include +#include +#include + +#endif + +using namespace boost::safe_numbers; + +inline std::mt19937_64 rng{42}; +inline constexpr std::size_t N {1024}; + +// ============================================= +// Bitwise NOT (~) +// ============================================= + +template +void test_bitwise_not() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {0, std::numeric_limits::max()}; + + for (std::size_t i {0}; i < N; ++i) + { + const auto raw {dist(rng)}; + const T wrapped {raw}; + const auto result {~wrapped}; + const T expected {static_cast(~raw)}; + + BOOST_TEST(expected == result); + } + + // Edge cases + BOOST_TEST(T{std::numeric_limits::max()} == ~T{0}); + BOOST_TEST(T{0} == ~T{std::numeric_limits::max()}); +} + +// ============================================= +// Bitwise AND (&) +// ============================================= + +template +void test_bitwise_and() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {0, std::numeric_limits::max()}; + + for (std::size_t i {0}; i < N; ++i) + { + const auto raw_a {dist(rng)}; + const auto raw_b {dist(rng)}; + const T a {raw_a}; + const T b {raw_b}; + const auto result {a & b}; + const T expected {static_cast(raw_a & raw_b)}; + + BOOST_TEST(expected == result); + } + + // Edge cases + BOOST_TEST(T{0} == (T{0} & T{std::numeric_limits::max()})); + BOOST_TEST(T{std::numeric_limits::max()} == (T{std::numeric_limits::max()} & T{std::numeric_limits::max()})); + + // x & x == x + const T val {42}; + BOOST_TEST(val == (val & val)); +} + +// ============================================= +// Bitwise OR (|) +// ============================================= + +template +void test_bitwise_or() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {0, std::numeric_limits::max()}; + + for (std::size_t i {0}; i < N; ++i) + { + const auto raw_a {dist(rng)}; + const auto raw_b {dist(rng)}; + const T a {raw_a}; + const T b {raw_b}; + const auto result {a | b}; + const T expected {static_cast(raw_a | raw_b)}; + + BOOST_TEST(expected == result); + } + + // Edge cases + BOOST_TEST(T{std::numeric_limits::max()} == (T{0} | T{std::numeric_limits::max()})); + BOOST_TEST(T{0} == (T{0} | T{0})); + + // x | 0 == x + const T val {42}; + BOOST_TEST(val == (val | T{0})); +} + +// ============================================= +// Bitwise XOR (^) +// ============================================= + +template +void test_bitwise_xor() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {0, std::numeric_limits::max()}; + + for (std::size_t i {0}; i < N; ++i) + { + const auto raw_a {dist(rng)}; + const auto raw_b {dist(rng)}; + const T a {raw_a}; + const T b {raw_b}; + const auto result {a ^ b}; + const T expected {static_cast(raw_a ^ raw_b)}; + + BOOST_TEST(expected == result); + } + + // Edge cases: x ^ x == 0 + const T val {42}; + BOOST_TEST(T{0} == (val ^ val)); + + // x ^ 0 == x + BOOST_TEST(val == (val ^ T{0})); +} + +// ============================================= +// Left Shift (<<) - Success +// ============================================= + +template +void test_left_shift_success() +{ + using basis_type = detail::underlying_type_t; + constexpr auto digits {std::numeric_limits::digits}; + + // Shifting zero by any amount should be fine + BOOST_TEST(T{0} == (T{0} << T{0})); + + // Shift 1 left by amounts that fit + for (int i {0}; i < digits - 1; ++i) + { + const auto shift {static_cast(i)}; + const auto expected {static_cast(static_cast(1) << shift)}; + const auto result {T{1} << T{shift}}; + + BOOST_TEST(T{expected} == result); + } + + // Shift small values by small amounts + const T two {2}; + BOOST_TEST(T{4} == (two << T{1})); + BOOST_TEST(T{8} == (two << T{2})); +} + +// ============================================= +// Left Shift (<<) - Failure (throws) +// ============================================= + +template +void test_left_shift_failure() +{ + using basis_type = detail::underlying_type_t; + constexpr auto digits {std::numeric_limits::digits}; + + // Shifting by the full type width should throw + BOOST_TEST_THROWS(T{1} << T{static_cast(digits)}, std::overflow_error); + + // Shifting a large value left should throw + BOOST_TEST_THROWS(T{std::numeric_limits::max()} << T{1}, std::overflow_error); + + // Shifting the high bit left by 1 should throw + const auto high_bit {static_cast(static_cast(1) << (digits - 1))}; + BOOST_TEST_THROWS(T{high_bit} << T{1}, std::overflow_error); +} + +// ============================================= +// Right Shift (>>) - Success +// ============================================= + +template +void test_right_shift_success() +{ + using basis_type = detail::underlying_type_t; + constexpr auto digits {std::numeric_limits::digits}; + + // Shifting zero by any amount should be fine + BOOST_TEST(T{0} == (T{0} >> T{0})); + + // Shift the high bit right by increasing amounts + const auto high_bit {static_cast(static_cast(1) << (digits - 1))}; + for (int i {0}; i < digits; ++i) + { + const auto shift {static_cast(i)}; + const auto expected {static_cast(high_bit >> shift)}; + const auto result {T{high_bit} >> T{shift}}; + + BOOST_TEST(T{expected} == result); + } + + // Shift a value by 0 returns the same value + const T val {42}; + BOOST_TEST(val == (val >> T{0})); +} + +// ============================================= +// Right Shift (>>) - Failure (throws) +// ============================================= + +template +void test_right_shift_failure() +{ + using basis_type = detail::underlying_type_t; + constexpr auto digits {std::numeric_limits::digits}; + + // Shifting by the full type width should throw + BOOST_TEST_THROWS(T{1} >> T{static_cast(digits)}, std::overflow_error); + + // Shifting by more than the type width should throw + if constexpr (digits < std::numeric_limits::max()) + { + BOOST_TEST_THROWS(T{1} >> T{static_cast(digits + 1)}, std::overflow_error); + } +} + +// ============================================= +// Compound bitwise operators (&=, |=, ^=, <<=, >>=) +// ============================================= + +template +void test_compound_and() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {0, std::numeric_limits::max()}; + + for (std::size_t i {0}; i < N; ++i) + { + const auto raw_a {dist(rng)}; + const auto raw_b {dist(rng)}; + T a {raw_a}; + const T b {raw_b}; + a &= b; + const T expected {static_cast(raw_a & raw_b)}; + + BOOST_TEST(expected == a); + } +} + +template +void test_compound_or() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {0, std::numeric_limits::max()}; + + for (std::size_t i {0}; i < N; ++i) + { + const auto raw_a {dist(rng)}; + const auto raw_b {dist(rng)}; + T a {raw_a}; + const T b {raw_b}; + a |= b; + const T expected {static_cast(raw_a | raw_b)}; + + BOOST_TEST(expected == a); + } +} + +template +void test_compound_xor() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {0, std::numeric_limits::max()}; + + for (std::size_t i {0}; i < N; ++i) + { + const auto raw_a {dist(rng)}; + const auto raw_b {dist(rng)}; + T a {raw_a}; + const T b {raw_b}; + a ^= b; + const T expected {static_cast(raw_a ^ raw_b)}; + + BOOST_TEST(expected == a); + } +} + +template +void test_compound_left_shift() +{ + using basis_type = detail::underlying_type_t; + + // Valid compound left shift + T val {1}; + val <<= T{3}; + BOOST_TEST(T{8} == val); + + // Chained compound left shifts + T v {1}; + v <<= T{1}; + BOOST_TEST(T{2} == v); + v <<= T{1}; + BOOST_TEST(T{4} == v); + + // Compound left shift that overflows should throw + T big {std::numeric_limits::max()}; + BOOST_TEST_THROWS(big <<= T{1}, std::overflow_error); +} + +template +void test_compound_right_shift() +{ + using basis_type = detail::underlying_type_t; + constexpr auto digits {std::numeric_limits::digits}; + + // Valid compound right shift + T val {8}; + val >>= T{3}; + BOOST_TEST(T{1} == val); + + // Chained compound right shifts + T v {16}; + v >>= T{1}; + BOOST_TEST(T{8} == v); + v >>= T{2}; + BOOST_TEST(T{2} == v); + + // Compound right shift past type width should throw + T one {1}; + BOOST_TEST_THROWS(one >>= T{static_cast(digits)}, std::overflow_error); +} + +// ============================================= +// u128 specific tests (boost::random supports uint128_t) +// ============================================= + +void test_bitwise_not_u128() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {0, std::numeric_limits::max()}; + + for (std::size_t i {0}; i < N; ++i) + { + const auto raw {dist(rng)}; + const u128 wrapped {raw}; + const auto result {~wrapped}; + const u128 expected {~raw}; + + BOOST_TEST(expected == result); + } + + BOOST_TEST(u128{std::numeric_limits::max()} == ~u128{0}); + BOOST_TEST(u128{0} == ~u128{std::numeric_limits::max()}); +} + +void test_bitwise_and_u128() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {0, std::numeric_limits::max()}; + + for (std::size_t i {0}; i < N; ++i) + { + const auto raw_a {dist(rng)}; + const auto raw_b {dist(rng)}; + const u128 a {raw_a}; + const u128 b {raw_b}; + const auto result {a & b}; + const u128 expected {raw_a & raw_b}; + + BOOST_TEST(expected == result); + } + + BOOST_TEST(u128{0} == (u128{0} & u128{std::numeric_limits::max()})); + + const u128 val {42}; + BOOST_TEST(val == (val & val)); +} + +void test_bitwise_or_u128() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {0, std::numeric_limits::max()}; + + for (std::size_t i {0}; i < N; ++i) + { + const auto raw_a {dist(rng)}; + const auto raw_b {dist(rng)}; + const u128 a {raw_a}; + const u128 b {raw_b}; + const auto result {a | b}; + const u128 expected {raw_a | raw_b}; + + BOOST_TEST(expected == result); + } + + BOOST_TEST(u128{std::numeric_limits::max()} == (u128{0} | u128{std::numeric_limits::max()})); + + const u128 val {42}; + BOOST_TEST(val == (val | u128{0})); +} + +void test_bitwise_xor_u128() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {0, std::numeric_limits::max()}; + + for (std::size_t i {0}; i < N; ++i) + { + const auto raw_a {dist(rng)}; + const auto raw_b {dist(rng)}; + const u128 a {raw_a}; + const u128 b {raw_b}; + const auto result {a ^ b}; + const u128 expected {raw_a ^ raw_b}; + + BOOST_TEST(expected == result); + } + + const u128 val {42}; + BOOST_TEST(u128{0} == (val ^ val)); + BOOST_TEST(val == (val ^ u128{0})); +} + +void test_left_shift_success_u128() +{ + BOOST_TEST(u128{0} == (u128{0} << u128{0})); + + for (int i {0}; i < 127; ++i) + { + using basis_type = detail::underlying_type_t; + const auto shift {static_cast(i)}; + const auto expected {static_cast(static_cast(1) << shift)}; + const auto result {u128{1} << u128{shift}}; + + BOOST_TEST(u128{expected} == result); + } + + const u128 two {2}; + BOOST_TEST(u128{4} == (two << u128{1})); + BOOST_TEST(u128{8} == (two << u128{2})); +} + +void test_left_shift_failure_u128() +{ + using basis_type = detail::underlying_type_t; + + BOOST_TEST_THROWS(u128{1} << u128{128}, std::overflow_error); + BOOST_TEST_THROWS(u128{std::numeric_limits::max()} << u128{1}, std::overflow_error); +} + +void test_right_shift_success_u128() +{ + using basis_type = detail::underlying_type_t; + + BOOST_TEST(u128{0} == (u128{0} >> u128{0})); + + const auto high_bit {static_cast(static_cast(1) << 127)}; + for (int i {0}; i < 128; ++i) + { + const auto shift {static_cast(i)}; + const auto expected {static_cast(high_bit >> shift)}; + const auto result {u128{high_bit} >> u128{shift}}; + + BOOST_TEST(u128{expected} == result); + } + + const u128 val {42}; + BOOST_TEST(val == (val >> u128{0})); +} + +void test_right_shift_failure_u128() +{ + BOOST_TEST_THROWS(u128{1} >> u128{128}, std::overflow_error); + BOOST_TEST_THROWS(u128{1} >> u128{200}, std::overflow_error); +} + +void test_compound_and_u128() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {0, std::numeric_limits::max()}; + + for (std::size_t i {0}; i < N; ++i) + { + const auto raw_a {dist(rng)}; + const auto raw_b {dist(rng)}; + u128 a {raw_a}; + const u128 b {raw_b}; + a &= b; + const u128 expected {raw_a & raw_b}; + + BOOST_TEST(expected == a); + } +} + +void test_compound_or_u128() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {0, std::numeric_limits::max()}; + + for (std::size_t i {0}; i < N; ++i) + { + const auto raw_a {dist(rng)}; + const auto raw_b {dist(rng)}; + u128 a {raw_a}; + const u128 b {raw_b}; + a |= b; + const u128 expected {raw_a | raw_b}; + + BOOST_TEST(expected == a); + } +} + +void test_compound_xor_u128() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {0, std::numeric_limits::max()}; + + for (std::size_t i {0}; i < N; ++i) + { + const auto raw_a {dist(rng)}; + const auto raw_b {dist(rng)}; + u128 a {raw_a}; + const u128 b {raw_b}; + a ^= b; + const u128 expected {raw_a ^ raw_b}; + + BOOST_TEST(expected == a); + } +} + +void test_compound_left_shift_u128() +{ + using basis_type = detail::underlying_type_t; + + u128 val {1}; + val <<= u128{3}; + BOOST_TEST(u128{8} == val); + + u128 big {std::numeric_limits::max()}; + BOOST_TEST_THROWS(big <<= u128{1}, std::overflow_error); +} + +void test_compound_right_shift_u128() +{ + u128 val {8}; + val >>= u128{3}; + BOOST_TEST(u128{1} == val); + + u128 one {1}; + BOOST_TEST_THROWS(one >>= u128{128}, std::overflow_error); +} + +int main() +{ + // Bitwise NOT + test_bitwise_not(); + test_bitwise_not(); + test_bitwise_not(); + test_bitwise_not(); + test_bitwise_not_u128(); + + // Bitwise AND + test_bitwise_and(); + test_bitwise_and(); + test_bitwise_and(); + test_bitwise_and(); + test_bitwise_and_u128(); + + // Bitwise OR + test_bitwise_or(); + test_bitwise_or(); + test_bitwise_or(); + test_bitwise_or(); + test_bitwise_or_u128(); + + // Bitwise XOR + test_bitwise_xor(); + test_bitwise_xor(); + test_bitwise_xor(); + test_bitwise_xor(); + test_bitwise_xor_u128(); + + // Left shift - success + test_left_shift_success(); + test_left_shift_success(); + test_left_shift_success(); + test_left_shift_success(); + test_left_shift_success_u128(); + + // Left shift - failure + test_left_shift_failure(); + test_left_shift_failure(); + test_left_shift_failure(); + test_left_shift_failure(); + test_left_shift_failure_u128(); + + // Right shift - success + test_right_shift_success(); + test_right_shift_success(); + test_right_shift_success(); + test_right_shift_success(); + test_right_shift_success_u128(); + + // Right shift - failure + test_right_shift_failure(); + test_right_shift_failure(); + test_right_shift_failure(); + test_right_shift_failure(); + test_right_shift_failure_u128(); + + // Compound &= + test_compound_and(); + test_compound_and(); + test_compound_and(); + test_compound_and(); + test_compound_and_u128(); + + // Compound |= + test_compound_or(); + test_compound_or(); + test_compound_or(); + test_compound_or(); + test_compound_or_u128(); + + // Compound ^= + test_compound_xor(); + test_compound_xor(); + test_compound_xor(); + test_compound_xor(); + test_compound_xor_u128(); + + // Compound <<= + test_compound_left_shift(); + test_compound_left_shift(); + test_compound_left_shift(); + test_compound_left_shift(); + test_compound_left_shift_u128(); + + // Compound >>= + test_compound_right_shift(); + test_compound_right_shift(); + test_compound_right_shift(); + test_compound_right_shift(); + test_compound_right_shift_u128(); + + return boost::report_errors(); +}