diff --git a/doc/modules/ROOT/pages/api_reference.adoc b/doc/modules/ROOT/pages/api_reference.adoc index e74e65b..b1eb939 100644 --- a/doc/modules/ROOT/pages/api_reference.adoc +++ b/doc/modules/ROOT/pages/api_reference.adoc @@ -182,12 +182,15 @@ https://www.boost.org/LICENSE_1_0.txt | xref:integer_utilities.adoc[`ipow`] | Integer exponentiation by squaring -| xref:integer_utilities.adoc[`log2`] +| xref:integer_utilities.adoc[`ilog2`] | Returns the floor of the base-2 logarithm -| xref:integer_utilities.adoc[`log10`] +| xref:integer_utilities.adoc[`ilog10`] | Returns the floor of the base-10 logarithm +| xref:integer_utilities.adoc[`ilog`] +| Returns the floor of an arbitrary-base logarithm + | xref:integer_utilities.adoc[`abs_diff`] | Computes the absolute difference between two safe integers @@ -299,7 +302,7 @@ This header is not included in the convenience header since it requires external | Contains specializations of `` for library types | `` -| Integer utility functions (`isqrt`, `remove_trailing_zeros`, `is_power_10`, `is_power_2`, `ipow`, `log2`, `log10`, `abs_diff`, `div_ceil`, `next_multiple_of`) +| Integer utility functions (`isqrt`, `remove_trailing_zeros`, `is_power_10`, `is_power_2`, `ipow`, `ilog2`, `ilog10`, `ilog`, `abs_diff`, `div_ceil`, `next_multiple_of`) | `` | Standard numeric algorithms (`gcd`, `lcm`, `midpoint`) diff --git a/doc/modules/ROOT/pages/integer_utilities.adoc b/doc/modules/ROOT/pages/integer_utilities.adoc index 5bc2156..940c389 100644 --- a/doc/modules/ROOT/pages/integer_utilities.adoc +++ b/doc/modules/ROOT/pages/integer_utilities.adoc @@ -164,14 +164,14 @@ is_power_10(u32{1234}); // false is_power_10(u64{10000000000000000000ULL}); // true ---- -== log2 +== ilog2 Returns the integer base-2 logarithm (floor of log~2~) of a value. [source,c++] ---- template -[[nodiscard]] constexpr auto log2(const T n) noexcept -> int; +[[nodiscard]] constexpr auto ilog2(const T n) -> int; ---- Computes `floor(log~2~(n))` using `bit_width(n) - 1`. @@ -180,10 +180,13 @@ Computes `floor(log~2~(n))` using `bit_width(n) - 1`. * `n` -- The value to compute the logarithm of. Must be non-zero. +==== Throws + +`std::domain_error` if `n == 0`. + ==== Return Value The floor of the base-2 logarithm of `n`. -For `n == 0`, returns `-1` (since `bit_width(0) == 0`). ==== Complexity @@ -195,13 +198,13 @@ O(1). ---- using namespace boost::safe_numbers; -auto r1 = log2(u32{1024}); // r1 == 10 (2^10 = 1024) -auto r2 = log2(u32{1000}); // r2 == 9 (floor of log2(1000)) -auto r3 = log2(u32{1}); // r3 == 0 -auto r4 = log2(u64{9223372036854775808ULL}); // r4 == 63 (2^63) +auto r1 = ilog2(u32{1024}); // r1 == 10 (2^10 = 1024) +auto r2 = ilog2(u32{1000}); // r2 == 9 (floor of log2(1000)) +auto r3 = ilog2(u32{1}); // r3 == 0 +auto r4 = ilog2(u64{9223372036854775808ULL}); // r4 == 63 (2^63) ---- -== log10 +== ilog10 Returns the integer base-10 logarithm (floor of log~10~) of a value. @@ -210,7 +213,7 @@ Uses an O(1) algorithm based on the most significant bit position to approximate [source,c++] ---- template -[[nodiscard]] constexpr auto log10(const T n) noexcept -> int; +[[nodiscard]] constexpr auto ilog10(const T n) -> int; ---- Computes `floor(log~10~(n))` using `num_digits(n) - 1`, where `num_digits` approximates the digit count via `log~10~(x) ~= log~2~(x) / log~2~(10)` and refines with at most two comparisons against a power-of-10 lookup table. @@ -219,6 +222,10 @@ Computes `floor(log~10~(n))` using `num_digits(n) - 1`, where `num_digits` appro * `n` -- The value to compute the logarithm of. Must be non-zero. +==== Throws + +`std::domain_error` if `n == 0`. + ==== Return Value The floor of the base-10 logarithm of `n`. @@ -229,10 +236,55 @@ The floor of the base-10 logarithm of `n`. ---- using namespace boost::safe_numbers; -auto r1 = log10(u32{1000}); // r1 == 3 -auto r2 = log10(u32{999}); // r2 == 2 (floor of log10(999)) -auto r3 = log10(u32{1}); // r3 == 0 -auto r4 = log10(u64{10000000000000000000ULL}); // r4 == 19 +auto r1 = ilog10(u32{1000}); // r1 == 3 +auto r2 = ilog10(u32{999}); // r2 == 2 (floor of log10(999)) +auto r3 = ilog10(u32{1}); // r3 == 0 +auto r4 = ilog10(u64{10000000000000000000ULL}); // r4 == 19 +---- + +== ilog + +Returns the integer logarithm in an arbitrary base (floor of log~base~) of a value. + +[source,c++] +---- +template +[[nodiscard]] constexpr auto ilog(const T n, const T base) -> int; +---- + +Computes `floor(log~base~(n))` by repeated division. +For the common cases of base 2 and base 10, prefer the specialized `ilog2` and `ilog10` functions which run in O(1). + +==== Parameters + +* `n` -- The value to compute the logarithm of. Must be non-zero. +* `base` -- The base of the logarithm. Must be >= 2. + +==== Throws + +* `std::domain_error` if `n == 0`. +* `std::domain_error` if `base < 2` (base 0 would cause division by zero; base 1 would cause an infinite loop). + +==== Return Value + +The floor of the base-`base` logarithm of `n`. +For `n < base`, returns `0`. + +==== Complexity + +O(log~base~(n)) divisions. + +==== Example + +[source,c++] +---- +using namespace boost::safe_numbers; + +auto r1 = ilog(u32{1000}, u32{10}); // r1 == 3 (floor of log10(1000)) +auto r2 = ilog(u32{1024}, u32{2}); // r2 == 10 (floor of log2(1024)) +auto r3 = ilog(u32{80}, u32{3}); // r3 == 3 (floor of log3(80)) +auto r4 = ilog(u32{3125}, u32{5}); // r4 == 5 (5^5 = 3125) +auto r5 = ilog(u32{1}, u32{7}); // r5 == 0 (any base, log(1) = 0) ---- == ipow diff --git a/include/boost/safe_numbers/integer_utilities.hpp b/include/boost/safe_numbers/integer_utilities.hpp index 7bd34e7..5048797 100644 --- a/include/boost/safe_numbers/integer_utilities.hpp +++ b/include/boost/safe_numbers/integer_utilities.hpp @@ -11,6 +11,13 @@ #include #include +#ifndef BOOST_SAFE_NUMBERS_BUILD_MODULE + +#include +#include + +#endif + namespace boost::safe_numbers { // Newton's method as it can't possibly overflow, and converges rapidly @@ -71,20 +78,63 @@ template // Integer log base 2: floor(log2(n)) == bit_width(n) - 1 template -[[nodiscard]] constexpr auto log2(const T n) noexcept -> int +[[nodiscard]] constexpr auto ilog2(const T n) -> int { + using underlying_type = detail::underlying_type_t; + + if (static_cast(n) == underlying_type{0}) + { + BOOST_THROW_EXCEPTION(std::domain_error("ilog2(0) is undefined")); + } + return bit_width(n) - 1; } -// Integer log base 10: floor(log10(n)) == num_digits(n) - 1 +// Integer log base 10: floor(ilog10(n)) == num_digits(n) - 1 // Uses MSB-based approximation with power-of-10 table lookup (O(1)) template -[[nodiscard]] constexpr auto log10(const T n) noexcept -> int +[[nodiscard]] constexpr auto ilog10(const T n) -> int { using underlying_type = detail::underlying_type_t; + + if (static_cast(n) == underlying_type{0}) + { + BOOST_THROW_EXCEPTION(std::domain_error("ilog10(0) is undefined")); + } + return detail::num_digits(static_cast(n)) - 1; } +// Integer log arbitrary base: floor(log_base(n)) +// Repeated division: O(log_base(n)) divisions +template +[[nodiscard]] constexpr auto ilog(const T n, const T base) -> int +{ + using underlying_type = detail::underlying_type_t; + + if (static_cast(n) == underlying_type{0}) + { + BOOST_THROW_EXCEPTION(std::domain_error("ilog(0, base) is undefined")); + } + + if (static_cast(base) < underlying_type{2}) + { + BOOST_THROW_EXCEPTION(std::domain_error("ilog(n, base) requires base >= 2")); + } + + auto result {0}; + auto val {static_cast(n)}; + const auto b {static_cast(base)}; + + while (val >= b) + { + val /= b; + ++result; + } + + return result; +} + namespace detail { // Iterative exponentiation by squaring: O(log b) multiplications diff --git a/test/Jamfile b/test/Jamfile index 9143fd1..d430420 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -127,8 +127,9 @@ run test_remove_trailing_zeros.cpp ; run test_is_power_10.cpp ; run test_is_power_2.cpp ; run test_ipow.cpp ; -run test_log2.cpp ; -run test_log10.cpp ; +run test_ilog2.cpp ; +run test_ilog10.cpp ; +run test_ilog.cpp ; run test_gcd.cpp ; run test_lcm.cpp ; run test_midpoint.cpp ; diff --git a/test/test_ilog.cpp b/test/test_ilog.cpp new file mode 100644 index 0000000..98b6373 --- /dev/null +++ b/test/test_ilog.cpp @@ -0,0 +1,341 @@ +// 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 +#include + +using namespace boost::safe_numbers; + +// ============================================================================= +// Runtime tests: exact powers of the base +// ============================================================================= + +template +void test_ilog_exact_powers() +{ + using underlying = typename detail::underlying_type_t; + + // Base 2 + BOOST_TEST_EQ(ilog(T{static_cast(1)}, T{static_cast(2)}), 0); // 2^0 + BOOST_TEST_EQ(ilog(T{static_cast(2)}, T{static_cast(2)}), 1); // 2^1 + BOOST_TEST_EQ(ilog(T{static_cast(4)}, T{static_cast(2)}), 2); // 2^2 + BOOST_TEST_EQ(ilog(T{static_cast(8)}, T{static_cast(2)}), 3); // 2^3 + BOOST_TEST_EQ(ilog(T{static_cast(128)}, T{static_cast(2)}), 7); // 2^7 + + // Base 3 + BOOST_TEST_EQ(ilog(T{static_cast(1)}, T{static_cast(3)}), 0); // 3^0 + BOOST_TEST_EQ(ilog(T{static_cast(3)}, T{static_cast(3)}), 1); // 3^1 + BOOST_TEST_EQ(ilog(T{static_cast(9)}, T{static_cast(3)}), 2); // 3^2 + BOOST_TEST_EQ(ilog(T{static_cast(27)}, T{static_cast(3)}), 3); // 3^3 + BOOST_TEST_EQ(ilog(T{static_cast(81)}, T{static_cast(3)}), 4); // 3^4 + + // Base 10 + BOOST_TEST_EQ(ilog(T{static_cast(1)}, T{static_cast(10)}), 0); // 10^0 + BOOST_TEST_EQ(ilog(T{static_cast(10)}, T{static_cast(10)}), 1); // 10^1 + BOOST_TEST_EQ(ilog(T{static_cast(100)}, T{static_cast(10)}), 2); // 10^2 + + // Base 16 + BOOST_TEST_EQ(ilog(T{static_cast(1)}, T{static_cast(16)}), 0); + BOOST_TEST_EQ(ilog(T{static_cast(16)}, T{static_cast(16)}), 1); +} + +// ============================================================================= +// Runtime tests: non-powers (floor behavior) +// ============================================================================= + +template +void test_ilog_floor() +{ + using underlying = typename detail::underlying_type_t; + + // Base 2 floor + BOOST_TEST_EQ(ilog(T{static_cast(3)}, T{static_cast(2)}), 1); // floor(log2(3)) = 1 + BOOST_TEST_EQ(ilog(T{static_cast(5)}, T{static_cast(2)}), 2); // floor(log2(5)) = 2 + BOOST_TEST_EQ(ilog(T{static_cast(7)}, T{static_cast(2)}), 2); // floor(log2(7)) = 2 + BOOST_TEST_EQ(ilog(T{static_cast(255)}, T{static_cast(2)}), 7); // floor(log2(255)) = 7 + + // Base 3 floor + BOOST_TEST_EQ(ilog(T{static_cast(2)}, T{static_cast(3)}), 0); // floor(log3(2)) = 0 + BOOST_TEST_EQ(ilog(T{static_cast(4)}, T{static_cast(3)}), 1); // floor(log3(4)) = 1 + BOOST_TEST_EQ(ilog(T{static_cast(8)}, T{static_cast(3)}), 1); // floor(log3(8)) = 1 + BOOST_TEST_EQ(ilog(T{static_cast(26)}, T{static_cast(3)}), 2); // floor(log3(26)) = 2 + BOOST_TEST_EQ(ilog(T{static_cast(80)}, T{static_cast(3)}), 3); // floor(log3(80)) = 3 + + // Base 10 floor + BOOST_TEST_EQ(ilog(T{static_cast(5)}, T{static_cast(10)}), 0); + BOOST_TEST_EQ(ilog(T{static_cast(9)}, T{static_cast(10)}), 0); + BOOST_TEST_EQ(ilog(T{static_cast(11)}, T{static_cast(10)}), 1); + BOOST_TEST_EQ(ilog(T{static_cast(99)}, T{static_cast(10)}), 1); + BOOST_TEST_EQ(ilog(T{static_cast(101)}, T{static_cast(10)}), 2); + BOOST_TEST_EQ(ilog(T{static_cast(255)}, T{static_cast(10)}), 2); + + // Base 5 floor + BOOST_TEST_EQ(ilog(T{static_cast(1)}, T{static_cast(5)}), 0); + BOOST_TEST_EQ(ilog(T{static_cast(4)}, T{static_cast(5)}), 0); + BOOST_TEST_EQ(ilog(T{static_cast(5)}, T{static_cast(5)}), 1); + BOOST_TEST_EQ(ilog(T{static_cast(24)}, T{static_cast(5)}), 1); + BOOST_TEST_EQ(ilog(T{static_cast(25)}, T{static_cast(5)}), 2); + BOOST_TEST_EQ(ilog(T{static_cast(124)}, T{static_cast(5)}), 2); + BOOST_TEST_EQ(ilog(T{static_cast(125)}, T{static_cast(5)}), 3); +} + +// ============================================================================= +// Consistency: ilog(n, 2) == ilog2(n) and ilog(n, 10) == ilog10(n) +// ============================================================================= + +template +void test_ilog_consistency() +{ + using underlying = typename detail::underlying_type_t; + + // Check against specialized ilog2 + BOOST_TEST_EQ(ilog(T{static_cast(1)}, T{static_cast(2)}), ilog2(T{static_cast(1)})); + BOOST_TEST_EQ(ilog(T{static_cast(2)}, T{static_cast(2)}), ilog2(T{static_cast(2)})); + BOOST_TEST_EQ(ilog(T{static_cast(7)}, T{static_cast(2)}), ilog2(T{static_cast(7)})); + BOOST_TEST_EQ(ilog(T{static_cast(128)}, T{static_cast(2)}), ilog2(T{static_cast(128)})); + BOOST_TEST_EQ(ilog(T{static_cast(255)}, T{static_cast(2)}), ilog2(T{static_cast(255)})); + + // Check against specialized ilog10 + BOOST_TEST_EQ(ilog(T{static_cast(1)}, T{static_cast(10)}), ilog10(T{static_cast(1)})); + BOOST_TEST_EQ(ilog(T{static_cast(10)}, T{static_cast(10)}), ilog10(T{static_cast(10)})); + BOOST_TEST_EQ(ilog(T{static_cast(99)}, T{static_cast(10)}), ilog10(T{static_cast(99)})); + BOOST_TEST_EQ(ilog(T{static_cast(100)}, T{static_cast(10)}), ilog10(T{static_cast(100)})); + BOOST_TEST_EQ(ilog(T{static_cast(255)}, T{static_cast(10)}), ilog10(T{static_cast(255)})); +} + +// ============================================================================= +// Type-specific tests for larger values +// ============================================================================= + +void test_ilog_u16() +{ + // Base 2 + BOOST_TEST_EQ(ilog(u16{static_cast(1024)}, u16{static_cast(2)}), 10); + BOOST_TEST_EQ(ilog(u16{static_cast(32768)}, u16{static_cast(2)}), 15); + BOOST_TEST_EQ(ilog(u16{static_cast(65535)}, u16{static_cast(2)}), 15); + + // Base 3 + BOOST_TEST_EQ(ilog(u16{static_cast(243)}, u16{static_cast(3)}), 5); // 3^5 + BOOST_TEST_EQ(ilog(u16{static_cast(59049)}, u16{static_cast(3)}), 10); // 3^10 + + // Base 7 + BOOST_TEST_EQ(ilog(u16{static_cast(2401)}, u16{static_cast(7)}), 4); // 7^4 + BOOST_TEST_EQ(ilog(u16{static_cast(16807)}, u16{static_cast(7)}), 5); // 7^5 + + // Base 256 + BOOST_TEST_EQ(ilog(u16{static_cast(256)}, u16{static_cast(256)}), 1); + BOOST_TEST_EQ(ilog(u16{static_cast(65535)}, u16{static_cast(256)}), 1); +} + +void test_ilog_u32() +{ + // Base 2 + BOOST_TEST_EQ(ilog(u32{UINT32_C(1073741824)}, u32{UINT32_C(2)}), 30); + BOOST_TEST_EQ(ilog(u32{UINT32_C(4294967295)}, u32{UINT32_C(2)}), 31); + + // Base 3 + BOOST_TEST_EQ(ilog(u32{UINT32_C(1594323)}, u32{UINT32_C(3)}), 13); // 3^13 + BOOST_TEST_EQ(ilog(u32{UINT32_C(3486784401)}, u32{UINT32_C(3)}), 20); // 3^20 + + // Base 10 + BOOST_TEST_EQ(ilog(u32{UINT32_C(1000000000)}, u32{UINT32_C(10)}), 9); + BOOST_TEST_EQ(ilog(u32{UINT32_C(4294967295)}, u32{UINT32_C(10)}), 9); + + // Base 1000 + BOOST_TEST_EQ(ilog(u32{UINT32_C(1000000)}, u32{UINT32_C(1000)}), 2); + BOOST_TEST_EQ(ilog(u32{UINT32_C(1000000000)}, u32{UINT32_C(1000)}), 3); +} + +void test_ilog_u64() +{ + // Base 2 + BOOST_TEST_EQ(ilog(u64{UINT64_C(9223372036854775808)}, u64{UINT64_C(2)}), 63); + BOOST_TEST_EQ(ilog(u64{UINT64_MAX}, u64{UINT64_C(2)}), 63); + + // Base 3 + BOOST_TEST_EQ(ilog(u64{UINT64_C(12157665459056928801)}, u64{UINT64_C(3)}), 40); // 3^40 + + // Base 10 + BOOST_TEST_EQ(ilog(u64{UINT64_C(10000000000000000000)}, u64{UINT64_C(10)}), 19); + BOOST_TEST_EQ(ilog(u64{UINT64_MAX}, u64{UINT64_C(10)}), 19); + + // Large base + BOOST_TEST_EQ(ilog(u64{UINT64_C(1000000000000000000)}, u64{UINT64_C(1000000000)}), 2); +} + +void test_ilog_u128() +{ + using boost::int128::uint128_t; + + // Small values + BOOST_TEST_EQ(ilog(u128{uint128_t{1}}, u128{uint128_t{2}}), 0); + BOOST_TEST_EQ(ilog(u128{uint128_t{100}}, u128{uint128_t{10}}), 2); + + // 2^64 + const auto two_pow_64 = uint128_t{1} << 64U; + BOOST_TEST_EQ(ilog(u128{two_pow_64}, u128{uint128_t{2}}), 64); + + // 2^127 + const auto two_pow_127 = uint128_t{1} << 127U; + BOOST_TEST_EQ(ilog(u128{two_pow_127}, u128{uint128_t{2}}), 127); + + // Base 10 large + const auto ten_pow_20 = uint128_t{UINT64_C(10000000000)} * uint128_t{UINT64_C(10000000000)}; + BOOST_TEST_EQ(ilog(u128{ten_pow_20}, u128{uint128_t{10}}), 20); + + // Base 10^10 + BOOST_TEST_EQ(ilog(u128{ten_pow_20}, u128{uint128_t{UINT64_C(10000000000)}}), 2); + + // Large base equal to value + BOOST_TEST_EQ(ilog(u128{two_pow_64}, u128{two_pow_64}), 1); +} + +// ============================================================================= +// Exhaustive u8 tests for bases 2..15 +// ============================================================================= + +void test_ilog_exhaustive_u8() +{ + for (unsigned base {2}; base <= 15; ++base) + { + for (unsigned n {1}; n <= 255; ++n) + { + // Reference: floor(log_base(n)) by repeated division + auto expected {0}; + auto tmp {n}; + while (tmp >= base) + { + tmp /= base; + ++expected; + } + + BOOST_TEST_EQ(ilog(u8{static_cast(n)}, u8{static_cast(base)}), expected); + } + } +} + +// ============================================================================= +// Domain error: ilog(0, base) throws +// ============================================================================= + +template +void test_ilog_zero_throws() +{ + using underlying = typename detail::underlying_type_t; + + BOOST_TEST_THROWS(static_cast(ilog(T{static_cast(0)}, T{static_cast(2)})), std::domain_error); + BOOST_TEST_THROWS(static_cast(ilog(T{static_cast(0)}, T{static_cast(10)})), std::domain_error); +} + +// ============================================================================= +// Domain error: ilog(n, base) with base < 2 throws +// ============================================================================= + +template +void test_ilog_bad_base_throws() +{ + using underlying = typename detail::underlying_type_t; + + // base = 0: would be division by zero + BOOST_TEST_THROWS(static_cast(ilog(T{static_cast(1)}, T{static_cast(0)})), std::domain_error); + BOOST_TEST_THROWS(static_cast(ilog(T{static_cast(100)}, T{static_cast(0)})), std::domain_error); + + // base = 1: would be infinite loop + BOOST_TEST_THROWS(static_cast(ilog(T{static_cast(1)}, T{static_cast(1)})), std::domain_error); + BOOST_TEST_THROWS(static_cast(ilog(T{static_cast(100)}, T{static_cast(1)})), std::domain_error); +} + +// ============================================================================= +// Constexpr tests +// ============================================================================= + +void test_ilog_constexpr() +{ + // Base 2 + static_assert(ilog(u8{static_cast(1)}, u8{static_cast(2)}) == 0); + static_assert(ilog(u8{static_cast(128)}, u8{static_cast(2)}) == 7); + static_assert(ilog(u8{static_cast(255)}, u8{static_cast(2)}) == 7); + + // Base 3 + static_assert(ilog(u8{static_cast(27)}, u8{static_cast(3)}) == 3); + static_assert(ilog(u8{static_cast(80)}, u8{static_cast(3)}) == 3); + + // Base 10 + static_assert(ilog(u16{static_cast(10000)}, u16{static_cast(10)}) == 4); + static_assert(ilog(u16{static_cast(65535)}, u16{static_cast(10)}) == 4); + + static_assert(ilog(u32{UINT32_C(1000000000)}, u32{UINT32_C(10)}) == 9); + static_assert(ilog(u32{UINT32_C(1048576)}, u32{UINT32_C(2)}) == 20); + + static_assert(ilog(u64{UINT64_C(9223372036854775808)}, u64{UINT64_C(2)}) == 63); + + // Base 5 + static_assert(ilog(u32{UINT32_C(3125)}, u32{UINT32_C(5)}) == 5); // 5^5 + + // Base 7 + static_assert(ilog(u32{UINT32_C(16807)}, u32{UINT32_C(7)}) == 5); // 7^5 +} + +int main() +{ + // Exact powers - all types + test_ilog_exact_powers(); + test_ilog_exact_powers(); + test_ilog_exact_powers(); + test_ilog_exact_powers(); + test_ilog_exact_powers(); + + // Floor behavior - all types + test_ilog_floor(); + test_ilog_floor(); + test_ilog_floor(); + test_ilog_floor(); + test_ilog_floor(); + + // Consistency with ilog2/ilog10 - all types + test_ilog_consistency(); + test_ilog_consistency(); + test_ilog_consistency(); + test_ilog_consistency(); + test_ilog_consistency(); + + // Type-specific larger values + test_ilog_u16(); + test_ilog_u32(); + test_ilog_u64(); + test_ilog_u128(); + + // Exhaustive u8 for multiple bases + test_ilog_exhaustive_u8(); + + // Domain error: ilog(0, base) + test_ilog_zero_throws(); + test_ilog_zero_throws(); + test_ilog_zero_throws(); + test_ilog_zero_throws(); + test_ilog_zero_throws(); + + // Domain error: ilog(n, base) with base < 2 + test_ilog_bad_base_throws(); + test_ilog_bad_base_throws(); + test_ilog_bad_base_throws(); + test_ilog_bad_base_throws(); + test_ilog_bad_base_throws(); + + // Constexpr evaluation + test_ilog_constexpr(); + + return boost::report_errors(); +} diff --git a/test/test_ilog10.cpp b/test/test_ilog10.cpp new file mode 100644 index 0000000..ac6413f --- /dev/null +++ b/test/test_ilog10.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 +#include + +using namespace boost::safe_numbers; + +// ============================================================================= +// Runtime tests: exact powers of 10 +// ============================================================================= + +template +void test_ilog10_powers_of_10() +{ + using underlying = typename detail::underlying_type_t; + + BOOST_TEST_EQ(ilog10(T{static_cast(1)}), 0); // 10^0 + BOOST_TEST_EQ(ilog10(T{static_cast(10)}), 1); // 10^1 + BOOST_TEST_EQ(ilog10(T{static_cast(100)}), 2); // 10^2 +} + +void test_ilog10_powers_of_10_u16() +{ + BOOST_TEST_EQ(ilog10(u16{static_cast(1000)}), 3); // 10^3 + BOOST_TEST_EQ(ilog10(u16{static_cast(10000)}), 4); // 10^4 +} + +void test_ilog10_powers_of_10_u32() +{ + BOOST_TEST_EQ(ilog10(u32{UINT32_C(1000)}), 3); + BOOST_TEST_EQ(ilog10(u32{UINT32_C(10000)}), 4); + BOOST_TEST_EQ(ilog10(u32{UINT32_C(100000)}), 5); + BOOST_TEST_EQ(ilog10(u32{UINT32_C(1000000)}), 6); + BOOST_TEST_EQ(ilog10(u32{UINT32_C(10000000)}), 7); + BOOST_TEST_EQ(ilog10(u32{UINT32_C(100000000)}), 8); + BOOST_TEST_EQ(ilog10(u32{UINT32_C(1000000000)}), 9); +} + +void test_ilog10_powers_of_10_u64() +{ + BOOST_TEST_EQ(ilog10(u64{UINT64_C(1000000000000)}), 12); + BOOST_TEST_EQ(ilog10(u64{UINT64_C(10000000000000000)}), 16); + BOOST_TEST_EQ(ilog10(u64{UINT64_C(1000000000000000000)}), 18); + BOOST_TEST_EQ(ilog10(u64{UINT64_C(10000000000000000000)}), 19); +} + +void test_ilog10_powers_of_10_u128() +{ + using boost::int128::uint128_t; + + BOOST_TEST_EQ(ilog10(u128{uint128_t{UINT64_C(1000000000000)}}), 12); + + // 10^20 + const auto ten_pow_20 = uint128_t{UINT64_C(10000000000)} * uint128_t{UINT64_C(10000000000)}; + BOOST_TEST_EQ(ilog10(u128{ten_pow_20}), 20); + + // 10^30 + const auto ten_pow_30 = ten_pow_20 * uint128_t{UINT64_C(10000000000)}; + BOOST_TEST_EQ(ilog10(u128{ten_pow_30}), 30); +} + +// ============================================================================= +// Runtime tests: non-powers of 10 (floor behavior) +// ============================================================================= + +template +void test_ilog10_floor() +{ + using underlying = typename detail::underlying_type_t; + + BOOST_TEST_EQ(ilog10(T{static_cast(2)}), 0); // floor(ilog10(2)) = 0 + BOOST_TEST_EQ(ilog10(T{static_cast(5)}), 0); + BOOST_TEST_EQ(ilog10(T{static_cast(9)}), 0); + BOOST_TEST_EQ(ilog10(T{static_cast(11)}), 1); // floor(ilog10(11)) = 1 + BOOST_TEST_EQ(ilog10(T{static_cast(50)}), 1); + BOOST_TEST_EQ(ilog10(T{static_cast(99)}), 1); + BOOST_TEST_EQ(ilog10(T{static_cast(101)}), 2); // floor(ilog10(101)) = 2 + BOOST_TEST_EQ(ilog10(T{static_cast(200)}), 2); + BOOST_TEST_EQ(ilog10(T{static_cast(255)}), 2); +} + +void test_ilog10_floor_u32() +{ + BOOST_TEST_EQ(ilog10(u32{UINT32_C(999)}), 2); + BOOST_TEST_EQ(ilog10(u32{UINT32_C(1001)}), 3); + BOOST_TEST_EQ(ilog10(u32{UINT32_C(999999999)}), 8); + BOOST_TEST_EQ(ilog10(u32{UINT32_C(4294967295)}), 9); // floor(ilog10(2^32 - 1)) +} + +void test_ilog10_floor_u64() +{ + BOOST_TEST_EQ(ilog10(u64{UINT64_C(9999999999999999)}), 15); + BOOST_TEST_EQ(ilog10(u64{UINT64_C(10000000000000001)}), 16); + BOOST_TEST_EQ(ilog10(u64{UINT64_MAX}), 19); // floor(ilog10(2^64 - 1)) +} + +void test_ilog10_floor_u128() +{ + using boost::int128::uint128_t; + + BOOST_TEST_EQ(ilog10(u128{uint128_t{UINT64_C(9999999999999)}}), 12); + + // 10^20 + 1 + const auto ten_pow_20 = uint128_t{UINT64_C(10000000000)} * uint128_t{UINT64_C(10000000000)}; + BOOST_TEST_EQ(ilog10(u128{ten_pow_20 + uint128_t{1}}), 20); + + // 10^20 - 1 + BOOST_TEST_EQ(ilog10(u128{ten_pow_20 - uint128_t{1}}), 19); +} + +// ============================================================================= +// Tests with trailing zeros (exercises remove_trailing_zeros path) +// ============================================================================= + +template +void test_ilog10_trailing_zeros() +{ + using underlying = typename detail::underlying_type_t; + + // Numbers with trailing zeros where RTZ does the heavy lifting + BOOST_TEST_EQ(ilog10(T{static_cast(20)}), 1); + BOOST_TEST_EQ(ilog10(T{static_cast(30)}), 1); + BOOST_TEST_EQ(ilog10(T{static_cast(50)}), 1); + BOOST_TEST_EQ(ilog10(T{static_cast(90)}), 1); +} + +void test_ilog10_trailing_zeros_u32() +{ + BOOST_TEST_EQ(ilog10(u32{UINT32_C(12300)}), 4); + BOOST_TEST_EQ(ilog10(u32{UINT32_C(5000000)}), 6); + BOOST_TEST_EQ(ilog10(u32{UINT32_C(4000000000)}), 9); +} + +void test_ilog10_trailing_zeros_u64() +{ + BOOST_TEST_EQ(ilog10(u64{UINT64_C(123456789000)}), 11); + BOOST_TEST_EQ(ilog10(u64{UINT64_C(5000000000000000000)}), 18); +} + +// ============================================================================= +// Exhaustive u8 tests +// ============================================================================= + +void test_ilog10_exhaustive_u8() +{ + for (unsigned i {1}; i <= 255; ++i) + { + const auto n = static_cast(i); + auto result = ilog10(u8{n}); + + // Compute expected floor(ilog10(i)) + auto expected {0}; + auto tmp {i}; + while (tmp >= 10) + { + tmp /= 10; + ++expected; + } + + BOOST_TEST_EQ(result, expected); + } +} + +// ============================================================================= +// Exhaustive u16 tests +// ============================================================================= + +void test_ilog10_exhaustive_u16() +{ + for (unsigned i {1}; i <= 65535; ++i) + { + const auto n = static_cast(i); + auto result = ilog10(u16{n}); + + auto expected {0}; + auto tmp {i}; + while (tmp >= 10) + { + tmp /= 10; + ++expected; + } + + BOOST_TEST_EQ(result, expected); + } +} + +// ============================================================================= +// Domain error: ilog10(0) throws +// ============================================================================= + +template +void test_ilog10_zero_throws() +{ + using underlying = typename detail::underlying_type_t; + + BOOST_TEST_THROWS(static_cast(ilog10(T{static_cast(0)})), std::domain_error); +} + +// ============================================================================= +// Constexpr tests +// ============================================================================= + +void test_ilog10_constexpr() +{ + static_assert(ilog10(u8{static_cast(1)}) == 0); + static_assert(ilog10(u8{static_cast(10)}) == 1); + static_assert(ilog10(u8{static_cast(100)}) == 2); + static_assert(ilog10(u8{static_cast(255)}) == 2); + + static_assert(ilog10(u16{static_cast(10000)}) == 4); + static_assert(ilog10(u16{static_cast(65535)}) == 4); + + static_assert(ilog10(u32{UINT32_C(1000000000)}) == 9); + static_assert(ilog10(u32{UINT32_C(4294967295)}) == 9); + + static_assert(ilog10(u64{UINT64_C(1)}) == 0); + static_assert(ilog10(u64{UINT64_C(10000000000000000000)}) == 19); +} + +int main() +{ + // Powers of 10 - all types + test_ilog10_powers_of_10(); + test_ilog10_powers_of_10(); + test_ilog10_powers_of_10(); + test_ilog10_powers_of_10(); + test_ilog10_powers_of_10(); + test_ilog10_powers_of_10_u16(); + test_ilog10_powers_of_10_u32(); + test_ilog10_powers_of_10_u64(); + test_ilog10_powers_of_10_u128(); + + // Floor behavior - all types + test_ilog10_floor(); + test_ilog10_floor(); + test_ilog10_floor(); + test_ilog10_floor(); + test_ilog10_floor(); + test_ilog10_floor_u32(); + test_ilog10_floor_u64(); + test_ilog10_floor_u128(); + + // Trailing zeros (RTZ path) + test_ilog10_trailing_zeros(); + test_ilog10_trailing_zeros(); + test_ilog10_trailing_zeros(); + test_ilog10_trailing_zeros(); + test_ilog10_trailing_zeros(); + test_ilog10_trailing_zeros_u32(); + test_ilog10_trailing_zeros_u64(); + + // Exhaustive u8 and u16 + test_ilog10_exhaustive_u8(); + test_ilog10_exhaustive_u16(); + + // Domain error: ilog10(0) + test_ilog10_zero_throws(); + test_ilog10_zero_throws(); + test_ilog10_zero_throws(); + test_ilog10_zero_throws(); + test_ilog10_zero_throws(); + + // Constexpr evaluation + test_ilog10_constexpr(); + + return boost::report_errors(); +} diff --git a/test/test_ilog2.cpp b/test/test_ilog2.cpp new file mode 100644 index 0000000..84197f1 --- /dev/null +++ b/test/test_ilog2.cpp @@ -0,0 +1,209 @@ +// 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 +#include + +using namespace boost::safe_numbers; + +// ============================================================================= +// Runtime tests: powers of 2 +// ============================================================================= + +template +void test_ilog2_powers_of_2() +{ + using underlying = typename detail::underlying_type_t; + + BOOST_TEST_EQ(ilog2(T{static_cast(1)}), 0); // 2^0 + BOOST_TEST_EQ(ilog2(T{static_cast(2)}), 1); // 2^1 + BOOST_TEST_EQ(ilog2(T{static_cast(4)}), 2); // 2^2 + BOOST_TEST_EQ(ilog2(T{static_cast(8)}), 3); // 2^3 + BOOST_TEST_EQ(ilog2(T{static_cast(16)}), 4); // 2^4 + BOOST_TEST_EQ(ilog2(T{static_cast(32)}), 5); // 2^5 + BOOST_TEST_EQ(ilog2(T{static_cast(64)}), 6); // 2^6 + BOOST_TEST_EQ(ilog2(T{static_cast(128)}), 7); // 2^7 +} + +// ============================================================================= +// Runtime tests: non-powers of 2 (floor behavior) +// ============================================================================= + +template +void test_ilog2_floor() +{ + using underlying = typename detail::underlying_type_t; + + BOOST_TEST_EQ(ilog2(T{static_cast(3)}), 1); // floor(ilog2(3)) = 1 + BOOST_TEST_EQ(ilog2(T{static_cast(5)}), 2); // floor(ilog2(5)) = 2 + BOOST_TEST_EQ(ilog2(T{static_cast(6)}), 2); // floor(ilog2(6)) = 2 + BOOST_TEST_EQ(ilog2(T{static_cast(7)}), 2); // floor(ilog2(7)) = 2 + BOOST_TEST_EQ(ilog2(T{static_cast(9)}), 3); // floor(ilog2(9)) = 3 + BOOST_TEST_EQ(ilog2(T{static_cast(15)}), 3); // floor(ilog2(15)) = 3 + BOOST_TEST_EQ(ilog2(T{static_cast(17)}), 4); // floor(ilog2(17)) = 4 + BOOST_TEST_EQ(ilog2(T{static_cast(100)}), 6); // floor(ilog2(100)) = 6 + BOOST_TEST_EQ(ilog2(T{static_cast(255)}), 7); // floor(ilog2(255)) = 7 +} + +// ============================================================================= +// Type-specific tests for larger values +// ============================================================================= + +void test_ilog2_u16() +{ + BOOST_TEST_EQ(ilog2(u16{static_cast(256)}), 8); // 2^8 + BOOST_TEST_EQ(ilog2(u16{static_cast(1000)}), 9); // floor(ilog2(1000)) + BOOST_TEST_EQ(ilog2(u16{static_cast(1024)}), 10); // 2^10 + BOOST_TEST_EQ(ilog2(u16{static_cast(32768)}), 15); // 2^15 + BOOST_TEST_EQ(ilog2(u16{static_cast(65535)}), 15); // floor(ilog2(65535)) +} + +void test_ilog2_u32() +{ + BOOST_TEST_EQ(ilog2(u32{UINT32_C(65536)}), 16); // 2^16 + BOOST_TEST_EQ(ilog2(u32{UINT32_C(1000000)}), 19); // floor(ilog2(10^6)) + BOOST_TEST_EQ(ilog2(u32{UINT32_C(1048576)}), 20); // 2^20 + BOOST_TEST_EQ(ilog2(u32{UINT32_C(1073741824)}), 30); // 2^30 + BOOST_TEST_EQ(ilog2(u32{UINT32_C(2147483648)}), 31); // 2^31 + BOOST_TEST_EQ(ilog2(u32{UINT32_C(4294967295)}), 31); // floor(ilog2(2^32 - 1)) +} + +void test_ilog2_u64() +{ + BOOST_TEST_EQ(ilog2(u64{UINT64_C(4294967296)}), 32); // 2^32 + BOOST_TEST_EQ(ilog2(u64{UINT64_C(1099511627776)}), 40); // 2^40 + BOOST_TEST_EQ(ilog2(u64{UINT64_C(1000000000000)}), 39); // floor(ilog2(10^12)) + BOOST_TEST_EQ(ilog2(u64{UINT64_C(4611686018427387904)}), 62); // 2^62 + BOOST_TEST_EQ(ilog2(u64{UINT64_C(9223372036854775808)}), 63); // 2^63 + BOOST_TEST_EQ(ilog2(u64{UINT64_MAX}), 63); // floor(ilog2(2^64 - 1)) +} + +void test_ilog2_u128() +{ + using boost::int128::uint128_t; + + BOOST_TEST_EQ(ilog2(u128{uint128_t{1}}), 0); + BOOST_TEST_EQ(ilog2(u128{uint128_t{UINT64_C(1099511627776)}}), 40); // 2^40 + + // 2^64 + const auto two_pow_64 = uint128_t{1} << 64U; + BOOST_TEST_EQ(ilog2(u128{two_pow_64}), 64); + + // 2^100 + const auto two_pow_100 = uint128_t{1} << 100U; + BOOST_TEST_EQ(ilog2(u128{two_pow_100}), 100); + + // 2^127 + const auto two_pow_127 = uint128_t{1} << 127U; + BOOST_TEST_EQ(ilog2(u128{two_pow_127}), 127); + + // 2^127 + 1 (still 127) + BOOST_TEST_EQ(ilog2(u128{two_pow_127 + uint128_t{1}}), 127); +} + +// ============================================================================= +// Exhaustive u8 tests +// ============================================================================= + +void test_ilog2_exhaustive_u8() +{ + for (unsigned i {1}; i <= 255; ++i) + { + const auto n = static_cast(i); + auto result = ilog2(u8{n}); + + // Compute expected floor(ilog2(i)) + auto expected {0}; + auto tmp {i}; + while (tmp > 1) + { + tmp >>= 1U; + ++expected; + } + + BOOST_TEST_EQ(result, expected); + } +} + +// ============================================================================= +// Domain error: ilog2(0) throws +// ============================================================================= + +template +void test_ilog2_zero_throws() +{ + using underlying = typename detail::underlying_type_t; + + BOOST_TEST_THROWS(static_cast(ilog2(T{static_cast(0)})), std::domain_error); +} + +// ============================================================================= +// Constexpr tests +// ============================================================================= + +void test_ilog2_constexpr() +{ + static_assert(ilog2(u8{static_cast(1)}) == 0); + static_assert(ilog2(u8{static_cast(2)}) == 1); + static_assert(ilog2(u8{static_cast(128)}) == 7); + static_assert(ilog2(u8{static_cast(255)}) == 7); + + static_assert(ilog2(u16{static_cast(1024)}) == 10); + static_assert(ilog2(u16{static_cast(65535)}) == 15); + + static_assert(ilog2(u32{UINT32_C(1048576)}) == 20); + static_assert(ilog2(u32{UINT32_C(4294967295)}) == 31); + + static_assert(ilog2(u64{UINT64_C(1)}) == 0); + static_assert(ilog2(u64{UINT64_C(9223372036854775808)}) == 63); +} + +int main() +{ + // Powers of 2 - all types + test_ilog2_powers_of_2(); + test_ilog2_powers_of_2(); + test_ilog2_powers_of_2(); + test_ilog2_powers_of_2(); + test_ilog2_powers_of_2(); + + // Floor behavior - all types + test_ilog2_floor(); + test_ilog2_floor(); + test_ilog2_floor(); + test_ilog2_floor(); + test_ilog2_floor(); + + // Type-specific larger values + test_ilog2_u16(); + test_ilog2_u32(); + test_ilog2_u64(); + test_ilog2_u128(); + + // Exhaustive u8 + test_ilog2_exhaustive_u8(); + + // Domain error: ilog2(0) + test_ilog2_zero_throws(); + test_ilog2_zero_throws(); + test_ilog2_zero_throws(); + test_ilog2_zero_throws(); + test_ilog2_zero_throws(); + + // Constexpr evaluation + test_ilog2_constexpr(); + + return boost::report_errors(); +} diff --git a/test/test_log10.cpp b/test/test_log10.cpp deleted file mode 100644 index c06e1d0..0000000 --- a/test/test_log10.cpp +++ /dev/null @@ -1,260 +0,0 @@ -// 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: exact powers of 10 -// ============================================================================= - -template -void test_log10_powers_of_10() -{ - using underlying = typename detail::underlying_type_t; - - BOOST_TEST_EQ(log10(T{static_cast(1)}), 0); // 10^0 - BOOST_TEST_EQ(log10(T{static_cast(10)}), 1); // 10^1 - BOOST_TEST_EQ(log10(T{static_cast(100)}), 2); // 10^2 -} - -void test_log10_powers_of_10_u16() -{ - BOOST_TEST_EQ(log10(u16{static_cast(1000)}), 3); // 10^3 - BOOST_TEST_EQ(log10(u16{static_cast(10000)}), 4); // 10^4 -} - -void test_log10_powers_of_10_u32() -{ - BOOST_TEST_EQ(log10(u32{UINT32_C(1000)}), 3); - BOOST_TEST_EQ(log10(u32{UINT32_C(10000)}), 4); - BOOST_TEST_EQ(log10(u32{UINT32_C(100000)}), 5); - BOOST_TEST_EQ(log10(u32{UINT32_C(1000000)}), 6); - BOOST_TEST_EQ(log10(u32{UINT32_C(10000000)}), 7); - BOOST_TEST_EQ(log10(u32{UINT32_C(100000000)}), 8); - BOOST_TEST_EQ(log10(u32{UINT32_C(1000000000)}), 9); -} - -void test_log10_powers_of_10_u64() -{ - BOOST_TEST_EQ(log10(u64{UINT64_C(1000000000000)}), 12); - BOOST_TEST_EQ(log10(u64{UINT64_C(10000000000000000)}), 16); - BOOST_TEST_EQ(log10(u64{UINT64_C(1000000000000000000)}), 18); - BOOST_TEST_EQ(log10(u64{UINT64_C(10000000000000000000)}), 19); -} - -void test_log10_powers_of_10_u128() -{ - using boost::int128::uint128_t; - - BOOST_TEST_EQ(log10(u128{uint128_t{UINT64_C(1000000000000)}}), 12); - - // 10^20 - const auto ten_pow_20 = uint128_t{UINT64_C(10000000000)} * uint128_t{UINT64_C(10000000000)}; - BOOST_TEST_EQ(log10(u128{ten_pow_20}), 20); - - // 10^30 - const auto ten_pow_30 = ten_pow_20 * uint128_t{UINT64_C(10000000000)}; - BOOST_TEST_EQ(log10(u128{ten_pow_30}), 30); -} - -// ============================================================================= -// Runtime tests: non-powers of 10 (floor behavior) -// ============================================================================= - -template -void test_log10_floor() -{ - using underlying = typename detail::underlying_type_t; - - BOOST_TEST_EQ(log10(T{static_cast(2)}), 0); // floor(log10(2)) = 0 - BOOST_TEST_EQ(log10(T{static_cast(5)}), 0); - BOOST_TEST_EQ(log10(T{static_cast(9)}), 0); - BOOST_TEST_EQ(log10(T{static_cast(11)}), 1); // floor(log10(11)) = 1 - BOOST_TEST_EQ(log10(T{static_cast(50)}), 1); - BOOST_TEST_EQ(log10(T{static_cast(99)}), 1); - BOOST_TEST_EQ(log10(T{static_cast(101)}), 2); // floor(log10(101)) = 2 - BOOST_TEST_EQ(log10(T{static_cast(200)}), 2); - BOOST_TEST_EQ(log10(T{static_cast(255)}), 2); -} - -void test_log10_floor_u32() -{ - BOOST_TEST_EQ(log10(u32{UINT32_C(999)}), 2); - BOOST_TEST_EQ(log10(u32{UINT32_C(1001)}), 3); - BOOST_TEST_EQ(log10(u32{UINT32_C(999999999)}), 8); - BOOST_TEST_EQ(log10(u32{UINT32_C(4294967295)}), 9); // floor(log10(2^32 - 1)) -} - -void test_log10_floor_u64() -{ - BOOST_TEST_EQ(log10(u64{UINT64_C(9999999999999999)}), 15); - BOOST_TEST_EQ(log10(u64{UINT64_C(10000000000000001)}), 16); - BOOST_TEST_EQ(log10(u64{UINT64_MAX}), 19); // floor(log10(2^64 - 1)) -} - -void test_log10_floor_u128() -{ - using boost::int128::uint128_t; - - BOOST_TEST_EQ(log10(u128{uint128_t{UINT64_C(9999999999999)}}), 12); - - // 10^20 + 1 - const auto ten_pow_20 = uint128_t{UINT64_C(10000000000)} * uint128_t{UINT64_C(10000000000)}; - BOOST_TEST_EQ(log10(u128{ten_pow_20 + uint128_t{1}}), 20); - - // 10^20 - 1 - BOOST_TEST_EQ(log10(u128{ten_pow_20 - uint128_t{1}}), 19); -} - -// ============================================================================= -// Tests with trailing zeros (exercises remove_trailing_zeros path) -// ============================================================================= - -template -void test_log10_trailing_zeros() -{ - using underlying = typename detail::underlying_type_t; - - // Numbers with trailing zeros where RTZ does the heavy lifting - BOOST_TEST_EQ(log10(T{static_cast(20)}), 1); - BOOST_TEST_EQ(log10(T{static_cast(30)}), 1); - BOOST_TEST_EQ(log10(T{static_cast(50)}), 1); - BOOST_TEST_EQ(log10(T{static_cast(90)}), 1); -} - -void test_log10_trailing_zeros_u32() -{ - BOOST_TEST_EQ(log10(u32{UINT32_C(12300)}), 4); - BOOST_TEST_EQ(log10(u32{UINT32_C(5000000)}), 6); - BOOST_TEST_EQ(log10(u32{UINT32_C(4000000000)}), 9); -} - -void test_log10_trailing_zeros_u64() -{ - BOOST_TEST_EQ(log10(u64{UINT64_C(123456789000)}), 11); - BOOST_TEST_EQ(log10(u64{UINT64_C(5000000000000000000)}), 18); -} - -// ============================================================================= -// Exhaustive u8 tests -// ============================================================================= - -void test_log10_exhaustive_u8() -{ - for (unsigned i {1}; i <= 255; ++i) - { - const auto n = static_cast(i); - auto result = log10(u8{n}); - - // Compute expected floor(log10(i)) - auto expected {0}; - auto tmp {i}; - while (tmp >= 10) - { - tmp /= 10; - ++expected; - } - - BOOST_TEST_EQ(result, expected); - } -} - -// ============================================================================= -// Exhaustive u16 tests -// ============================================================================= - -void test_log10_exhaustive_u16() -{ - for (unsigned i {1}; i <= 65535; ++i) - { - const auto n = static_cast(i); - auto result = log10(u16{n}); - - auto expected {0}; - auto tmp {i}; - while (tmp >= 10) - { - tmp /= 10; - ++expected; - } - - BOOST_TEST_EQ(result, expected); - } -} - -// ============================================================================= -// Constexpr tests -// ============================================================================= - -void test_log10_constexpr() -{ - static_assert(log10(u8{static_cast(1)}) == 0); - static_assert(log10(u8{static_cast(10)}) == 1); - static_assert(log10(u8{static_cast(100)}) == 2); - static_assert(log10(u8{static_cast(255)}) == 2); - - static_assert(log10(u16{static_cast(10000)}) == 4); - static_assert(log10(u16{static_cast(65535)}) == 4); - - static_assert(log10(u32{UINT32_C(1000000000)}) == 9); - static_assert(log10(u32{UINT32_C(4294967295)}) == 9); - - static_assert(log10(u64{UINT64_C(1)}) == 0); - static_assert(log10(u64{UINT64_C(10000000000000000000)}) == 19); -} - -int main() -{ - // Powers of 10 - all types - test_log10_powers_of_10(); - test_log10_powers_of_10(); - test_log10_powers_of_10(); - test_log10_powers_of_10(); - test_log10_powers_of_10(); - test_log10_powers_of_10_u16(); - test_log10_powers_of_10_u32(); - test_log10_powers_of_10_u64(); - test_log10_powers_of_10_u128(); - - // Floor behavior - all types - test_log10_floor(); - test_log10_floor(); - test_log10_floor(); - test_log10_floor(); - test_log10_floor(); - test_log10_floor_u32(); - test_log10_floor_u64(); - test_log10_floor_u128(); - - // Trailing zeros (RTZ path) - test_log10_trailing_zeros(); - test_log10_trailing_zeros(); - test_log10_trailing_zeros(); - test_log10_trailing_zeros(); - test_log10_trailing_zeros(); - test_log10_trailing_zeros_u32(); - test_log10_trailing_zeros_u64(); - - // Exhaustive u8 and u16 - test_log10_exhaustive_u8(); - test_log10_exhaustive_u16(); - - // Constexpr evaluation - test_log10_constexpr(); - - return boost::report_errors(); -} diff --git a/test/test_log2.cpp b/test/test_log2.cpp deleted file mode 100644 index 8a7df47..0000000 --- a/test/test_log2.cpp +++ /dev/null @@ -1,189 +0,0 @@ -// 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_log2_powers_of_2() -{ - using underlying = typename detail::underlying_type_t; - - BOOST_TEST_EQ(log2(T{static_cast(1)}), 0); // 2^0 - BOOST_TEST_EQ(log2(T{static_cast(2)}), 1); // 2^1 - BOOST_TEST_EQ(log2(T{static_cast(4)}), 2); // 2^2 - BOOST_TEST_EQ(log2(T{static_cast(8)}), 3); // 2^3 - BOOST_TEST_EQ(log2(T{static_cast(16)}), 4); // 2^4 - BOOST_TEST_EQ(log2(T{static_cast(32)}), 5); // 2^5 - BOOST_TEST_EQ(log2(T{static_cast(64)}), 6); // 2^6 - BOOST_TEST_EQ(log2(T{static_cast(128)}), 7); // 2^7 -} - -// ============================================================================= -// Runtime tests: non-powers of 2 (floor behavior) -// ============================================================================= - -template -void test_log2_floor() -{ - using underlying = typename detail::underlying_type_t; - - BOOST_TEST_EQ(log2(T{static_cast(3)}), 1); // floor(log2(3)) = 1 - BOOST_TEST_EQ(log2(T{static_cast(5)}), 2); // floor(log2(5)) = 2 - BOOST_TEST_EQ(log2(T{static_cast(6)}), 2); // floor(log2(6)) = 2 - BOOST_TEST_EQ(log2(T{static_cast(7)}), 2); // floor(log2(7)) = 2 - BOOST_TEST_EQ(log2(T{static_cast(9)}), 3); // floor(log2(9)) = 3 - BOOST_TEST_EQ(log2(T{static_cast(15)}), 3); // floor(log2(15)) = 3 - BOOST_TEST_EQ(log2(T{static_cast(17)}), 4); // floor(log2(17)) = 4 - BOOST_TEST_EQ(log2(T{static_cast(100)}), 6); // floor(log2(100)) = 6 - BOOST_TEST_EQ(log2(T{static_cast(255)}), 7); // floor(log2(255)) = 7 -} - -// ============================================================================= -// Type-specific tests for larger values -// ============================================================================= - -void test_log2_u16() -{ - BOOST_TEST_EQ(log2(u16{static_cast(256)}), 8); // 2^8 - BOOST_TEST_EQ(log2(u16{static_cast(1000)}), 9); // floor(log2(1000)) - BOOST_TEST_EQ(log2(u16{static_cast(1024)}), 10); // 2^10 - BOOST_TEST_EQ(log2(u16{static_cast(32768)}), 15); // 2^15 - BOOST_TEST_EQ(log2(u16{static_cast(65535)}), 15); // floor(log2(65535)) -} - -void test_log2_u32() -{ - BOOST_TEST_EQ(log2(u32{UINT32_C(65536)}), 16); // 2^16 - BOOST_TEST_EQ(log2(u32{UINT32_C(1000000)}), 19); // floor(log2(10^6)) - BOOST_TEST_EQ(log2(u32{UINT32_C(1048576)}), 20); // 2^20 - BOOST_TEST_EQ(log2(u32{UINT32_C(1073741824)}), 30); // 2^30 - BOOST_TEST_EQ(log2(u32{UINT32_C(2147483648)}), 31); // 2^31 - BOOST_TEST_EQ(log2(u32{UINT32_C(4294967295)}), 31); // floor(log2(2^32 - 1)) -} - -void test_log2_u64() -{ - BOOST_TEST_EQ(log2(u64{UINT64_C(4294967296)}), 32); // 2^32 - BOOST_TEST_EQ(log2(u64{UINT64_C(1099511627776)}), 40); // 2^40 - BOOST_TEST_EQ(log2(u64{UINT64_C(1000000000000)}), 39); // floor(log2(10^12)) - BOOST_TEST_EQ(log2(u64{UINT64_C(4611686018427387904)}), 62); // 2^62 - BOOST_TEST_EQ(log2(u64{UINT64_C(9223372036854775808)}), 63); // 2^63 - BOOST_TEST_EQ(log2(u64{UINT64_MAX}), 63); // floor(log2(2^64 - 1)) -} - -void test_log2_u128() -{ - using boost::int128::uint128_t; - - BOOST_TEST_EQ(log2(u128{uint128_t{1}}), 0); - BOOST_TEST_EQ(log2(u128{uint128_t{UINT64_C(1099511627776)}}), 40); // 2^40 - - // 2^64 - const auto two_pow_64 = uint128_t{1} << 64U; - BOOST_TEST_EQ(log2(u128{two_pow_64}), 64); - - // 2^100 - const auto two_pow_100 = uint128_t{1} << 100U; - BOOST_TEST_EQ(log2(u128{two_pow_100}), 100); - - // 2^127 - const auto two_pow_127 = uint128_t{1} << 127U; - BOOST_TEST_EQ(log2(u128{two_pow_127}), 127); - - // 2^127 + 1 (still 127) - BOOST_TEST_EQ(log2(u128{two_pow_127 + uint128_t{1}}), 127); -} - -// ============================================================================= -// Exhaustive u8 tests -// ============================================================================= - -void test_log2_exhaustive_u8() -{ - for (unsigned i {1}; i <= 255; ++i) - { - const auto n = static_cast(i); - auto result = log2(u8{n}); - - // Compute expected floor(log2(i)) - auto expected {0}; - auto tmp {i}; - while (tmp > 1) - { - tmp >>= 1U; - ++expected; - } - - BOOST_TEST_EQ(result, expected); - } -} - -// ============================================================================= -// Constexpr tests -// ============================================================================= - -void test_log2_constexpr() -{ - static_assert(log2(u8{static_cast(1)}) == 0); - static_assert(log2(u8{static_cast(2)}) == 1); - static_assert(log2(u8{static_cast(128)}) == 7); - static_assert(log2(u8{static_cast(255)}) == 7); - - static_assert(log2(u16{static_cast(1024)}) == 10); - static_assert(log2(u16{static_cast(65535)}) == 15); - - static_assert(log2(u32{UINT32_C(1048576)}) == 20); - static_assert(log2(u32{UINT32_C(4294967295)}) == 31); - - static_assert(log2(u64{UINT64_C(1)}) == 0); - static_assert(log2(u64{UINT64_C(9223372036854775808)}) == 63); -} - -int main() -{ - // Powers of 2 - all types - test_log2_powers_of_2(); - test_log2_powers_of_2(); - test_log2_powers_of_2(); - test_log2_powers_of_2(); - test_log2_powers_of_2(); - - // Floor behavior - all types - test_log2_floor(); - test_log2_floor(); - test_log2_floor(); - test_log2_floor(); - test_log2_floor(); - - // Type-specific larger values - test_log2_u16(); - test_log2_u32(); - test_log2_u64(); - test_log2_u128(); - - // Exhaustive u8 - test_log2_exhaustive_u8(); - - // Constexpr evaluation - test_log2_constexpr(); - - return boost::report_errors(); -}