diff --git a/doc/modules/ROOT/nav.adoc b/doc/modules/ROOT/nav.adoc index 7e574a5..6525ad5 100644 --- a/doc/modules/ROOT/nav.adoc +++ b/doc/modules/ROOT/nav.adoc @@ -12,6 +12,7 @@ ** xref:examples.adoc#examples_charconv[Character Conversion] ** xref:examples.adoc#examples_fmt_format[Formatting] ** xref:examples.adoc#examples_iostream[Stream I/O] +** xref:examples.adoc#examples_bit[Bit Manipulation] * xref:api_reference.adoc[] ** xref:api_reference.adoc#api_types[Types] ** xref:api_reference.adoc#api_functions[Functions] @@ -23,4 +24,5 @@ * xref:format.adoc[] * xref:iostream.adoc[] * xref:charconv.adoc[] +* xref:bit.adoc[] * xref:reference.adoc[] diff --git a/doc/modules/ROOT/pages/api_reference.adoc b/doc/modules/ROOT/pages/api_reference.adoc index 3f53d95..815aebd 100644 --- a/doc/modules/ROOT/pages/api_reference.adoc +++ b/doc/modules/ROOT/pages/api_reference.adoc @@ -79,6 +79,49 @@ https://www.boost.org/LICENSE_1_0.txt | Stream insertion and extraction for all safe integer types |=== +=== Bit Manipulation + +[cols="1,2", options="header"] +|=== +| Function | Description + +| xref:bit.adoc[`has_single_bit`] +| Returns `true` if the value is a power of two + +| xref:bit.adoc[`bit_ceil`] +| Returns the smallest power of two not less than the value + +| xref:bit.adoc[`bit_floor`] +| Returns the largest power of two not greater than the value + +| xref:bit.adoc[`bit_width`] +| Returns the number of bits needed to represent the value + +| xref:bit.adoc[`rotl`] +| Bitwise left rotation + +| xref:bit.adoc[`rotr`] +| Bitwise right rotation + +| xref:bit.adoc[`countl_zero`] +| Counts consecutive zero bits from the most significant bit + +| xref:bit.adoc[`countl_one`] +| Counts consecutive one bits from the most significant bit + +| xref:bit.adoc[`countr_zero`] +| Counts consecutive zero bits from the least significant bit + +| xref:bit.adoc[`countr_one`] +| Counts consecutive one bits from the least significant bit + +| xref:bit.adoc[`popcount`] +| Returns the number of set bits + +| xref:bit.adoc[`byteswap`] +| Reverses the bytes of the value +|=== + === Arithmetic [cols="1,2", options="header"] @@ -114,6 +157,9 @@ https://www.boost.org/LICENSE_1_0.txt | `` | Convenience header including all library types +| `` +| Bit manipulation functions (`has_single_bit`, `bit_ceil`, `bit_floor`, `bit_width`, `rotl`, `rotr`, `countl_zero`, `countl_one`, `countr_zero`, `countr_one`, `popcount`, `byteswap`) + | `` | Character conversion functions (`to_chars`, `from_chars`) diff --git a/doc/modules/ROOT/pages/bit.adoc b/doc/modules/ROOT/pages/bit.adoc new file mode 100644 index 0000000..8f4d26f --- /dev/null +++ b/doc/modules/ROOT/pages/bit.adoc @@ -0,0 +1,194 @@ +//// +Copyright 2026 Matt Borland +Distributed under the Boost Software License, Version 1.0. +https://www.boost.org/LICENSE_1_0.txt +//// + +[#bit] += `` Support +:idprefix: bit_ + +== Description + +The library provides thin wrappers around the C++20 https://en.cppreference.com/w/cpp/header/bit.html[``] functions for safe integer types. +Each function extracts the underlying value, delegates to the corresponding `std::` function, and wraps the result back into the safe type where appropriate. +For `u128`, the functions delegate to the `boost::int128` implementations. + +[source,c++] +---- +#include +---- + +== Power-of-Two Functions + +=== has_single_bit + +[source,c++] +---- +template +constexpr auto has_single_bit(UnsignedInt x) noexcept -> bool; +---- + +Returns `true` if `x` is a power of two. +See https://en.cppreference.com/w/cpp/numeric/has_single_bit.html[`std::has_single_bit`]. + +=== bit_ceil + +[source,c++] +---- +template +constexpr auto bit_ceil(UnsignedInt x) noexcept -> UnsignedInt; +---- + +Returns the smallest power of two not less than `x`. +Undefined behavior if the result is not representable in the underlying type. +See https://en.cppreference.com/w/cpp/numeric/bit_ceil.html[`std::bit_ceil`]. + +=== bit_floor + +[source,c++] +---- +template +constexpr auto bit_floor(UnsignedInt x) noexcept -> UnsignedInt; +---- + +Returns the largest power of two not greater than `x`. +Returns zero if `x` is zero. +See https://en.cppreference.com/w/cpp/numeric/bit_floor.html[`std::bit_floor`]. + +=== bit_width + +[source,c++] +---- +template +constexpr auto bit_width(UnsignedInt x) noexcept -> int; +---- + +Returns the number of bits needed to represent `x` (i.e., 1 + floor(log2(x)) for x > 0, or 0 for x == 0). +See https://en.cppreference.com/w/cpp/numeric/bit_width.html[`std::bit_width`]. + +== Rotation Functions + +=== rotl + +[source,c++] +---- +template +constexpr auto rotl(UnsignedInt x, int s) noexcept -> UnsignedInt; +---- + +Computes the result of bitwise left-rotating `x` by `s` positions. +See https://en.cppreference.com/w/cpp/numeric/rotl.html[`std::rotl`]. + +=== rotr + +[source,c++] +---- +template +constexpr auto rotr(UnsignedInt x, int s) noexcept -> UnsignedInt; +---- + +Computes the result of bitwise right-rotating `x` by `s` positions. +See https://en.cppreference.com/w/cpp/numeric/rotr.html[`std::rotr`]. + +== Counting Functions + +=== countl_zero + +[source,c++] +---- +template +constexpr auto countl_zero(UnsignedInt x) noexcept -> int; +---- + +Returns the number of consecutive 0-bits starting from the most significant bit. +See https://en.cppreference.com/w/cpp/numeric/countl_zero.html[`std::countl_zero`]. + +=== countl_one + +[source,c++] +---- +template +constexpr auto countl_one(UnsignedInt x) noexcept -> int; +---- + +Returns the number of consecutive 1-bits starting from the most significant bit. +See https://en.cppreference.com/w/cpp/numeric/countl_one.html[`std::countl_one`]. + +=== countr_zero + +[source,c++] +---- +template +constexpr auto countr_zero(UnsignedInt x) noexcept -> int; +---- + +Returns the number of consecutive 0-bits starting from the least significant bit. +See https://en.cppreference.com/w/cpp/numeric/countr_zero.html[`std::countr_zero`]. + +=== countr_one + +[source,c++] +---- +template +constexpr auto countr_one(UnsignedInt x) noexcept -> int; +---- + +Returns the number of consecutive 1-bits starting from the least significant bit. +See https://en.cppreference.com/w/cpp/numeric/countr_one.html[`std::countr_one`]. + +=== popcount + +[source,c++] +---- +template +constexpr auto popcount(UnsignedInt x) noexcept -> int; +---- + +Returns the number of 1-bits in `x`. +See https://en.cppreference.com/w/cpp/numeric/popcount.html[`std::popcount`]. + +== Byte Manipulation + +=== byteswap + +[source,c++] +---- +template +constexpr auto byteswap(UnsignedInt x) noexcept -> UnsignedInt; +---- + +Reverses the bytes of `x`. +For standard unsigned types this delegates to `std::byteswap` (C++23). +For `u128` this delegates to the `boost::int128::byteswap` implementation. +See https://en.cppreference.com/w/cpp/numeric/byteswap.html[`std::byteswap`]. + +== Examples + +.This https://github.com/boostorg/safe_numbers/blob/develop/examples/bit.cpp[example] demonstrates the bit manipulation functions. +==== +[source, c++] +---- +include::example$bit.cpp[] +---- + +Output: +---- +has_single_bit(40) = 0 +has_single_bit(32) = 1 +bit_ceil(40) = 64 +bit_floor(40) = 32 +bit_width(40) = 6 + +rotl(0b10110001, 2) = 198 +rotr(0b10110001, 2) = 108 + +countl_zero(0x0F00) = 4 +countl_one(0x0F00) = 0 +countr_zero(0x0F00) = 8 +countr_one(0x0F00) = 0 +popcount(0x0F00) = 4 + +byteswap(0x12345678) = 0x78563412 +---- +==== diff --git a/doc/modules/ROOT/pages/examples.adoc b/doc/modules/ROOT/pages/examples.adoc index 5147b03..5b9d955 100644 --- a/doc/modules/ROOT/pages/examples.adoc +++ b/doc/modules/ROOT/pages/examples.adoc @@ -327,6 +327,40 @@ Roundtrip: 18446744073709551615 -> "18446744073709551615" -> 1844674407370955161 ---- ==== +[#examples_bit] +== Bit Manipulation + +The library provides thin wrappers around all C++20 `` functions for safe integer types. +Each function extracts the underlying value, delegates to the standard library, and wraps the result back. + +.This https://github.com/boostorg/safe_numbers/blob/develop/examples/bit.cpp[example] demonstrates the bit manipulation functions. +==== +[source, c++] +---- +include::example$bit.cpp[] +---- + +Output: +---- +has_single_bit(40) = 0 +has_single_bit(32) = 1 +bit_ceil(40) = 64 +bit_floor(40) = 32 +bit_width(40) = 6 + +rotl(0b10110001, 2) = 198 +rotr(0b10110001, 2) = 108 + +countl_zero(0x0F00) = 4 +countl_one(0x0F00) = 0 +countr_zero(0x0F00) = 8 +countr_one(0x0F00) = 0 +popcount(0x0F00) = 4 + +byteswap(0x12345678) = 0x78563412 +---- +==== + [#examples_policy_comparison] == Policy Comparison diff --git a/examples/bit.cpp b/examples/bit.cpp new file mode 100644 index 0000000..c3cf48a --- /dev/null +++ b/examples/bit.cpp @@ -0,0 +1,47 @@ +// 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 u32 x {0b0000'0000'0000'0000'0000'0000'0010'1000}; // 40 + + // Power-of-two queries + std::cout << "has_single_bit(40) = " << has_single_bit(x) << '\n'; + std::cout << "has_single_bit(32) = " << has_single_bit(u32{32}) << '\n'; + std::cout << "bit_ceil(40) = " << static_cast(bit_ceil(x)) << '\n'; + std::cout << "bit_floor(40) = " << static_cast(bit_floor(x)) << '\n'; + std::cout << "bit_width(40) = " << bit_width(x) << '\n'; + + std::cout << '\n'; + + // Rotation + const u8 y {0b1011'0001}; // 177 + std::cout << "rotl(0b10110001, 2) = " << static_cast(rotl(y, 2)) << '\n'; + std::cout << "rotr(0b10110001, 2) = " << static_cast(rotr(y, 2)) << '\n'; + + std::cout << '\n'; + + // Counting + const u16 z {0b0000'1111'0000'0000}; // 3840 + std::cout << "countl_zero(0x0F00) = " << countl_zero(z) << '\n'; + std::cout << "countl_one(0x0F00) = " << countl_one(z) << '\n'; + std::cout << "countr_zero(0x0F00) = " << countr_zero(z) << '\n'; + std::cout << "countr_one(0x0F00) = " << countr_one(z) << '\n'; + std::cout << "popcount(0x0F00) = " << popcount(z) << '\n'; + + std::cout << '\n'; + + // Byteswap + const u32 w {0x12345678}; + std::cout << std::hex; + std::cout << "byteswap(0x12345678) = 0x" << static_cast(byteswap(w)) << '\n'; + + return 0; +} diff --git a/include/boost/safe_numbers.hpp b/include/boost/safe_numbers.hpp index b039c3a..3bcfe15 100644 --- a/include/boost/safe_numbers.hpp +++ b/include/boost/safe_numbers.hpp @@ -11,5 +11,6 @@ #include #include #include +#include #endif //BOOST_SAFENUMBERS_HPP diff --git a/include/boost/safe_numbers/bit.hpp b/include/boost/safe_numbers/bit.hpp new file mode 100644 index 0000000..df892a8 --- /dev/null +++ b/include/boost/safe_numbers/bit.hpp @@ -0,0 +1,131 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_SAFE_NUMBERS_BIT_HPP +#define BOOST_SAFE_NUMBERS_BIT_HPP + +#include +#include +#include +#include + +#ifndef BOOST_SAFE_NUMBERS_BUILD_MODULE + +#include + +#endif // BOOST_SAFE_NUMBERS_BUILD_MODULE + +namespace boost::safe_numbers { + +BOOST_SAFE_NUMBERS_EXPORT template +constexpr auto has_single_bit(const UnsignedInt x) noexcept -> bool +{ + using boost::core::has_single_bit; + using basis_type = typename UnsignedInt::basis_type; + + return has_single_bit(static_cast(x)); +} + +BOOST_SAFE_NUMBERS_EXPORT template +constexpr auto bit_ceil(const UnsignedInt x) noexcept -> UnsignedInt +{ + using boost::core::bit_ceil; + using basis_type = typename UnsignedInt::basis_type; + + return static_cast(bit_ceil(static_cast(x))); +} + +BOOST_SAFE_NUMBERS_EXPORT template +constexpr auto bit_floor(const UnsignedInt x) noexcept -> UnsignedInt +{ + using boost::core::bit_floor; + using basis_type = typename UnsignedInt::basis_type; + + return static_cast(bit_floor(static_cast(x))); +} + +BOOST_SAFE_NUMBERS_EXPORT template +constexpr auto bit_width(const UnsignedInt x) noexcept -> int +{ + using boost::core::bit_width; + using basis_type = typename UnsignedInt::basis_type; + + return static_cast(bit_width(static_cast(x))); +} + +BOOST_SAFE_NUMBERS_EXPORT template +constexpr auto rotl(const UnsignedInt x, const int s) noexcept -> UnsignedInt +{ + using boost::core::rotl; + using basis_type = typename UnsignedInt::basis_type; + + return static_cast(rotl(static_cast(x), s)); +} + +BOOST_SAFE_NUMBERS_EXPORT template +constexpr auto rotr(const UnsignedInt x, const int s) noexcept -> UnsignedInt +{ + using boost::core::rotr; + using basis_type = typename UnsignedInt::basis_type; + + return static_cast(rotr(static_cast(x), s)); +} + +BOOST_SAFE_NUMBERS_EXPORT template +constexpr auto countl_zero(const UnsignedInt x) noexcept -> int +{ + using boost::core::countl_zero; + using basis_type = typename UnsignedInt::basis_type; + + return countl_zero(static_cast(x)); +} + +BOOST_SAFE_NUMBERS_EXPORT template +constexpr auto countl_one(const UnsignedInt x) noexcept -> int +{ + using boost::core::countl_one; + using basis_type = typename UnsignedInt::basis_type; + + return countl_one(static_cast(x)); +} + +BOOST_SAFE_NUMBERS_EXPORT template +constexpr auto countr_zero(const UnsignedInt x) noexcept -> int +{ + using boost::core::countr_zero; + using basis_type = typename UnsignedInt::basis_type; + + return countr_zero(static_cast(x)); +} + +BOOST_SAFE_NUMBERS_EXPORT template +constexpr auto countr_one(const UnsignedInt x) noexcept -> int +{ + using boost::core::countr_one; + using basis_type = typename UnsignedInt::basis_type; + + return countr_one(static_cast(x)); +} + +BOOST_SAFE_NUMBERS_EXPORT template +constexpr auto popcount(const UnsignedInt x) noexcept -> int +{ + using boost::core::popcount; + using basis_type = typename UnsignedInt::basis_type; + + return popcount(static_cast(x)); +} + +BOOST_SAFE_NUMBERS_EXPORT template +constexpr auto byteswap(const UnsignedInt x) noexcept -> UnsignedInt +{ + using boost::core::byteswap; + using basis_type = typename UnsignedInt::basis_type; + + return static_cast(byteswap(static_cast(x))); +} + +} // namespace boost::safe_numbers + +#endif // BOOST_SAFE_NUMBERS_BIT_HPP diff --git a/include/boost/safe_numbers/detail/type_traits.hpp b/include/boost/safe_numbers/detail/type_traits.hpp index 25af6f3..f6e1f64 100644 --- a/include/boost/safe_numbers/detail/type_traits.hpp +++ b/include/boost/safe_numbers/detail/type_traits.hpp @@ -38,6 +38,22 @@ concept library_type = is_library_type_v; namespace impl { +template +struct is_unsigned_library_type : std::false_type {}; + +template +struct is_unsigned_library_type> : std::true_type {}; + +} // namespace impl + +template +inline constexpr bool is_unsigned_library_type_v = impl::is_unsigned_library_type::value; + +template +concept unsigned_library_type = is_unsigned_library_type_v; + +namespace impl { + template struct underlying { diff --git a/test/Jamfile b/test/Jamfile index 461aeeb..b2bea52 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -69,6 +69,7 @@ run test_unsigned_inc_dec.cpp ; run test_unsigned_std_format.cpp ; run test_unsigned_fmt_format.cpp ; run test_unsigned_charconv.cpp ; +run test_bit.cpp ; run test_unsigned_saturating_addition.cpp ; run test_unsigned_comparisons.cpp ; run test_unsigned_saturating_subtraction.cpp ; @@ -124,3 +125,4 @@ run ../examples/literals.cpp ; run ../examples/fmt_format.cpp ; run ../examples/iostream.cpp ; run ../examples/charconv.cpp ; +run ../examples/bit.cpp ; diff --git a/test/test_bit.cpp b/test/test_bit.cpp new file mode 100644 index 0000000..ee1ecab --- /dev/null +++ b/test/test_bit.cpp @@ -0,0 +1,596 @@ +// 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 + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include +#include +#include +#include +#include + +#endif + +using namespace boost::safe_numbers; + +inline std::mt19937_64 rng{42}; +inline constexpr std::size_t N {1024}; + +template +void test_has_single_bit() +{ + 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 {std::has_single_bit(raw)}; + const T wrapped {raw}; + const auto result {boost::safe_numbers::has_single_bit(wrapped)}; + + BOOST_TEST_EQ(expected, result); + } + + // Edge cases + BOOST_TEST_EQ(std::has_single_bit(static_cast(0)), boost::safe_numbers::has_single_bit(T{0})); + BOOST_TEST_EQ(std::has_single_bit(static_cast(1)), boost::safe_numbers::has_single_bit(T{1})); + BOOST_TEST_EQ(std::has_single_bit(static_cast(2)), boost::safe_numbers::has_single_bit(T{2})); +} + +template +void test_bit_ceil() +{ + using basis_type = detail::underlying_type_t; + + // bit_ceil has UB if the result isn't representable, so use small values + boost::random::uniform_int_distribution dist {1, static_cast(std::numeric_limits::max() >> 1)}; + + for (std::size_t i {}; i < N; ++i) + { + const auto raw {dist(rng)}; + const auto expected {std::bit_ceil(raw)}; + const T wrapped {raw}; + const auto result {boost::safe_numbers::bit_ceil(wrapped)}; + + BOOST_TEST(T{expected} == result); + } + + // Edge cases + BOOST_TEST(T{std::bit_ceil(static_cast(1))} == boost::safe_numbers::bit_ceil(T{1})); + BOOST_TEST(T{std::bit_ceil(static_cast(2))} == boost::safe_numbers::bit_ceil(T{2})); + BOOST_TEST(T{std::bit_ceil(static_cast(3))} == boost::safe_numbers::bit_ceil(T{3})); +} + +template +void test_bit_floor() +{ + 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 {std::bit_floor(raw)}; + const T wrapped {raw}; + const auto result {boost::safe_numbers::bit_floor(wrapped)}; + + BOOST_TEST(T{expected} == result); + } + + // Edge cases + BOOST_TEST(T{std::bit_floor(static_cast(0))} == boost::safe_numbers::bit_floor(T{0})); + BOOST_TEST(T{std::bit_floor(static_cast(1))} == boost::safe_numbers::bit_floor(T{1})); + BOOST_TEST(T{std::bit_floor(static_cast(7))} == boost::safe_numbers::bit_floor(T{7})); +} + +template +void test_bit_width() +{ + 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 {std::bit_width(raw)}; + const T wrapped {raw}; + const auto result {boost::safe_numbers::bit_width(wrapped)}; + + BOOST_TEST_EQ(expected, result); + } + + // Edge cases + BOOST_TEST_EQ(std::bit_width(static_cast(0)), boost::safe_numbers::bit_width(T{0})); + BOOST_TEST_EQ(std::bit_width(static_cast(1)), boost::safe_numbers::bit_width(T{1})); + BOOST_TEST_EQ(std::bit_width(std::numeric_limits::max()), boost::safe_numbers::bit_width(T{std::numeric_limits::max()})); +} + +template +void test_rotl() +{ + 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 s {static_cast(i % (sizeof(basis_type) * 8U))}; + const auto expected {std::rotl(raw, s)}; + const T wrapped {raw}; + const auto result {boost::safe_numbers::rotl(wrapped, s)}; + + BOOST_TEST(T{expected} == result); + } +} + +template +void test_rotr() +{ + 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 s {static_cast(i % (sizeof(basis_type) * 8U))}; + const auto expected {std::rotr(raw, s)}; + const T wrapped {raw}; + const auto result {boost::safe_numbers::rotr(wrapped, s)}; + + BOOST_TEST(T{expected} == result); + } +} + +template +void test_countl_zero() +{ + 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 {std::countl_zero(raw)}; + const T wrapped {raw}; + const auto result {boost::safe_numbers::countl_zero(wrapped)}; + + BOOST_TEST_EQ(expected, result); + } + + // Edge cases + BOOST_TEST_EQ(std::countl_zero(static_cast(0)), boost::safe_numbers::countl_zero(T{0})); + BOOST_TEST_EQ(std::countl_zero(std::numeric_limits::max()), boost::safe_numbers::countl_zero(T{std::numeric_limits::max()})); +} + +template +void test_countl_one() +{ + 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 {std::countl_one(raw)}; + const T wrapped {raw}; + const auto result {boost::safe_numbers::countl_one(wrapped)}; + + BOOST_TEST_EQ(expected, result); + } + + // Edge cases + BOOST_TEST_EQ(std::countl_one(static_cast(0)), boost::safe_numbers::countl_one(T{0})); + BOOST_TEST_EQ(std::countl_one(std::numeric_limits::max()), boost::safe_numbers::countl_one(T{std::numeric_limits::max()})); +} + +template +void test_countr_zero() +{ + 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 {std::countr_zero(raw)}; + const T wrapped {raw}; + const auto result {boost::safe_numbers::countr_zero(wrapped)}; + + BOOST_TEST_EQ(expected, result); + } + + // Edge cases + BOOST_TEST_EQ(std::countr_zero(static_cast(0)), boost::safe_numbers::countr_zero(T{0})); + BOOST_TEST_EQ(std::countr_zero(std::numeric_limits::max()), boost::safe_numbers::countr_zero(T{std::numeric_limits::max()})); +} + +template +void test_countr_one() +{ + 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 {std::countr_one(raw)}; + const T wrapped {raw}; + const auto result {boost::safe_numbers::countr_one(wrapped)}; + + BOOST_TEST_EQ(expected, result); + } + + // Edge cases + BOOST_TEST_EQ(std::countr_one(static_cast(0)), boost::safe_numbers::countr_one(T{0})); + BOOST_TEST_EQ(std::countr_one(std::numeric_limits::max()), boost::safe_numbers::countr_one(T{std::numeric_limits::max()})); +} + +template +void test_popcount() +{ + 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 {std::popcount(raw)}; + const T wrapped {raw}; + const auto result {boost::safe_numbers::popcount(wrapped)}; + + BOOST_TEST_EQ(expected, result); + } + + // Edge cases + BOOST_TEST_EQ(std::popcount(static_cast(0)), boost::safe_numbers::popcount(T{0})); + BOOST_TEST_EQ(std::popcount(std::numeric_limits::max()), boost::safe_numbers::popcount(T{std::numeric_limits::max()})); +} + +template +void test_byteswap() +{ + 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 {boost::core::byteswap(raw)}; + const T wrapped {raw}; + const auto result {boost::safe_numbers::byteswap(wrapped)}; + + BOOST_TEST(T{expected} == result); + } + + // Edge cases + BOOST_TEST(T{boost::core::byteswap(static_cast(0))} == boost::safe_numbers::byteswap(T{0})); + BOOST_TEST(T{boost::core::byteswap(std::numeric_limits::max())} == boost::safe_numbers::byteswap(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() +{ + 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 {boost::int128::has_single_bit(raw)}; + const u128 wrapped {raw}; + const auto result {boost::safe_numbers::has_single_bit(wrapped)}; + + BOOST_TEST_EQ(expected, result); + } +} + +void test_bit_ceil_u128() +{ + using basis_type = detail::underlying_type_t; + boost::random::uniform_int_distribution dist {1, static_cast(std::numeric_limits::max() >> 1)}; + + for (std::size_t i {}; i < N; ++i) + { + const auto raw {dist(rng)}; + const auto expected {boost::int128::bit_ceil(raw)}; + const u128 wrapped {raw}; + const auto result {boost::safe_numbers::bit_ceil(wrapped)}; + + BOOST_TEST(u128{expected} == result); + } +} + +void test_bit_floor_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 {boost::int128::bit_floor(raw)}; + const u128 wrapped {raw}; + const auto result {boost::safe_numbers::bit_floor(wrapped)}; + + BOOST_TEST(u128{expected} == result); + } +} + +void test_bit_width_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 {boost::int128::bit_width(raw)}; + const u128 wrapped {raw}; + const auto result {boost::safe_numbers::bit_width(wrapped)}; + + BOOST_TEST_EQ(expected, result); + } +} + +void test_rotl_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 s {static_cast(i % 128U)}; + const auto expected {boost::int128::rotl(raw, s)}; + const u128 wrapped {raw}; + const auto result {boost::safe_numbers::rotl(wrapped, s)}; + + BOOST_TEST(u128{expected} == result); + } +} + +void test_rotr_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 s {static_cast(i % 128U)}; + const auto expected {boost::int128::rotr(raw, s)}; + const u128 wrapped {raw}; + const auto result {boost::safe_numbers::rotr(wrapped, s)}; + + BOOST_TEST(u128{expected} == result); + } +} + +void test_countl_zero_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 {boost::int128::countl_zero(raw)}; + const u128 wrapped {raw}; + const auto result {boost::safe_numbers::countl_zero(wrapped)}; + + BOOST_TEST_EQ(expected, result); + } +} + +void test_countl_one_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 {boost::int128::countl_one(raw)}; + const u128 wrapped {raw}; + const auto result {boost::safe_numbers::countl_one(wrapped)}; + + BOOST_TEST_EQ(expected, result); + } +} + +void test_countr_zero_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 {boost::int128::countr_zero(raw)}; + const u128 wrapped {raw}; + const auto result {boost::safe_numbers::countr_zero(wrapped)}; + + BOOST_TEST_EQ(expected, result); + } +} + +void test_countr_one_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 {boost::int128::countr_one(raw)}; + const u128 wrapped {raw}; + const auto result {boost::safe_numbers::countr_one(wrapped)}; + + BOOST_TEST_EQ(expected, result); + } +} + +void test_popcount_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 {boost::int128::popcount(raw)}; + const u128 wrapped {raw}; + const auto result {boost::safe_numbers::popcount(wrapped)}; + + BOOST_TEST_EQ(expected, result); + } +} + +void test_byteswap_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 {boost::int128::byteswap(raw)}; + const u128 wrapped {raw}; + const auto result {boost::safe_numbers::byteswap(wrapped)}; + + BOOST_TEST(u128{expected} == result); + } +} + +int main() +{ + test_has_single_bit(); + test_has_single_bit(); + test_has_single_bit(); + test_has_single_bit(); + test_has_single_bit_u128(); + + test_bit_ceil(); + test_bit_ceil(); + test_bit_ceil(); + test_bit_ceil(); + test_bit_ceil_u128(); + + test_bit_floor(); + test_bit_floor(); + test_bit_floor(); + test_bit_floor(); + test_bit_floor_u128(); + + test_bit_width(); + test_bit_width(); + test_bit_width(); + test_bit_width(); + test_bit_width_u128(); + + test_rotl(); + test_rotl(); + test_rotl(); + test_rotl(); + test_rotl_u128(); + + test_rotr(); + test_rotr(); + test_rotr(); + test_rotr(); + test_rotr_u128(); + + test_countl_zero(); + test_countl_zero(); + test_countl_zero(); + test_countl_zero(); + test_countl_zero_u128(); + + test_countl_one(); + test_countl_one(); + test_countl_one(); + test_countl_one(); + test_countl_one_u128(); + + test_countr_zero(); + test_countr_zero(); + test_countr_zero(); + test_countr_zero(); + test_countr_zero_u128(); + + test_countr_one(); + test_countr_one(); + test_countr_one(); + test_countr_one(); + test_countr_one_u128(); + + test_popcount(); + test_popcount(); + test_popcount(); + test_popcount(); + test_popcount_u128(); + + test_byteswap(); + test_byteswap(); + test_byteswap(); + test_byteswap(); + + test_byteswap_u128(); + + return boost::report_errors(); +}