Skip to content

Commit

Permalink
Add next_multiple_of() and checked_next_multiple_of() for unsigned ints
Browse files Browse the repository at this point in the history
These were added in Rust 1.73:
https://blog.rust-lang.org/2023/10/05/Rust-1.73.0.html

Signed versions exist but are unstable, so they are not added here yet.
  • Loading branch information
danakj committed Dec 4, 2023
1 parent dcdd6a8 commit 232625d
Show file tree
Hide file tree
Showing 10 changed files with 289 additions and 4 deletions.
57 changes: 55 additions & 2 deletions sus/num/__private/unsigned_integer_methods.inc
Original file line number Diff line number Diff line change
Expand Up @@ -1638,7 +1638,7 @@ sus_pure constexpr _self wrapping_rem(U rhs) const& noexcept {
/// # Examples
/// Basic usage:
/// ```
/// assert_eq!((7_u8).div_ceil(4u), 2u);
/// sus::check((7_u8).div_ceil(4u) == 2u);
/// ```
sus_pure constexpr _self div_ceil(_self rhs) const& noexcept {
::sus::check_with_message(rhs.primitive_value != _primitive{0},
Expand Down Expand Up @@ -2138,6 +2138,59 @@ sus_pure constexpr bool is_power_of_two() const& noexcept {
return count_ones() == _self(_primitive{1u});
}

/// Calculates the smallest value greater than or equal to itself that is a
/// multiple of `rhs`.
///
/// # Panics
/// This function will panic if `rhs` is zero.
///
/// ## Overflow behavior
/// On overflow, this function will panic if overflow checks are enabled (they
/// are by default) and wrap if overflow checks are disabled (not the default).
///
/// # Examples
/// Basic usage:
/// ```
/// sus::check((16_u32).next_multiple_of(8u) == 16u);
/// sus::check((23_u32).next_multiple_of(8u) == 24u);
/// ```
sus_pure constexpr _self next_multiple_of(_self rhs) const& noexcept {
const _self r = *this % rhs;
return r == _primitive{0} ? *this : *this + (rhs - r);
}

#if _pointer
template <class U>
requires((UnsignedNumeric<U> || UnsignedPrimitiveInteger<U>) &&
::sus::mem::size_of<U>() <= ::sus::mem::size_of<_primitive>())
sus_pure constexpr _self next_multiple_of(U rhs) const& noexcept {
return next_multiple_of(_self(_primitive{rhs}));
}
#endif

/// Calculates the smallest value greater than or equal to itself that is a
/// multiple of `rhs`. Returns `None` if `rhs` is zero or the operation would
/// result in overflow.
///
/// # Examples
/// Basic usage:
/// ```
/// sus::check((16_u32).checked_next_multiple_of(8u) == sus::some(16u));
/// sus::check((23_u32).checked_next_multiple_of(8u) == sus::some(24u));
/// sus::check((1_u32).checked_next_multiple_of(0u) == sus::none());
/// sus::check(u32::MAX.checked_next_multiple_of(2u) == sus::none());
/// ```
sus_pure constexpr ::sus::option::Option<_self> checked_next_multiple_of(
_self rhs) const& noexcept;

#if _pointer
template <class U>
requires((UnsignedNumeric<U> || UnsignedPrimitiveInteger<U>) &&
::sus::mem::size_of<U>() <= ::sus::mem::size_of<_primitive>())
sus_pure constexpr ::sus::option::Option<_self> checked_next_multiple_of(
U rhs) const& noexcept;
#endif

/// Returns the smallest power of two greater than or equal to self.
///
/// # Panics
Expand All @@ -2146,7 +2199,7 @@ sus_pure constexpr bool is_power_of_two() const& noexcept {
sus_pure constexpr _self next_power_of_two() const& noexcept {
const auto one_less =
__private::one_less_than_next_power_of_two(primitive_value);
return _self(one_less) + _self(_primitive{1u});
return _self(one_less) + _self(_primitive{1});
}

/// Returns the smallest power of two greater than or equal to `self`.
Expand Down
24 changes: 23 additions & 1 deletion sus/num/__private/unsigned_integer_methods_impl.inc
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,28 @@ _self::checked_next_power_of_two() const& noexcept {
return _self(one_less).checked_add(_self(_primitive{1u}));
}

sus_pure constexpr ::sus::option::Option<_self> _self::checked_next_multiple_of(
_self rhs) const& noexcept {
return checked_rem(rhs).and_then([s = *this, rhs](_self r) {
return r == _primitive{0}
? Option<_self>(s)
// SAFETY: rhs - r cannot overflow because `r` is smaller than
// `rhs`, as it's the remainder of `*this / rhs`.
: s.checked_add(__private::unchecked_sub(rhs.primitive_value,
r.primitive_value));
});
}

#if _pointer
template <class U>
requires((UnsignedNumeric<U> || UnsignedPrimitiveInteger<U>) &&
::sus::mem::size_of<U>() <= ::sus::mem::size_of<_primitive>())
sus_pure constexpr ::sus::option::Option<_self> _self::checked_next_multiple_of(
U rhs) const& noexcept {
return checked_next_multiple_of(_self(_primitive{rhs}));
}
#endif

sus_pure constexpr ::sus::collections::Array<u8,
::sus::mem::size_of<_primitive>()>
_self::to_be_bytes() const& noexcept {
Expand Down Expand Up @@ -715,7 +737,7 @@ sus_pure constexpr _self _self::from_ne_bytes(
for (usize i; i < ::sus::mem::size_of<_primitive>(); i += 1u) {
// size_of<_primitive>() is < u32::MAX so the cast<u32>() is not lossy.
val |= bytes[i] << ::sus::cast<u32>(::sus::mem::size_of<_primitive>() -
1u - i);
1u - i);
}
} else {
::sus::ptr::copy_nonoverlapping(
Expand Down
23 changes: 23 additions & 0 deletions sus/num/u16_overflow_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -268,4 +268,27 @@ TEST(u16OverflowDeathTest, DivCeilDivByZero) {
#endif
}

TEST(u16OverflowDeathTest, NextMultipleOfDivByZero) {
#if GTEST_HAS_DEATH_TEST
EXPECT_DEATH(
{
auto x = (0_u16).next_multiple_of(0_u16);
ensure_use(&x);
},
"attempt to calculate the remainder with a divisor of zero");
EXPECT_DEATH(
{
auto x = u16::MAX.next_multiple_of(0_u16);
ensure_use(&x);
},
"attempt to calculate the remainder with a divisor of zero");
#endif

// Overflow occurs but is not checked.
EXPECT_EQ(u16::MAX.next_multiple_of(2_u16), 0u);
EXPECT_EQ(u16::MAX.next_multiple_of(3_u16), u16::MAX);
EXPECT_EQ(u16::MAX.next_multiple_of(4_u16), 0u);
EXPECT_EQ(u16::MAX.next_multiple_of(5_u16), u16::MAX);
}

} // namespace
23 changes: 23 additions & 0 deletions sus/num/u32_overflow_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -273,4 +273,27 @@ TEST(u32OverflowDeathTest, DivCeilDivByZero) {
#endif
}

TEST(u32OverflowDeathTest, NextMultipleOfDivByZero) {
#if GTEST_HAS_DEATH_TEST
EXPECT_DEATH(
{
auto x = (0_u32).next_multiple_of(0_u32);
ensure_use(&x);
},
"attempt to calculate the remainder with a divisor of zero");
EXPECT_DEATH(
{
auto x = u32::MAX.next_multiple_of(0_u32);
ensure_use(&x);
},
"attempt to calculate the remainder with a divisor of zero");
#endif

// Overflow occurs but is not checked.
EXPECT_EQ(u32::MAX.next_multiple_of(2_u32), 0u);
EXPECT_EQ(u32::MAX.next_multiple_of(3_u32), u32::MAX);
EXPECT_EQ(u32::MAX.next_multiple_of(4_u32), 0u);
EXPECT_EQ(u32::MAX.next_multiple_of(5_u32), u32::MAX);
}

} // namespace
56 changes: 56 additions & 0 deletions sus/num/u32_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2204,4 +2204,60 @@ TEST(u32DeathTest, DivCeilDivByZero) {
#endif
}

TEST(u32, NextMultipleOf_Example) {
sus::check((16_u32).next_multiple_of(8u) == 16u);
sus::check((23_u32).next_multiple_of(8u) == 24u);
}

TEST(u32, NextMultipleOf) {
EXPECT_EQ((0_u32).next_multiple_of(1u), 0u);
EXPECT_EQ((1_u32).next_multiple_of(1u), 1u);
EXPECT_EQ((1_u32).next_multiple_of(5u), 5u);
EXPECT_EQ((5_u32).next_multiple_of(1u), 5u);
EXPECT_EQ((16_u32).next_multiple_of(8u), 16u);
EXPECT_EQ((23_u32).next_multiple_of(8u), 24u);
}

TEST(u32DeathTest, NextMultipleOfDivByZero) {
#if GTEST_HAS_DEATH_TEST
EXPECT_DEATH(
{
auto x = (0_u32).next_multiple_of(0_u32);
ensure_use(&x);
},
"attempt to calculate the remainder with a divisor of zero");
EXPECT_DEATH(
{
auto x = u32::MAX.next_multiple_of(0_u32);
ensure_use(&x);
},
"attempt to calculate the remainder with a divisor of zero");
EXPECT_DEATH(
{
auto x = u32::MAX.next_multiple_of(2_u32);
ensure_use(&x);
},
"attempt to add with overflow");
#endif
}

TEST(u32, CheckedNextMultipleOf_Example) {
sus::check((16_u32).checked_next_multiple_of(8u) == sus::some(16u));
sus::check((23_u32).checked_next_multiple_of(8u) == sus::some(24u));
sus::check((1_u32).checked_next_multiple_of(0u) == sus::none());
sus::check(u32::MAX.checked_next_multiple_of(2u) == sus::none());
}

TEST(u32, CheckedNextMultipleOf) {
EXPECT_EQ((0_u32).checked_next_multiple_of(1u), sus::some(0u));
EXPECT_EQ((1_u32).checked_next_multiple_of(1u), sus::some(1u));
EXPECT_EQ((1_u32).checked_next_multiple_of(5u), sus::some(5u));
EXPECT_EQ((5_u32).checked_next_multiple_of(1u), sus::some(5u));
EXPECT_EQ((16_u32).checked_next_multiple_of(8u), sus::some(16u));
EXPECT_EQ((23_u32).checked_next_multiple_of(8u), sus::some(24u));

EXPECT_EQ((23_u32).checked_next_multiple_of(0u), sus::none());
EXPECT_EQ(u32::MAX.checked_next_multiple_of(20u), sus::none());
}

} // namespace
23 changes: 23 additions & 0 deletions sus/num/u64_overflow_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -268,4 +268,27 @@ TEST(u64OverflowDeathTest, DivCeilDivByZero) {
#endif
}

TEST(u64OverflowDeathTest, NextMultipleOfDivByZero) {
#if GTEST_HAS_DEATH_TEST
EXPECT_DEATH(
{
auto x = (0_u64).next_multiple_of(0_u64);
ensure_use(&x);
},
"attempt to calculate the remainder with a divisor of zero");
EXPECT_DEATH(
{
auto x = u64::MAX.next_multiple_of(0_u64);
ensure_use(&x);
},
"attempt to calculate the remainder with a divisor of zero");
#endif

// Overflow occurs but is not checked.
EXPECT_EQ(u64::MAX.next_multiple_of(2_u64), 0u);
EXPECT_EQ(u64::MAX.next_multiple_of(3_u64), u64::MAX);
EXPECT_EQ(u64::MAX.next_multiple_of(4_u64), 0u);
EXPECT_EQ(u64::MAX.next_multiple_of(5_u64), u64::MAX);
}

} // namespace
23 changes: 23 additions & 0 deletions sus/num/u8_overflow_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -244,4 +244,27 @@ TEST(u8OverflowDeathTest, DivCeilDivByZero) {
#endif
}

TEST(u8OverflowDeathTest, NextMultipleOfDivByZero) {
#if GTEST_HAS_DEATH_TEST
EXPECT_DEATH(
{
auto x = (0_u8).next_multiple_of(0_u8);
ensure_use(&x);
},
"attempt to calculate the remainder with a divisor of zero");
EXPECT_DEATH(
{
auto x = u8::MAX.next_multiple_of(0_u8);
ensure_use(&x);
},
"attempt to calculate the remainder with a divisor of zero");
#endif

// Overflow occurs but is not checked.
EXPECT_EQ(u8::MAX.next_multiple_of(2_u8), 0u);
EXPECT_EQ(u8::MAX.next_multiple_of(3_u8), u8::MAX);
EXPECT_EQ(u8::MAX.next_multiple_of(4_u8), 0u);
EXPECT_EQ(u8::MAX.next_multiple_of(5_u8), u8::MAX);
}

} // namespace
25 changes: 25 additions & 0 deletions sus/num/uptr_overflow_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -270,4 +270,29 @@ TEST(uptrOverflowDeathTest, DivCeilDivByZero) {
#endif
}

TEST(uptrOverflowDeathTest, NextMultipleOfDivByZero) {
#if GTEST_HAS_DEATH_TEST
EXPECT_DEATH(
{
auto x = uptr().with_addr(0_usize).next_multiple_of(0_u32);
ensure_use(&x);
},
"attempt to calculate the remainder with a divisor of zero");
EXPECT_DEATH(
{
auto x = uptr::MAX_BIT_PATTERN.next_multiple_of(0_u32);
ensure_use(&x);
},
"attempt to calculate the remainder with a divisor of zero");
#endif

// Overflow occurs but is not checked.
EXPECT_EQ(uptr::MAX_BIT_PATTERN.next_multiple_of(2_u32), 0u);
EXPECT_EQ(uptr::MAX_BIT_PATTERN.next_multiple_of(3_u32),
uptr::MAX_BIT_PATTERN);
EXPECT_EQ(uptr::MAX_BIT_PATTERN.next_multiple_of(4_u32), 0u);
EXPECT_EQ(uptr::MAX_BIT_PATTERN.next_multiple_of(5_u32),
uptr::MAX_BIT_PATTERN);
}

} // namespace
16 changes: 15 additions & 1 deletion sus/num/uptr_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1064,7 +1064,6 @@ TEST(uptr, ArithemticWithSmallerIntegers) {
EXPECT_EQ(i.wrapping_rem_euclid(u), i.wrapping_rem_euclid(i));

// Ceil math.

static_assert(std::same_as<uptr, decltype(i.div_ceil(i))>);
static_assert(std::same_as<uptr, decltype(i.div_ceil(p))>);
static_assert(std::same_as<uptr, decltype(i.div_ceil(u))>);
Expand All @@ -1082,6 +1081,21 @@ TEST(uptr, ArithemticWithSmallerIntegers) {
static_assert(std::same_as<Option<u32>, decltype(i.checked_log(u))>);
EXPECT_EQ(i.checked_log(p), i.checked_log(i));
EXPECT_EQ(i.checked_log(u), i.checked_log(i));

// Next multiple of.
static_assert(std::same_as<uptr, decltype(i.next_multiple_of(i))>);
static_assert(std::same_as<uptr, decltype(i.next_multiple_of(p))>);
static_assert(std::same_as<uptr, decltype(i.next_multiple_of(u))>);
EXPECT_EQ(i.next_multiple_of(p), i.next_multiple_of(i));
EXPECT_EQ(i.next_multiple_of(u), i.next_multiple_of(i));
static_assert(
std::same_as<Option<uptr>, decltype(i.checked_next_multiple_of(i))>);
static_assert(
std::same_as<Option<uptr>, decltype(i.checked_next_multiple_of(p))>);
static_assert(
std::same_as<Option<uptr>, decltype(i.checked_next_multiple_of(u))>);
EXPECT_EQ(i.checked_next_multiple_of(p), i.checked_next_multiple_of(i));
EXPECT_EQ(i.checked_next_multiple_of(u), i.checked_next_multiple_of(i));
}

TEST(uptr, fmt) {
Expand Down
23 changes: 23 additions & 0 deletions sus/num/usize_overflow_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -270,4 +270,27 @@ TEST(usizeOverflowDeathTest, DivCeilDivByZero) {
#endif
}

TEST(usizeOverflowDeathTest, NextMultipleOfDivByZero) {
#if GTEST_HAS_DEATH_TEST
EXPECT_DEATH(
{
auto x = (0_usize).next_multiple_of(0_usize);
ensure_use(&x);
},
"attempt to calculate the remainder with a divisor of zero");
EXPECT_DEATH(
{
auto x = usize::MAX.next_multiple_of(0_usize);
ensure_use(&x);
},
"attempt to calculate the remainder with a divisor of zero");
#endif

// Overflow occurs but is not checked.
EXPECT_EQ(usize::MAX.next_multiple_of(2_usize), 0u);
EXPECT_EQ(usize::MAX.next_multiple_of(3_usize), usize::MAX);
EXPECT_EQ(usize::MAX.next_multiple_of(4_usize), 0u);
EXPECT_EQ(usize::MAX.next_multiple_of(5_usize), usize::MAX);
}

} // namespace

0 comments on commit 232625d

Please sign in to comment.