diff --git a/doc/modules/ROOT/pages/bit.adoc b/doc/modules/ROOT/pages/bit.adoc index 7b66529..12dfd54 100644 --- a/doc/modules/ROOT/pages/bit.adoc +++ b/doc/modules/ROOT/pages/bit.adoc @@ -169,6 +169,20 @@ See https://en.cppreference.com/w/cpp/numeric/byteswap.html[`std::byteswap`]. NOTE: `byteswap` is not available for `bounded_uint` types. Byte reversal can produce values outside the valid bounded range, making the operation semantically meaningless for bounded integers. +=== bitswap + +[source,c++] +---- +template +constexpr auto bitswap(Int x) noexcept -> Int; +---- + +Reverses all bits of `x`. +This performs a full bit-reversal: for an N-bit integer, bit 0 becomes bit N-1, bit 1 becomes bit N-2, and so on. +Implemented using a lookup-table approach that reverses each byte individually and then reverses the byte order. + +NOTE: `bitswap` is not available for `bounded_uint` types. Bit reversal can produce values outside the valid bounded range, making the operation semantically meaningless for bounded integers. + == Verified Types The bit functions have special behavior with xref:verified_integers.adoc[verified types]: @@ -176,7 +190,7 @@ The bit functions have special behavior with xref:verified_integers.adoc[verifie Functions that return `int` or `bool` (`has_single_bit`, `bit_width`, `countl_zero`, `countl_one`, `countr_zero`, `countr_one`, `popcount`) work at runtime with verified types. These functions only read the value via the `constexpr` conversion operator. -Functions that return the safe type (`bit_ceil`, `bit_floor`, `rotl`, `rotr`, `byteswap`) have `consteval` overloads for verified types, meaning they can only be called at compile time. +Functions that return the safe type (`bit_ceil`, `bit_floor`, `rotl`, `rotr`, `byteswap`, `bitswap`) have `consteval` overloads for verified types, meaning they can only be called at compile time. The `consteval` overloads use a `requires` clause to distinguish them: [source,c++] @@ -192,7 +206,7 @@ template consteval auto bit_ceil(UnsignedInt x) noexcept -> UnsignedInt; ---- -The same pattern applies to `bit_floor`, `rotl`, `rotr`, and `byteswap`. +The same pattern applies to `bit_floor`, `rotl`, `rotr`, `byteswap`, and `bitswap`. == Examples @@ -221,6 +235,7 @@ countr_one(0x0F00) = 0 popcount(0x0F00) = 4 byteswap(0x12345678) = 0x78563412 +bitswap(0x12345678) = 0x1e6a2c48 bounded has_single_bit(40) = 0 bounded bit_ceil(40) = 64 diff --git a/examples/bit.cpp b/examples/bit.cpp index 5df8022..b7909d0 100644 --- a/examples/bit.cpp +++ b/examples/bit.cpp @@ -43,6 +43,7 @@ int main() const u32 w {0x12345678}; std::cout << std::hex; std::cout << "byteswap(0x12345678) = 0x" << static_cast(byteswap(w)) << '\n'; + std::cout << "bitswap(0x12345678) = 0x" << static_cast(bitswap(w)) << '\n'; std::cout << std::dec << '\n'; diff --git a/include/boost/safe_numbers/bit.hpp b/include/boost/safe_numbers/bit.hpp index dbe003f..412f72b 100644 --- a/include/boost/safe_numbers/bit.hpp +++ b/include/boost/safe_numbers/bit.hpp @@ -12,6 +12,7 @@ #ifndef BOOST_SAFE_NUMBERS_BUILD_MODULE #include +#include #endif // BOOST_SAFE_NUMBERS_BUILD_MODULE @@ -180,6 +181,72 @@ BOOST_SAFE_NUMBERS_EXPORT template (x))}; } +namespace detail { + +consteval auto reverse_byte(std::uint8_t b) noexcept -> std::uint8_t +{ + b = static_cast(((b & UINT8_C(0x55)) << 1u) | ((b & UINT8_C(0xAA)) >> 1u)); + b = static_cast(((b & UINT8_C(0x33)) << 2u) | ((b & UINT8_C(0xCC)) >> 2u)); + b = static_cast(((b & UINT8_C(0x0F)) << 4u) | ((b & UINT8_C(0xF0)) >> 4u)); + + return b; +} + +consteval auto make_byte_reverse_table() -> std::array +{ + std::array table {}; + + for (int i {}; i < 256; ++i) + { + table[i] = reverse_byte(static_cast(i)); + } + + return table; +} + +inline constexpr auto reverse_table {make_byte_reverse_table()}; + +template +[[nodiscard]] constexpr auto bitswap_impl(UnsignedInt x) noexcept -> UnsignedInt +{ + if constexpr (sizeof(UnsignedInt) == 1) + { + return static_cast(reverse_table[static_cast(x)]); + } + else + { + constexpr auto n {sizeof(UnsignedInt)}; + + UnsignedInt result {}; + for (std::size_t i {}; i < n; ++i) + { + result = static_cast(static_cast(result << 8U) | + static_cast(reverse_table[static_cast(x & 0xFFU)])); + x >>= 8U; + } + + return result; + } +} + +} // namespace detail + +BOOST_SAFE_NUMBERS_EXPORT template + requires (!detail::is_verified_type_v) +[[nodiscard]] constexpr auto bitswap(Int x) noexcept -> Int +{ + using underlying_type = detail::underlying_type_t; + return static_cast(detail::bitswap_impl(static_cast(x))); +} + +BOOST_SAFE_NUMBERS_EXPORT template + requires detail::is_verified_type_v +[[nodiscard]] consteval auto bitswap(const Int x) noexcept -> Int +{ + using underlying_type = detail::underlying_type_t; + return static_cast(detail::bitswap_impl(static_cast(x))); +} + } // namespace boost::safe_numbers #endif // BOOST_SAFE_NUMBERS_BIT_HPP diff --git a/test/test_bit.cpp b/test/test_bit.cpp index c937c1f..da6d635 100644 --- a/test/test_bit.cpp +++ b/test/test_bit.cpp @@ -321,6 +321,27 @@ void test_byteswap() BOOST_TEST(T{boost::core::byteswap(std::numeric_limits::max())} == boost::safe_numbers::byteswap(T{std::numeric_limits::max()})); } +template +void test_bitswap() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {0, std::numeric_limits::max()}; + + for (std::size_t i {}; i < N; ++i) + { + const auto raw {dist(rng)}; + const auto expected {detail::bitswap_impl(raw)}; + const T wrapped {raw}; + const auto result {boost::safe_numbers::bitswap(wrapped)}; + + BOOST_TEST(T{expected} == result); + } + + // Edge cases + BOOST_TEST(T{detail::bitswap_impl(static_cast(0))} == boost::safe_numbers::bitswap(T{0})); + BOOST_TEST(T{detail::bitswap_impl(std::numeric_limits::max())} == boost::safe_numbers::bitswap(T{std::numeric_limits::max()})); +} + // u128 tests use boost::int128 functions as reference since std:: doesn't support 128-bit types void test_has_single_bit_u128() @@ -517,6 +538,22 @@ void test_byteswap_u128() } } +void test_bitswap_u128() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {0, std::numeric_limits::max()}; + + for (std::size_t i {}; i < N; ++i) + { + const auto raw {dist(rng)}; + const auto expected {detail::bitswap_impl(raw)}; + const u128 wrapped {raw}; + const auto result {boost::safe_numbers::bitswap(wrapped)}; + + BOOST_TEST(u128{expected} == result); + } +} + // ----------------------------------------------- // bounded_uint types covering full underlying range // ----------------------------------------------- @@ -707,6 +744,13 @@ int main() test_byteswap_u128(); + test_bitswap(); + test_bitswap(); + test_bitswap(); + test_bitswap(); + + test_bitswap_u128(); + // Full-range bounded_uint tests (reuse existing templates) test_has_single_bit(); test_has_single_bit(); diff --git a/test/test_verified_bit.cpp b/test/test_verified_bit.cpp index e4a7309..72b092b 100644 --- a/test/test_verified_bit.cpp +++ b/test/test_verified_bit.cpp @@ -234,6 +234,23 @@ static_assert(test_byteswap_impl()); static_assert(test_byteswap_impl()); static_assert(test_byteswap_impl()); +// ============================================================================= +// bitswap (returns Int - compile-time only, non-bounded only) +// ============================================================================= + +template +consteval auto test_bitswap_impl() -> bool +{ + // bitswap(0) == 0 for any width + return boost::safe_numbers::bitswap(VerifiedT{BasisT{0}}) == VerifiedT{BasisT{0}}; +} + +static_assert(test_bitswap_impl()); +static_assert(test_bitswap_impl()); +static_assert(test_bitswap_impl()); +static_assert(test_bitswap_impl()); +static_assert(test_bitswap_impl()); + // ============================================================================= // Main - runtime tests for functions returning int/bool // =============================================================================