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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion doc/modules/ROOT/pages/api_reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -296,7 +299,7 @@ This header is not included in the convenience header since it requires external
| Contains specializations of `<format>` for library types

| `<boost/safe_numbers/integer_utilities.hpp>`
| 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`)

| `<boost/safe_numbers/numeric.hpp>`
| Standard numeric algorithms (`gcd`, `lcm`, `midpoint`)
Expand Down
44 changes: 44 additions & 0 deletions doc/modules/ROOT/pages/integer_utilities.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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 <integral_library_type T>
[[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<Min, Max>`.

==== 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}
----

6 changes: 6 additions & 0 deletions include/boost/safe_numbers/integer_utilities.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,12 @@ template <detail::integral_library_type T>
}
}

template <detail::integral_library_type T>
[[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
1 change: 1 addition & 0 deletions test/Jamfile
Original file line number Diff line number Diff line change
Expand Up @@ -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 ;
Expand Down
266 changes: 266 additions & 0 deletions test/test_next_multiple_of.cpp
Original file line number Diff line number Diff line change
@@ -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 <boost/safe_numbers.hpp>
#include <cstdint>

#endif

#include <boost/core/lightweight_test.hpp>

using namespace boost::safe_numbers;

// =============================================================================
// Runtime tests: next_multiple_of(a, b) when a is already a multiple of b
// =============================================================================

template <typename T>
void test_already_multiple()
{
using underlying = typename detail::underlying_type_t<T>;

// 0 is a multiple of everything
BOOST_TEST_EQ(next_multiple_of(T{static_cast<underlying>(0)}, T{static_cast<underlying>(1)}), T{static_cast<underlying>(0)});
BOOST_TEST_EQ(next_multiple_of(T{static_cast<underlying>(0)}, T{static_cast<underlying>(5)}), T{static_cast<underlying>(0)});
BOOST_TEST_EQ(next_multiple_of(T{static_cast<underlying>(0)}, T{static_cast<underlying>(100)}), T{static_cast<underlying>(0)});

// Exact multiples
BOOST_TEST_EQ(next_multiple_of(T{static_cast<underlying>(6)}, T{static_cast<underlying>(3)}), T{static_cast<underlying>(6)});
BOOST_TEST_EQ(next_multiple_of(T{static_cast<underlying>(10)}, T{static_cast<underlying>(5)}), T{static_cast<underlying>(10)});
BOOST_TEST_EQ(next_multiple_of(T{static_cast<underlying>(100)}, T{static_cast<underlying>(10)}), T{static_cast<underlying>(100)});
BOOST_TEST_EQ(next_multiple_of(T{static_cast<underlying>(12)}, T{static_cast<underlying>(4)}), T{static_cast<underlying>(12)});
BOOST_TEST_EQ(next_multiple_of(T{static_cast<underlying>(200)}, T{static_cast<underlying>(25)}), T{static_cast<underlying>(200)});
}

// =============================================================================
// Runtime tests: next_multiple_of(a, b) when a is NOT a multiple of b
// =============================================================================

template <typename T>
void test_rounding_up()
{
using underlying = typename detail::underlying_type_t<T>;

BOOST_TEST_EQ(next_multiple_of(T{static_cast<underlying>(1)}, T{static_cast<underlying>(3)}), T{static_cast<underlying>(3)});
BOOST_TEST_EQ(next_multiple_of(T{static_cast<underlying>(2)}, T{static_cast<underlying>(3)}), T{static_cast<underlying>(3)});
BOOST_TEST_EQ(next_multiple_of(T{static_cast<underlying>(4)}, T{static_cast<underlying>(3)}), T{static_cast<underlying>(6)});
BOOST_TEST_EQ(next_multiple_of(T{static_cast<underlying>(5)}, T{static_cast<underlying>(3)}), T{static_cast<underlying>(6)});
BOOST_TEST_EQ(next_multiple_of(T{static_cast<underlying>(7)}, T{static_cast<underlying>(5)}), T{static_cast<underlying>(10)});
BOOST_TEST_EQ(next_multiple_of(T{static_cast<underlying>(11)}, T{static_cast<underlying>(10)}), T{static_cast<underlying>(20)});
BOOST_TEST_EQ(next_multiple_of(T{static_cast<underlying>(99)}, T{static_cast<underlying>(10)}), T{static_cast<underlying>(100)});
BOOST_TEST_EQ(next_multiple_of(T{static_cast<underlying>(101)}, T{static_cast<underlying>(10)}), T{static_cast<underlying>(110)});
}

// =============================================================================
// Runtime tests: next_multiple_of(a, 1) == a
// =============================================================================

template <typename T>
void test_multiple_of_one()
{
using underlying = typename detail::underlying_type_t<T>;

BOOST_TEST_EQ(next_multiple_of(T{static_cast<underlying>(0)}, T{static_cast<underlying>(1)}), T{static_cast<underlying>(0)});
BOOST_TEST_EQ(next_multiple_of(T{static_cast<underlying>(1)}, T{static_cast<underlying>(1)}), T{static_cast<underlying>(1)});
BOOST_TEST_EQ(next_multiple_of(T{static_cast<underlying>(7)}, T{static_cast<underlying>(1)}), T{static_cast<underlying>(7)});
BOOST_TEST_EQ(next_multiple_of(T{static_cast<underlying>(42)}, T{static_cast<underlying>(1)}), T{static_cast<underlying>(42)});
BOOST_TEST_EQ(next_multiple_of(T{static_cast<underlying>(255)}, T{static_cast<underlying>(1)}), T{static_cast<underlying>(255)});
}

// =============================================================================
// Runtime tests: next_multiple_of(a, a) == a for a > 0
// =============================================================================

template <typename T>
void test_multiple_of_self()
{
using underlying = typename detail::underlying_type_t<T>;

BOOST_TEST_EQ(next_multiple_of(T{static_cast<underlying>(1)}, T{static_cast<underlying>(1)}), T{static_cast<underlying>(1)});
BOOST_TEST_EQ(next_multiple_of(T{static_cast<underlying>(7)}, T{static_cast<underlying>(7)}), T{static_cast<underlying>(7)});
BOOST_TEST_EQ(next_multiple_of(T{static_cast<underlying>(42)}, T{static_cast<underlying>(42)}), T{static_cast<underlying>(42)});
BOOST_TEST_EQ(next_multiple_of(T{static_cast<underlying>(100)}, T{static_cast<underlying>(100)}), T{static_cast<underlying>(100)});
BOOST_TEST_EQ(next_multiple_of(T{static_cast<underlying>(255)}, T{static_cast<underlying>(255)}), T{static_cast<underlying>(255)});
}

// =============================================================================
// Type-specific tests for larger values
// =============================================================================

void test_next_multiple_of_u16()
{
BOOST_TEST_EQ(next_multiple_of(u16{static_cast<std::uint16_t>(60001)}, u16{static_cast<std::uint16_t>(10)}),
u16{static_cast<std::uint16_t>(60010)});
BOOST_TEST_EQ(next_multiple_of(u16{static_cast<std::uint16_t>(60000)}, u16{static_cast<std::uint16_t>(10)}),
u16{static_cast<std::uint16_t>(60000)});
BOOST_TEST_EQ(next_multiple_of(u16{static_cast<std::uint16_t>(1)}, u16{static_cast<std::uint16_t>(256)}),
u16{static_cast<std::uint16_t>(256)});
BOOST_TEST_EQ(next_multiple_of(u16{static_cast<std::uint16_t>(257)}, u16{static_cast<std::uint16_t>(256)}),
u16{static_cast<std::uint16_t>(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<std::uint8_t>(a)}, u8{static_cast<std::uint8_t>(b)}),
u8{static_cast<std::uint8_t>(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<std::uint8_t>(10)}, byte_val{static_cast<std::uint8_t>(3)}),
byte_val{static_cast<std::uint8_t>(12)});
BOOST_TEST_EQ(next_multiple_of(byte_val{static_cast<std::uint8_t>(9)}, byte_val{static_cast<std::uint8_t>(3)}),
byte_val{static_cast<std::uint8_t>(9)});
BOOST_TEST_EQ(next_multiple_of(byte_val{static_cast<std::uint8_t>(0)}, byte_val{static_cast<std::uint8_t>(5)}),
byte_val{static_cast<std::uint8_t>(0)});
BOOST_TEST_EQ(next_multiple_of(byte_val{static_cast<std::uint8_t>(1)}, byte_val{static_cast<std::uint8_t>(128)}),
byte_val{static_cast<std::uint8_t>(128)});

BOOST_TEST_EQ(next_multiple_of(word_val{static_cast<std::uint16_t>(60001)}, word_val{static_cast<std::uint16_t>(10)}),
word_val{static_cast<std::uint16_t>(60010)});
BOOST_TEST_EQ(next_multiple_of(word_val{static_cast<std::uint16_t>(60000)}, word_val{static_cast<std::uint16_t>(10)}),
word_val{static_cast<std::uint16_t>(60000)});
}

// =============================================================================
// Constexpr tests
// =============================================================================

void test_next_multiple_of_constexpr()
{
static_assert(next_multiple_of(u8{static_cast<std::uint8_t>(10)}, u8{static_cast<std::uint8_t>(3)}) == u8{static_cast<std::uint8_t>(12)});
static_assert(next_multiple_of(u8{static_cast<std::uint8_t>(9)}, u8{static_cast<std::uint8_t>(3)}) == u8{static_cast<std::uint8_t>(9)});
static_assert(next_multiple_of(u8{static_cast<std::uint8_t>(0)}, u8{static_cast<std::uint8_t>(1)}) == u8{static_cast<std::uint8_t>(0)});
static_assert(next_multiple_of(u8{static_cast<std::uint8_t>(1)}, u8{static_cast<std::uint8_t>(5)}) == u8{static_cast<std::uint8_t>(5)});

static_assert(next_multiple_of(u16{static_cast<std::uint16_t>(1000)}, u16{static_cast<std::uint16_t>(3)}) == u16{static_cast<std::uint16_t>(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<u8>();
test_already_multiple<u16>();
test_already_multiple<u32>();
test_already_multiple<u64>();
test_already_multiple<u128>();

// Rounding up - all types
test_rounding_up<u8>();
test_rounding_up<u16>();
test_rounding_up<u32>();
test_rounding_up<u64>();
test_rounding_up<u128>();

// Multiple of one - all types
test_multiple_of_one<u8>();
test_multiple_of_one<u16>();
test_multiple_of_one<u32>();
test_multiple_of_one<u64>();
test_multiple_of_one<u128>();

// Multiple of self - all types
test_multiple_of_self<u8>();
test_multiple_of_self<u16>();
test_multiple_of_self<u32>();
test_multiple_of_self<u64>();
test_multiple_of_self<u128>();

// 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();
}