Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new unsigned integer methods: next_multiple_of, checked_next_multiple_of, div_ceil #418

Merged
merged 2 commits into from
Dec 4, 2023
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
85 changes: 84 additions & 1 deletion sus/num/__private/unsigned_integer_methods.inc
Original file line number Diff line number Diff line change
Expand Up @@ -1629,6 +1629,36 @@ sus_pure constexpr _self wrapping_rem(U rhs) const& noexcept {
}
#endif

/// Calculates the quotient of itself and `rhs`, rounding the result towards
/// positive infinity.
///
/// # Panics
/// This function will panic if `rhs` is zero.
///
/// # Examples
/// Basic usage:
/// ```
/// 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},
"attempt to divide by zero");
_self d =
_self(__private::unchecked_div(primitive_value, rhs.primitive_value));
_self r =
_self(__private::unchecked_rem(primitive_value, rhs.primitive_value));
return (r > _primitive{0}) ? d + _primitive{1} : d;
}

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

/// Performs Euclidean division.
///
/// Since, for the positive integers, all common definitions of division are
Expand Down Expand Up @@ -2108,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 @@ -2116,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
40 changes: 40 additions & 0 deletions sus/num/u16_overflow_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -251,4 +251,44 @@ TEST(u16OverflowDeathTest, WrappingRemEuclidOverflow) {
#endif
}

TEST(u16OverflowDeathTest, DivCeilDivByZero) {
#if GTEST_HAS_DEATH_TEST
EXPECT_DEATH(
{
auto x = (0_u16).div_ceil(0_u16);
ensure_use(&x);
},
"attempt to divide by zero");
EXPECT_DEATH(
{
auto x = u16::MAX.div_ceil(0_u16);
ensure_use(&x);
},
"attempt to divide by zero");
#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
2 changes: 2 additions & 0 deletions sus/num/u16_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,8 @@ TEST(u16, InvokeEverything) {
(void)i.overflowing_rem_euclid(j);
(void)i.wrapping_rem_euclid(j);

(void)i.div_ceil(j);

(void)i.checked_shl(1_u32);
(void)i.overflowing_shl(1_u32);
(void)i.wrapping_shl(1_u32);
Expand Down
40 changes: 40 additions & 0 deletions sus/num/u32_overflow_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -256,4 +256,44 @@ TEST(u32Overlow, NextPowerOfTwoOutOfBounds) {
EXPECT_EQ(u32::MAX.next_power_of_two(), 0_u32);
}

TEST(u32OverflowDeathTest, DivCeilDivByZero) {
#if GTEST_HAS_DEATH_TEST
EXPECT_DEATH(
{
auto x = (0_u32).div_ceil(0_u32);
ensure_use(&x);
},
"attempt to divide by zero");
EXPECT_DEATH(
{
auto x = u32::MAX.div_ceil(0_u32);
ensure_use(&x);
},
"attempt to divide by zero");
#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
92 changes: 92 additions & 0 deletions sus/num/u32_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2168,4 +2168,96 @@ static_assert([] {
return v.as_mut_ptr() == &v.primitive_value;
}());

TEST(u32, DivCeil) {
EXPECT_EQ((7_u32).div_ceil(1u), 7u);
EXPECT_EQ((7_u32).div_ceil(2u), 4u);
EXPECT_EQ((7_u32).div_ceil(3u), 3u);
EXPECT_EQ((7_u32).div_ceil(4u), 2u);
EXPECT_EQ((7_u32).div_ceil(5u), 2u);
EXPECT_EQ((7_u32).div_ceil(6u), 2u);
EXPECT_EQ((7_u32).div_ceil(7u), 1u);
EXPECT_EQ((7_u32).div_ceil(8u), 1u);

EXPECT_EQ((6_u32).div_ceil(1u), 6u);
EXPECT_EQ((6_u32).div_ceil(2u), 3u);
EXPECT_EQ((6_u32).div_ceil(3u), 2u);
EXPECT_EQ((6_u32).div_ceil(4u), 2u);
EXPECT_EQ((6_u32).div_ceil(5u), 2u);
EXPECT_EQ((6_u32).div_ceil(6u), 1u);
EXPECT_EQ((6_u32).div_ceil(7u), 1u);
}

TEST(u32DeathTest, DivCeilDivByZero) {
#if GTEST_HAS_DEATH_TEST
EXPECT_DEATH(
{
auto x = (0_u32).div_ceil(0_u32);
ensure_use(&x);
},
"attempt to divide by zero");
EXPECT_DEATH(
{
auto x = u32::MAX.div_ceil(0_u32);
ensure_use(&x);
},
"attempt to divide by zero");
#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
40 changes: 40 additions & 0 deletions sus/num/u64_overflow_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -251,4 +251,44 @@ TEST(u64OverflowDeathTest, WrappingRemEuclidOverflow) {
#endif
}

TEST(u64OverflowDeathTest, DivCeilDivByZero) {
#if GTEST_HAS_DEATH_TEST
EXPECT_DEATH(
{
auto x = (0_u64).div_ceil(0_u64);
ensure_use(&x);
},
"attempt to divide by zero");
EXPECT_DEATH(
{
auto x = u64::MAX.div_ceil(0_u64);
ensure_use(&x);
},
"attempt to divide by zero");
#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
2 changes: 2 additions & 0 deletions sus/num/u64_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,8 @@ TEST(u64, InvokeEverything) {
(void)i.overflowing_rem_euclid(j);
(void)i.wrapping_rem_euclid(j);

(void)i.div_ceil(j);

(void)i.checked_shl(1_u32);
(void)i.overflowing_shl(1_u32);
(void)i.wrapping_shl(1_u32);
Expand Down
Loading
Loading