diff --git a/doc/modules/ROOT/pages/api_reference.adoc b/doc/modules/ROOT/pages/api_reference.adoc index e0bae32..e74e65b 100644 --- a/doc/modules/ROOT/pages/api_reference.adoc +++ b/doc/modules/ROOT/pages/api_reference.adoc @@ -193,6 +193,9 @@ https://www.boost.org/LICENSE_1_0.txt | xref:integer_utilities.adoc[`div_ceil`] | Computes the ceiling of integer division + +| xref:integer_utilities.adoc[`next_multiple_of`] +| Returns the smallest multiple of `b` that is >= `a` |=== === Byte Conversions @@ -296,7 +299,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`) +| Integer utility functions (`isqrt`, `remove_trailing_zeros`, `is_power_10`, `is_power_2`, `ipow`, `log2`, `log10`, `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 c1c92c5..5bc2156 100644 --- a/doc/modules/ROOT/pages/integer_utilities.adoc +++ b/doc/modules/ROOT/pages/integer_utilities.adoc @@ -394,3 +394,47 @@ auto r4 = div_ceil(u32{1}, u32{2}); // r4 == u32{1} (ceil(0.5) = 1) auto r5 = div_ceil(u32{100}, u32{100}); // r5 == u32{1} ---- +== next_multiple_of + +Computes the smallest multiple of `b` that is greater than or equal to `a`. +This is useful for alignment calculations (e.g., aligning a size to a page boundary or block size). + +[source,c++] +---- +template +[[nodiscard]] constexpr auto next_multiple_of(const T a, const T b) noexcept -> T; +---- + +Returns the smallest value `m` such that `m >= a` and `m % b == 0`. +Equivalent to `div_ceil(a, b) * b`. + +Works with all safe integer types: `u8`, `u16`, `u32`, `u64`, `u128`, and `bounded_uint`. + +==== Parameters + +* `a` -- The value to round up. +* `b` -- The alignment / divisor. Must not be zero. + +==== Return Value + +The smallest multiple of `b` that is greater than or equal to `a`, as the same safe integer type `T`. +When `a` is already a multiple of `b`, returns `a`. +When `a == 0`, returns `0`. + +==== Complexity + +O(1). + +==== Example + +[source,c++] +---- +using namespace boost::safe_numbers; + +auto r1 = next_multiple_of(u32{10}, u32{3}); // r1 == u32{12} (next multiple of 3 >= 10) +auto r2 = next_multiple_of(u32{9}, u32{3}); // r2 == u32{9} (already a multiple) +auto r3 = next_multiple_of(u32{0}, u32{5}); // r3 == u32{0} +auto r4 = next_multiple_of(u32{1}, u32{4096}); // r4 == u32{4096} (align to page size) +auto r5 = next_multiple_of(u32{4097}, u32{4096}); // r5 == u32{8192} +---- + diff --git a/include/boost/safe_numbers/integer_utilities.hpp b/include/boost/safe_numbers/integer_utilities.hpp index 765783e..7bd34e7 100644 --- a/include/boost/safe_numbers/integer_utilities.hpp +++ b/include/boost/safe_numbers/integer_utilities.hpp @@ -143,6 +143,12 @@ template } } +template +[[nodiscard]] constexpr auto next_multiple_of(const T a, const T b) noexcept -> T +{ + return div_ceil(a, b) * b; +} + } // namespace boost::safe_numbers #endif // BOOST_SAFE_NUMBERS_INTEGER_UTILITIES_HPP diff --git a/test/Jamfile b/test/Jamfile index 97cadbe..9143fd1 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -134,6 +134,7 @@ run test_lcm.cpp ; run test_midpoint.cpp ; run test_abs_diff.cpp ; run test_div_ceil.cpp ; +run test_next_multiple_of.cpp ; # Compile Tests compile compile_tests/compile_test_unsigned_integers.cpp ; diff --git a/test/test_next_multiple_of.cpp b/test/test_next_multiple_of.cpp new file mode 100644 index 0000000..3ba426b --- /dev/null +++ b/test/test_next_multiple_of.cpp @@ -0,0 +1,266 @@ +// 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: next_multiple_of(a, b) when a is already a multiple of b +// ============================================================================= + +template +void test_already_multiple() +{ + using underlying = typename detail::underlying_type_t; + + // 0 is a multiple of everything + BOOST_TEST_EQ(next_multiple_of(T{static_cast(0)}, T{static_cast(1)}), T{static_cast(0)}); + BOOST_TEST_EQ(next_multiple_of(T{static_cast(0)}, T{static_cast(5)}), T{static_cast(0)}); + BOOST_TEST_EQ(next_multiple_of(T{static_cast(0)}, T{static_cast(100)}), T{static_cast(0)}); + + // Exact multiples + BOOST_TEST_EQ(next_multiple_of(T{static_cast(6)}, T{static_cast(3)}), T{static_cast(6)}); + BOOST_TEST_EQ(next_multiple_of(T{static_cast(10)}, T{static_cast(5)}), T{static_cast(10)}); + BOOST_TEST_EQ(next_multiple_of(T{static_cast(100)}, T{static_cast(10)}), T{static_cast(100)}); + BOOST_TEST_EQ(next_multiple_of(T{static_cast(12)}, T{static_cast(4)}), T{static_cast(12)}); + BOOST_TEST_EQ(next_multiple_of(T{static_cast(200)}, T{static_cast(25)}), T{static_cast(200)}); +} + +// ============================================================================= +// Runtime tests: next_multiple_of(a, b) when a is NOT a multiple of b +// ============================================================================= + +template +void test_rounding_up() +{ + using underlying = typename detail::underlying_type_t; + + BOOST_TEST_EQ(next_multiple_of(T{static_cast(1)}, T{static_cast(3)}), T{static_cast(3)}); + BOOST_TEST_EQ(next_multiple_of(T{static_cast(2)}, T{static_cast(3)}), T{static_cast(3)}); + BOOST_TEST_EQ(next_multiple_of(T{static_cast(4)}, T{static_cast(3)}), T{static_cast(6)}); + BOOST_TEST_EQ(next_multiple_of(T{static_cast(5)}, T{static_cast(3)}), T{static_cast(6)}); + BOOST_TEST_EQ(next_multiple_of(T{static_cast(7)}, T{static_cast(5)}), T{static_cast(10)}); + BOOST_TEST_EQ(next_multiple_of(T{static_cast(11)}, T{static_cast(10)}), T{static_cast(20)}); + BOOST_TEST_EQ(next_multiple_of(T{static_cast(99)}, T{static_cast(10)}), T{static_cast(100)}); + BOOST_TEST_EQ(next_multiple_of(T{static_cast(101)}, T{static_cast(10)}), T{static_cast(110)}); +} + +// ============================================================================= +// Runtime tests: next_multiple_of(a, 1) == a +// ============================================================================= + +template +void test_multiple_of_one() +{ + using underlying = typename detail::underlying_type_t; + + BOOST_TEST_EQ(next_multiple_of(T{static_cast(0)}, T{static_cast(1)}), T{static_cast(0)}); + BOOST_TEST_EQ(next_multiple_of(T{static_cast(1)}, T{static_cast(1)}), T{static_cast(1)}); + BOOST_TEST_EQ(next_multiple_of(T{static_cast(7)}, T{static_cast(1)}), T{static_cast(7)}); + BOOST_TEST_EQ(next_multiple_of(T{static_cast(42)}, T{static_cast(1)}), T{static_cast(42)}); + BOOST_TEST_EQ(next_multiple_of(T{static_cast(255)}, T{static_cast(1)}), T{static_cast(255)}); +} + +// ============================================================================= +// Runtime tests: next_multiple_of(a, a) == a for a > 0 +// ============================================================================= + +template +void test_multiple_of_self() +{ + using underlying = typename detail::underlying_type_t; + + BOOST_TEST_EQ(next_multiple_of(T{static_cast(1)}, T{static_cast(1)}), T{static_cast(1)}); + BOOST_TEST_EQ(next_multiple_of(T{static_cast(7)}, T{static_cast(7)}), T{static_cast(7)}); + BOOST_TEST_EQ(next_multiple_of(T{static_cast(42)}, T{static_cast(42)}), T{static_cast(42)}); + BOOST_TEST_EQ(next_multiple_of(T{static_cast(100)}, T{static_cast(100)}), T{static_cast(100)}); + BOOST_TEST_EQ(next_multiple_of(T{static_cast(255)}, T{static_cast(255)}), T{static_cast(255)}); +} + +// ============================================================================= +// Type-specific tests for larger values +// ============================================================================= + +void test_next_multiple_of_u16() +{ + BOOST_TEST_EQ(next_multiple_of(u16{static_cast(60001)}, u16{static_cast(10)}), + u16{static_cast(60010)}); + BOOST_TEST_EQ(next_multiple_of(u16{static_cast(60000)}, u16{static_cast(10)}), + u16{static_cast(60000)}); + BOOST_TEST_EQ(next_multiple_of(u16{static_cast(1)}, u16{static_cast(256)}), + u16{static_cast(256)}); + BOOST_TEST_EQ(next_multiple_of(u16{static_cast(257)}, u16{static_cast(256)}), + u16{static_cast(512)}); +} + +void test_next_multiple_of_u32() +{ + BOOST_TEST_EQ(next_multiple_of(u32{UINT32_C(1000000001)}, u32{UINT32_C(1000000000)}), + u32{UINT32_C(2000000000)}); + BOOST_TEST_EQ(next_multiple_of(u32{UINT32_C(1000000000)}, u32{UINT32_C(1000000000)}), + u32{UINT32_C(1000000000)}); + BOOST_TEST_EQ(next_multiple_of(u32{UINT32_C(1)}, u32{UINT32_C(4096)}), + u32{UINT32_C(4096)}); + BOOST_TEST_EQ(next_multiple_of(u32{UINT32_C(4097)}, u32{UINT32_C(4096)}), + u32{UINT32_C(8192)}); +} + +void test_next_multiple_of_u64() +{ + BOOST_TEST_EQ(next_multiple_of(u64{UINT64_C(1000000000000000001)}, u64{UINT64_C(1000000000000000000)}), + u64{UINT64_C(2000000000000000000)}); + BOOST_TEST_EQ(next_multiple_of(u64{UINT64_C(1000000000000000000)}, u64{UINT64_C(1000000000000000000)}), + u64{UINT64_C(1000000000000000000)}); + BOOST_TEST_EQ(next_multiple_of(u64{UINT64_C(10)}, u64{UINT64_C(3)}), + u64{UINT64_C(12)}); + BOOST_TEST_EQ(next_multiple_of(u64{UINT64_C(1)}, u64{UINT64_C(65536)}), + u64{UINT64_C(65536)}); +} + +void test_next_multiple_of_u128() +{ + using boost::int128::uint128_t; + + // Small values + BOOST_TEST_EQ(next_multiple_of(u128{uint128_t{0}}, u128{uint128_t{1}}), u128{uint128_t{0}}); + BOOST_TEST_EQ(next_multiple_of(u128{uint128_t{10}}, u128{uint128_t{3}}), u128{uint128_t{12}}); + BOOST_TEST_EQ(next_multiple_of(u128{uint128_t{10}}, u128{uint128_t{5}}), u128{uint128_t{10}}); + BOOST_TEST_EQ(next_multiple_of(u128{uint128_t{7}}, u128{uint128_t{7}}), u128{uint128_t{7}}); + + // Large values + const auto two_pow_64 = uint128_t{UINT64_C(1)} << 64U; + BOOST_TEST_EQ(next_multiple_of(u128{two_pow_64}, u128{two_pow_64}), u128{two_pow_64}); + BOOST_TEST_EQ(next_multiple_of(u128{uint128_t{1}}, u128{two_pow_64}), u128{two_pow_64}); + + // Equal large values + const auto big_val = uint128_t{UINT64_C(1000000000000)} * uint128_t{UINT64_C(1000000000000)}; + BOOST_TEST_EQ(next_multiple_of(u128{big_val}, u128{big_val}), u128{big_val}); +} + +// ============================================================================= +// Exhaustive u8 tests +// ============================================================================= + +void test_next_multiple_of_exhaustive_u8() +{ + for (unsigned a {0}; a <= 255; ++a) + { + for (unsigned b {1}; b <= 255; ++b) + { + // Reference: smallest multiple of b >= a + const auto expected = ((a + b - 1) / b) * b; + + if (expected > 255) + { + continue; // Skip cases that overflow u8 + } + + BOOST_TEST_EQ(next_multiple_of(u8{static_cast(a)}, u8{static_cast(b)}), + u8{static_cast(expected)}); + } + } +} + +// ============================================================================= +// Bounded uint tests +// ============================================================================= + +void test_next_multiple_of_bounded() +{ + using byte_val = bounded_uint<0u, 255u>; + using word_val = bounded_uint<0u, 65535u>; + + BOOST_TEST_EQ(next_multiple_of(byte_val{static_cast(10)}, byte_val{static_cast(3)}), + byte_val{static_cast(12)}); + BOOST_TEST_EQ(next_multiple_of(byte_val{static_cast(9)}, byte_val{static_cast(3)}), + byte_val{static_cast(9)}); + BOOST_TEST_EQ(next_multiple_of(byte_val{static_cast(0)}, byte_val{static_cast(5)}), + byte_val{static_cast(0)}); + BOOST_TEST_EQ(next_multiple_of(byte_val{static_cast(1)}, byte_val{static_cast(128)}), + byte_val{static_cast(128)}); + + BOOST_TEST_EQ(next_multiple_of(word_val{static_cast(60001)}, word_val{static_cast(10)}), + word_val{static_cast(60010)}); + BOOST_TEST_EQ(next_multiple_of(word_val{static_cast(60000)}, word_val{static_cast(10)}), + word_val{static_cast(60000)}); +} + +// ============================================================================= +// Constexpr tests +// ============================================================================= + +void test_next_multiple_of_constexpr() +{ + static_assert(next_multiple_of(u8{static_cast(10)}, u8{static_cast(3)}) == u8{static_cast(12)}); + static_assert(next_multiple_of(u8{static_cast(9)}, u8{static_cast(3)}) == u8{static_cast(9)}); + static_assert(next_multiple_of(u8{static_cast(0)}, u8{static_cast(1)}) == u8{static_cast(0)}); + static_assert(next_multiple_of(u8{static_cast(1)}, u8{static_cast(5)}) == u8{static_cast(5)}); + + static_assert(next_multiple_of(u16{static_cast(1000)}, u16{static_cast(3)}) == u16{static_cast(1002)}); + + static_assert(next_multiple_of(u32{UINT32_C(100)}, u32{UINT32_C(7)}) == u32{UINT32_C(105)}); + static_assert(next_multiple_of(u32{UINT32_C(1000000000)}, u32{UINT32_C(3)}) == u32{UINT32_C(1000000002)}); + + static_assert(next_multiple_of(u64{UINT64_C(1000)}, u64{UINT64_C(3)}) == u64{UINT64_C(1002)}); +} + +int main() +{ + // Already a multiple - all types + test_already_multiple(); + test_already_multiple(); + test_already_multiple(); + test_already_multiple(); + test_already_multiple(); + + // Rounding up - all types + test_rounding_up(); + test_rounding_up(); + test_rounding_up(); + test_rounding_up(); + test_rounding_up(); + + // Multiple of one - all types + test_multiple_of_one(); + test_multiple_of_one(); + test_multiple_of_one(); + test_multiple_of_one(); + test_multiple_of_one(); + + // Multiple of self - all types + test_multiple_of_self(); + test_multiple_of_self(); + test_multiple_of_self(); + test_multiple_of_self(); + test_multiple_of_self(); + + // Type-specific larger values + test_next_multiple_of_u16(); + test_next_multiple_of_u32(); + test_next_multiple_of_u64(); + test_next_multiple_of_u128(); + + // Exhaustive u8 + test_next_multiple_of_exhaustive_u8(); + + // Bounded uint + test_next_multiple_of_bounded(); + + // Constexpr evaluation + test_next_multiple_of_constexpr(); + + return boost::report_errors(); +}