Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions doc/modules/ROOT/pages/bit.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -169,14 +169,28 @@ 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 <non_bounded_integral_library_type Int>
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]:

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++]
Expand All @@ -192,7 +206,7 @@ template <unsigned_library_type UnsignedInt>
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

Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions examples/bit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ int main()
const u32 w {0x12345678};
std::cout << std::hex;
std::cout << "byteswap(0x12345678) = 0x" << static_cast<std::uint32_t>(byteswap(w)) << '\n';
std::cout << "bitswap(0x12345678) = 0x" << static_cast<std::uint32_t>(bitswap(w)) << '\n';

std::cout << std::dec << '\n';

Expand Down
67 changes: 67 additions & 0 deletions include/boost/safe_numbers/bit.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#ifndef BOOST_SAFE_NUMBERS_BUILD_MODULE

#include <boost/core/bit.hpp>
#include <array>

#endif // BOOST_SAFE_NUMBERS_BUILD_MODULE

Expand Down Expand Up @@ -180,6 +181,72 @@ BOOST_SAFE_NUMBERS_EXPORT template <detail::non_bounded_integral_library_type In
return Int{byteswap(static_cast<underlying_type>(x))};
}

namespace detail {

consteval auto reverse_byte(std::uint8_t b) noexcept -> std::uint8_t
{
b = static_cast<std::uint8_t>(((b & UINT8_C(0x55)) << 1u) | ((b & UINT8_C(0xAA)) >> 1u));
b = static_cast<std::uint8_t>(((b & UINT8_C(0x33)) << 2u) | ((b & UINT8_C(0xCC)) >> 2u));
b = static_cast<std::uint8_t>(((b & UINT8_C(0x0F)) << 4u) | ((b & UINT8_C(0xF0)) >> 4u));

return b;
}

consteval auto make_byte_reverse_table() -> std::array<std::uint8_t, 256>
{
std::array<std::uint8_t, 256> table {};

for (int i {}; i < 256; ++i)
{
table[i] = reverse_byte(static_cast<std::uint8_t>(i));
}

return table;
}

inline constexpr auto reverse_table {make_byte_reverse_table()};

template <fundamental_unsigned_integral UnsignedInt>
[[nodiscard]] constexpr auto bitswap_impl(UnsignedInt x) noexcept -> UnsignedInt
{
if constexpr (sizeof(UnsignedInt) == 1)
{
return static_cast<UnsignedInt>(reverse_table[static_cast<std::uint8_t>(x)]);
}
else
{
constexpr auto n {sizeof(UnsignedInt)};

UnsignedInt result {};
for (std::size_t i {}; i < n; ++i)
{
result = static_cast<UnsignedInt>(static_cast<UnsignedInt>(result << 8U) |
static_cast<UnsignedInt>(reverse_table[static_cast<std::uint8_t>(x & 0xFFU)]));
x >>= 8U;
}

return result;
}
}

} // namespace detail

BOOST_SAFE_NUMBERS_EXPORT template <detail::non_bounded_integral_library_type Int>
requires (!detail::is_verified_type_v<Int>)
[[nodiscard]] constexpr auto bitswap(Int x) noexcept -> Int
{
using underlying_type = detail::underlying_type_t<Int>;
return static_cast<Int>(detail::bitswap_impl(static_cast<underlying_type>(x)));
}

BOOST_SAFE_NUMBERS_EXPORT template <detail::non_bounded_integral_library_type Int>
requires detail::is_verified_type_v<Int>
[[nodiscard]] consteval auto bitswap(const Int x) noexcept -> Int
{
using underlying_type = detail::underlying_type_t<Int>;
return static_cast<Int>(detail::bitswap_impl(static_cast<underlying_type>(x)));
}

} // namespace boost::safe_numbers

#endif // BOOST_SAFE_NUMBERS_BIT_HPP
44 changes: 44 additions & 0 deletions test/test_bit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,27 @@ void test_byteswap()
BOOST_TEST(T{boost::core::byteswap(std::numeric_limits<basis_type>::max())} == boost::safe_numbers::byteswap(T{std::numeric_limits<basis_type>::max()}));
}

template <typename T>
void test_bitswap()
{
using basis_type = detail::underlying_type_t<T>;
boost::random::uniform_int_distribution<basis_type> dist {0, std::numeric_limits<basis_type>::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<basis_type>(0))} == boost::safe_numbers::bitswap(T{0}));
BOOST_TEST(T{detail::bitswap_impl(std::numeric_limits<basis_type>::max())} == boost::safe_numbers::bitswap(T{std::numeric_limits<basis_type>::max()}));
}

// u128 tests use boost::int128 functions as reference since std:: doesn't support 128-bit types

void test_has_single_bit_u128()
Expand Down Expand Up @@ -517,6 +538,22 @@ void test_byteswap_u128()
}
}

void test_bitswap_u128()
{
using basis_type = detail::underlying_type_t<u128>;
boost::random::uniform_int_distribution<basis_type> dist {0, std::numeric_limits<basis_type>::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
// -----------------------------------------------
Expand Down Expand Up @@ -707,6 +744,13 @@ int main()

test_byteswap_u128();

test_bitswap<u8>();
test_bitswap<u16>();
test_bitswap<u32>();
test_bitswap<u64>();

test_bitswap_u128();

// Full-range bounded_uint tests (reuse existing templates)
test_has_single_bit<bounded_u8_full>();
test_has_single_bit<bounded_u16_full>();
Expand Down
17 changes: 17 additions & 0 deletions test/test_verified_bit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,23 @@ static_assert(test_byteswap_impl<verified_u32, u32>());
static_assert(test_byteswap_impl<verified_u64, u64>());
static_assert(test_byteswap_impl<verified_u128, u128>());

// =============================================================================
// bitswap (returns Int - compile-time only, non-bounded only)
// =============================================================================

template <typename VerifiedT, typename BasisT>
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<verified_u8, u8>());
static_assert(test_bitswap_impl<verified_u16, u16>());
static_assert(test_bitswap_impl<verified_u32, u32>());
static_assert(test_bitswap_impl<verified_u64, u64>());
static_assert(test_bitswap_impl<verified_u128, u128>());

// =============================================================================
// Main - runtime tests for functions returning int/bool
// =============================================================================
Expand Down