diff --git a/doc/modules/ROOT/pages/api_reference.adoc b/doc/modules/ROOT/pages/api_reference.adoc index 34b6dfe..bb7c4dc 100644 --- a/doc/modules/ROOT/pages/api_reference.adoc +++ b/doc/modules/ROOT/pages/api_reference.adoc @@ -171,6 +171,9 @@ https://www.boost.org/LICENSE_1_0.txt | xref:integer_utilities.adoc[`is_power_10`] | Tests whether a safe unsigned integer is an exact power of 10 + +| xref:integer_utilities.adoc[`is_power_2`] +| Tests whether a safe unsigned integer is an exact power of 2 |=== === Arithmetic diff --git a/doc/modules/ROOT/pages/integer_utilities.adoc b/doc/modules/ROOT/pages/integer_utilities.adoc index 0b29561..c1687f4 100644 --- a/doc/modules/ROOT/pages/integer_utilities.adoc +++ b/doc/modules/ROOT/pages/integer_utilities.adoc @@ -114,7 +114,7 @@ It processes the exponent in O(log(max_digits)) steps, where `max_digits` is the ==== Parameters -* `n` -- The value to remove trailing zeros from. Must be non-zero. +* `n` -- The value to remove trailing zeros from. If `n` is zero, returns `{0, 0}`. ==== Return Value @@ -174,7 +174,7 @@ template consteval auto remove_trailing_zeros(const verified_type_basis val); ---- -Compile-time only overload for verified types. +Compile-time-only overload for verified types. Delegates to the detail implementation after extracting the underlying value. Since `remove_trailing_zeros` is `consteval` for verified types, the result is guaranteed to be a compile-time constant. @@ -193,8 +193,6 @@ constexpr auto r = remove_trailing_zeros(verified_u32{u32{5000}}); Tests whether an unsigned integer value is an exact power of 10 (i.e., one of 1, 10, 100, 1000, ...). -Implemented using `remove_trailing_zeros` and checking that the trimmed result equals 1. - === Runtime Overload [source,c++] @@ -206,7 +204,7 @@ constexpr auto is_power_10(const T n) -> bool; ==== Parameters -* `n` -- The value to test. Must be non-zero. +* `n` -- The value to test. ==== Return Value @@ -232,7 +230,7 @@ template consteval auto is_power_10(const verified_type_basis n) -> bool; ---- -Compile-time only overload for verified types. +Compile-time-only overload for verified types. Since `is_power_10` is `consteval` for verified types, the result is guaranteed to be a compile-time constant and can be used directly in `static_assert`. @@ -245,3 +243,58 @@ using namespace boost::safe_numbers; static_assert(is_power_10(verified_u32{u32{100}})); static_assert(!is_power_10(verified_u32{u32{200}})); ---- + +== is_power_2 + +Tests whether an unsigned integer value is an exact power of 2 (i.e., has exactly one bit set). + +=== Runtime Overload + +[source,c++] +---- +template + requires (!is_verified_type_v) +constexpr auto is_power_2(const T n) noexcept -> bool; +---- + +==== Parameters + +* `n` -- The value to test. + +==== Return Value + +`true` if `n` is a power of 2, `false` otherwise. + +==== Example + +[source,c++] +---- +using namespace boost::safe_numbers; + +is_power_2(u32{1024}); // true +is_power_2(u32{1}); // true +is_power_2(u32{1000}); // false +is_power_2(u64{9223372036854775808ULL}); // true (2^63) +---- + +=== Verified Overload + +[source,c++] +---- +template +consteval auto is_power_2(const verified_type_basis n) noexcept -> bool; +---- + +Compile-time-only overload for verified types. + +Since `is_power_2` is `consteval` for verified types, the result is guaranteed to be a compile-time constant and can be used directly in `static_assert`. + +==== Example + +[source,c++] +---- +using namespace boost::safe_numbers; + +static_assert(is_power_2(verified_u32{u32{1048576}})); // 2^20 +static_assert(!is_power_2(verified_u32{u32{1000000}})); +---- diff --git a/include/boost/safe_numbers/integer_utilities.hpp b/include/boost/safe_numbers/integer_utilities.hpp index e47bbea..4c85a0d 100644 --- a/include/boost/safe_numbers/integer_utilities.hpp +++ b/include/boost/safe_numbers/integer_utilities.hpp @@ -8,6 +8,7 @@ #include #include #include +#include namespace boost::safe_numbers { @@ -51,6 +52,12 @@ template constexpr auto remove_trailing_zeros(const T n) { using underlying = typename detail::underlying_type_t; + + if (static_cast(n) == static_cast(0)) + { + return detail::remove_trailing_zeros_return{static_cast(0), static_cast(0)}; + } + return detail::remove_trailing_zeros(static_cast(n)); } @@ -58,6 +65,12 @@ template consteval auto remove_trailing_zeros(const detail::verified_type_basis val) { using underlying = typename detail::underlying_type_t; + + if (static_cast(val) == static_cast(0)) + { + return detail::remove_trailing_zeros_return{static_cast(0), static_cast(0)}; + } + return detail::remove_trailing_zeros(static_cast(val)); } @@ -80,6 +93,19 @@ consteval auto is_power_10(const detail::verified_type_basis n) -> bool return trimmed_number == static_cast(1); } +template + requires (!detail::is_verified_type_v) +constexpr auto is_power_2(const T n) noexcept -> bool +{ + return has_single_bit(n); +} + +template +consteval auto is_power_2(const detail::verified_type_basis n) noexcept -> bool +{ + return has_single_bit(n); +} + } // namespace boost::safe_numbers #endif // BOOST_SAFE_NUMBERS_INTEGER_UTILITIES_HPP diff --git a/test/Jamfile b/test/Jamfile index cd0932e..921f388 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -133,6 +133,7 @@ run test_verified_limits.cpp ; run test_isqrt.cpp ; run test_remove_trailing_zeros.cpp ; run test_is_power_10.cpp ; +run test_is_power_2.cpp ; # Compile Tests compile compile_tests/compile_test_unsigned_integers.cpp ; diff --git a/test/test_is_power_10.cpp b/test/test_is_power_10.cpp index a7758c6..ff46a53 100644 --- a/test/test_is_power_10.cpp +++ b/test/test_is_power_10.cpp @@ -199,8 +199,41 @@ void test_is_power_10_verified() static_assert(!is_power_10(verified_u128{u128{uint128_t{UINT64_C(100001)}}})); } +// ============================================================================= +// Zero input tests +// ============================================================================= + +template +void test_is_power_10_zero() +{ + using underlying = typename detail::underlying_type_t; + BOOST_TEST(!is_power_10(T{static_cast(0)})); +} + +void test_is_power_10_zero_constexpr() +{ + static_assert(!is_power_10(u8{static_cast(0)})); + static_assert(!is_power_10(u32{UINT32_C(0)})); + static_assert(!is_power_10(u64{UINT64_C(0)})); +} + +void test_is_power_10_zero_verified() +{ + static_assert(!is_power_10(verified_u8{u8{static_cast(0)}})); + static_assert(!is_power_10(verified_u32{u32{UINT32_C(0)}})); +} + int main() { + // Zero input - all types + test_is_power_10_zero(); + test_is_power_10_zero(); + test_is_power_10_zero(); + test_is_power_10_zero(); + test_is_power_10_zero(); + test_is_power_10_zero_constexpr(); + test_is_power_10_zero_verified(); + // Powers of 10 - all types test_is_power_10_true(); test_is_power_10_true(); diff --git a/test/test_is_power_2.cpp b/test/test_is_power_2.cpp new file mode 100644 index 0000000..227cc19 --- /dev/null +++ b/test/test_is_power_2.cpp @@ -0,0 +1,280 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include +#include + +#endif + +#include + +using namespace boost::safe_numbers; + +// ============================================================================= +// Runtime tests: powers of 2 +// ============================================================================= + +template +void test_is_power_2_true() +{ + using underlying = typename detail::underlying_type_t; + + BOOST_TEST(is_power_2(T{static_cast(1)})); + BOOST_TEST(is_power_2(T{static_cast(2)})); + BOOST_TEST(is_power_2(T{static_cast(4)})); + BOOST_TEST(is_power_2(T{static_cast(8)})); + BOOST_TEST(is_power_2(T{static_cast(16)})); + BOOST_TEST(is_power_2(T{static_cast(32)})); + BOOST_TEST(is_power_2(T{static_cast(64)})); + BOOST_TEST(is_power_2(T{static_cast(128)})); +} + +void test_is_power_2_true_u16() +{ + BOOST_TEST(is_power_2(u16{static_cast(256)})); + BOOST_TEST(is_power_2(u16{static_cast(512)})); + BOOST_TEST(is_power_2(u16{static_cast(1024)})); + BOOST_TEST(is_power_2(u16{static_cast(2048)})); + BOOST_TEST(is_power_2(u16{static_cast(4096)})); + BOOST_TEST(is_power_2(u16{static_cast(8192)})); + BOOST_TEST(is_power_2(u16{static_cast(16384)})); + BOOST_TEST(is_power_2(u16{static_cast(32768)})); +} + +void test_is_power_2_true_u32() +{ + BOOST_TEST(is_power_2(u32{UINT32_C(65536)})); + BOOST_TEST(is_power_2(u32{UINT32_C(1048576)})); + BOOST_TEST(is_power_2(u32{UINT32_C(1073741824)})); + BOOST_TEST(is_power_2(u32{UINT32_C(2147483648)})); +} + +void test_is_power_2_true_u64() +{ + BOOST_TEST(is_power_2(u64{UINT64_C(4294967296)})); + BOOST_TEST(is_power_2(u64{UINT64_C(1099511627776)})); + BOOST_TEST(is_power_2(u64{UINT64_C(9223372036854775808)})); +} + +void test_is_power_2_true_u128() +{ + using boost::int128::uint128_t; + + BOOST_TEST(is_power_2(u128{uint128_t{1}})); + BOOST_TEST(is_power_2(u128{uint128_t{2}})); + BOOST_TEST(is_power_2(u128{uint128_t{UINT64_C(4294967296)}})); + + // 2^64 + const auto two_pow_64 = uint128_t{1} << 64; + BOOST_TEST(is_power_2(u128{two_pow_64})); + + // 2^100 + const auto two_pow_100 = uint128_t{1} << 100; + BOOST_TEST(is_power_2(u128{two_pow_100})); + + // 2^127 + const auto two_pow_127 = uint128_t{1} << 127; + BOOST_TEST(is_power_2(u128{two_pow_127})); +} + +// ============================================================================= +// Runtime tests: non-powers of 2 +// ============================================================================= + +template +void test_is_power_2_false() +{ + using underlying = typename detail::underlying_type_t; + + BOOST_TEST(!is_power_2(T{static_cast(3)})); + BOOST_TEST(!is_power_2(T{static_cast(5)})); + BOOST_TEST(!is_power_2(T{static_cast(6)})); + BOOST_TEST(!is_power_2(T{static_cast(7)})); + BOOST_TEST(!is_power_2(T{static_cast(9)})); + BOOST_TEST(!is_power_2(T{static_cast(10)})); + BOOST_TEST(!is_power_2(T{static_cast(12)})); + BOOST_TEST(!is_power_2(T{static_cast(15)})); + BOOST_TEST(!is_power_2(T{static_cast(100)})); + BOOST_TEST(!is_power_2(T{static_cast(255)})); +} + +void test_is_power_2_false_u32() +{ + BOOST_TEST(!is_power_2(u32{UINT32_C(3)})); + BOOST_TEST(!is_power_2(u32{UINT32_C(1000)})); + BOOST_TEST(!is_power_2(u32{UINT32_C(1073741823)})); + BOOST_TEST(!is_power_2(u32{UINT32_C(1073741825)})); + BOOST_TEST(!is_power_2(u32{UINT32_C(4294967295)})); +} + +void test_is_power_2_false_u64() +{ + BOOST_TEST(!is_power_2(u64{UINT64_C(4294967297)})); + BOOST_TEST(!is_power_2(u64{UINT64_C(10000000000000000000)})); + BOOST_TEST(!is_power_2(u64{UINT64_MAX})); +} + +void test_is_power_2_false_u128() +{ + using boost::int128::uint128_t; + + BOOST_TEST(!is_power_2(u128{uint128_t{3}})); + + // 2^64 + 1 + const auto two_pow_64_plus_1 = (uint128_t{1} << 64) + uint128_t{1}; + BOOST_TEST(!is_power_2(u128{two_pow_64_plus_1})); + + // 2^100 - 1 + const auto two_pow_100_minus_1 = (uint128_t{1} << 100) - uint128_t{1}; + BOOST_TEST(!is_power_2(u128{two_pow_100_minus_1})); +} + +// ============================================================================= +// Exhaustive tests for u8 and u16 +// ============================================================================= + +void test_is_power_2_exhaustive_u8() +{ + for (unsigned i {1}; i <= 255; ++i) + { + const auto n = static_cast(i); + auto result = is_power_2(u8{n}); + + const auto expected = (i & (i - 1)) == 0; + BOOST_TEST_EQ(result, expected); + } +} + +void test_is_power_2_exhaustive_u16() +{ + for (unsigned i {1}; i <= 65535; ++i) + { + const auto n = static_cast(i); + auto result = is_power_2(u16{n}); + + const auto expected = (i & (i - 1)) == 0; + BOOST_TEST_EQ(result, expected); + } +} + +// ============================================================================= +// Constexpr tests +// ============================================================================= + +void test_is_power_2_constexpr() +{ + static_assert(is_power_2(u8{static_cast(1)})); + static_assert(is_power_2(u8{static_cast(2)})); + static_assert(is_power_2(u8{static_cast(128)})); + static_assert(!is_power_2(u8{static_cast(3)})); + static_assert(!is_power_2(u8{static_cast(255)})); + + static_assert(is_power_2(u16{static_cast(32768)})); + static_assert(!is_power_2(u16{static_cast(1000)})); + + static_assert(is_power_2(u32{UINT32_C(2147483648)})); + static_assert(!is_power_2(u32{UINT32_C(2147483647)})); + + static_assert(is_power_2(u64{UINT64_C(9223372036854775808)})); + static_assert(!is_power_2(u64{UINT64_C(9223372036854775807)})); +} + +// ============================================================================= +// Verified type tests (consteval) +// ============================================================================= + +void test_is_power_2_verified() +{ + static_assert(is_power_2(verified_u8{u8{static_cast(1)}})); + static_assert(is_power_2(verified_u8{u8{static_cast(64)}})); + static_assert(!is_power_2(verified_u8{u8{static_cast(5)}})); + + static_assert(is_power_2(verified_u16{u16{static_cast(4096)}})); + static_assert(!is_power_2(verified_u16{u16{static_cast(4095)}})); + + static_assert(is_power_2(verified_u32{u32{UINT32_C(1048576)}})); + static_assert(!is_power_2(verified_u32{u32{UINT32_C(1000000)}})); + + static_assert(is_power_2(verified_u64{u64{UINT64_C(4294967296)}})); + static_assert(!is_power_2(verified_u64{u64{UINT64_C(4294967295)}})); + + using boost::int128::uint128_t; + static_assert(is_power_2(verified_u128{u128{uint128_t{UINT64_C(1099511627776)}}})); + static_assert(!is_power_2(verified_u128{u128{uint128_t{UINT64_C(1099511627775)}}})); +} + +// ============================================================================= +// Zero input tests +// ============================================================================= + +template +void test_is_power_2_zero() +{ + using underlying = typename detail::underlying_type_t; + BOOST_TEST(!is_power_2(T{static_cast(0)})); +} + +void test_is_power_2_zero_constexpr() +{ + static_assert(!is_power_2(u8{static_cast(0)})); + static_assert(!is_power_2(u32{UINT32_C(0)})); + static_assert(!is_power_2(u64{UINT64_C(0)})); +} + +void test_is_power_2_zero_verified() +{ + static_assert(!is_power_2(verified_u8{u8{static_cast(0)}})); + static_assert(!is_power_2(verified_u32{u32{UINT32_C(0)}})); +} + +int main() +{ + // Zero input - all types + test_is_power_2_zero(); + test_is_power_2_zero(); + test_is_power_2_zero(); + test_is_power_2_zero(); + test_is_power_2_zero(); + test_is_power_2_zero_constexpr(); + test_is_power_2_zero_verified(); + + // Powers of 2 - all types + test_is_power_2_true(); + test_is_power_2_true(); + test_is_power_2_true(); + test_is_power_2_true(); + test_is_power_2_true(); + test_is_power_2_true_u16(); + test_is_power_2_true_u32(); + test_is_power_2_true_u64(); + test_is_power_2_true_u128(); + + // Non-powers of 2 - all types + test_is_power_2_false(); + test_is_power_2_false(); + test_is_power_2_false(); + test_is_power_2_false(); + test_is_power_2_false(); + test_is_power_2_false_u32(); + test_is_power_2_false_u64(); + test_is_power_2_false_u128(); + + // Exhaustive correctness for u8 and u16 + test_is_power_2_exhaustive_u8(); + test_is_power_2_exhaustive_u16(); + + // Constexpr evaluation + test_is_power_2_constexpr(); + + // Verified types (consteval) + test_is_power_2_verified(); + + return boost::report_errors(); +} diff --git a/test/test_remove_trailing_zeros.cpp b/test/test_remove_trailing_zeros.cpp index 6b67484..a6846b5 100644 --- a/test/test_remove_trailing_zeros.cpp +++ b/test/test_remove_trailing_zeros.cpp @@ -273,8 +273,53 @@ void test_rtz_verified() BOOST_TEST_EQ(v128.number_of_removed_zeros, static_cast(5)); } +// ============================================================================= +// Zero input tests +// ============================================================================= + +template +void test_rtz_zero() +{ + using underlying = typename detail::underlying_type_t; + + auto result = remove_trailing_zeros(T{static_cast(0)}); + BOOST_TEST_EQ(result.trimmed_number, static_cast(0)); + BOOST_TEST_EQ(result.number_of_removed_zeros, static_cast(0)); +} + +void test_rtz_zero_constexpr() +{ + constexpr auto r8 = remove_trailing_zeros(u8{static_cast(0)}); + BOOST_TEST_EQ(r8.trimmed_number, static_cast(0)); + BOOST_TEST_EQ(r8.number_of_removed_zeros, static_cast(0)); + + constexpr auto r32 = remove_trailing_zeros(u32{UINT32_C(0)}); + BOOST_TEST_EQ(r32.trimmed_number, UINT32_C(0)); + BOOST_TEST_EQ(r32.number_of_removed_zeros, static_cast(0)); +} + +void test_rtz_zero_verified() +{ + constexpr auto v8 = remove_trailing_zeros(verified_u8{u8{static_cast(0)}}); + BOOST_TEST_EQ(v8.trimmed_number, static_cast(0)); + BOOST_TEST_EQ(v8.number_of_removed_zeros, static_cast(0)); + + constexpr auto v32 = remove_trailing_zeros(verified_u32{u32{UINT32_C(0)}}); + BOOST_TEST_EQ(v32.trimmed_number, UINT32_C(0)); + BOOST_TEST_EQ(v32.number_of_removed_zeros, static_cast(0)); +} + int main() { + // Zero input - all types + test_rtz_zero(); + test_rtz_zero(); + test_rtz_zero(); + test_rtz_zero(); + test_rtz_zero(); + test_rtz_zero_constexpr(); + test_rtz_zero_verified(); + // No trailing zeros - all types test_rtz_no_trailing_zeros(); test_rtz_no_trailing_zeros();