pub trait CheckedDivCeil {
fn checked_div_ceil(self, rhs: Self) -> Option<Self>
where
Self: Sized;
}
impl CheckedDivCeil for u128 {
fn checked_div_ceil(self, divisor: u128) -> Option<u128> {
if divisor == 0 {
return None;
}
let quotient = self.checked_div(divisor)?;
let product = quotient.checked_mul(divisor)?;
let remainder = self.checked_sub(product)?;
if remainder == 0 {
Some(quotient)
} else {
quotient.checked_add(1)
}
}
}
impl CheckedDivCeil for i128 {
fn checked_div_ceil(self, divisor: i128) -> Option<i128> {
if divisor == 0 {
return None;
}
let quotient = self.checked_div(divisor)?;
let quotient_times_divisor = quotient.checked_mul(divisor)?;
let remainder = self.checked_sub(quotient_times_divisor)?;
if remainder == 0 {
return Some(quotient);
}
let same_sign = (self > 0 && divisor > 0) || (self < 0 && divisor < 0);
if same_sign {
quotient.checked_add(1)
} else {
Some(quotient)
}
}
}
impl CheckedDivCeil for i64 {
fn checked_div_ceil(self, divisor: i64) -> Option<i64> {
if divisor == 0 {
return None;
}
let quotient = self.checked_div(divisor)?;
let quotient_times_divisor = quotient.checked_mul(divisor)?;
let remainder = self.checked_sub(quotient_times_divisor)?;
if remainder == 0 {
return Some(quotient);
}
let same_sign = (self > 0 && divisor > 0) || (self < 0 && divisor < 0);
if same_sign {
quotient.checked_add(1)
} else {
Some(quotient)
}
}
}
#[cfg(test)]
#[allow(
clippy::allow_attributes,
clippy::allow_attributes_without_reason,
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::arithmetic_side_effects,
reason = "test code"
)]
mod tests {
use super::*;
#[test]
fn u128_div_ceil_exact() {
assert_eq!(10u128.checked_div_ceil(5), Some(2));
}
#[test]
fn u128_div_ceil_rounds_up() {
assert_eq!(11u128.checked_div_ceil(5), Some(3));
}
#[test]
fn u128_div_ceil_zero_divisor() {
assert_eq!(10u128.checked_div_ceil(0), None);
}
#[test]
fn i128_div_ceil_positive() {
assert_eq!(11i128.checked_div_ceil(5), Some(3));
}
#[test]
fn i128_div_ceil_negative_no_roundup() {
assert_eq!((-11i128).checked_div_ceil(5), Some(-2));
}
#[test]
fn i128_div_ceil_both_negative_rounds_up() {
assert_eq!((-11i128).checked_div_ceil(-5), Some(3));
}
#[test]
fn i64_div_ceil_exact() {
assert_eq!(10i64.checked_div_ceil(5), Some(2));
}
#[test]
fn i64_div_ceil_rounds_up() {
assert_eq!(11i64.checked_div_ceil(5), Some(3));
}
}
#[cfg(test)]
#[allow(
clippy::allow_attributes,
clippy::allow_attributes_without_reason,
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::arithmetic_side_effects,
reason = "test code"
)]
mod proptests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn u128_div_ceil_never_panics(a: u128, b: u128) {
let _ = a.checked_div_ceil(b);
}
#[test]
fn u128_div_ceil_correct(a in 0u128..=u128::MAX / 2, b in 1u128..=1_000_000_000) {
let result = a.checked_div_ceil(b).unwrap();
let floor = a / b;
let remainder = a % b;
if remainder == 0 {
prop_assert_eq!(result, floor);
} else {
prop_assert_eq!(result, floor + 1);
}
}
#[test]
fn i128_div_ceil_never_panics(a: i64, b: i64) {
let _ = (a as i128).checked_div_ceil(b as i128);
}
#[test]
fn i128_div_ceil_rounds_toward_positive_infinity_for_same_sign(
a in 1i128..=1_000_000_000_000,
b in 1i128..=1_000_000_000,
) {
let result = a.checked_div_ceil(b).unwrap();
prop_assert!(result * b >= a);
prop_assert!((result - 1) * b < a);
}
}
}