use num_traits::{One, Zero};
pub trait CheckedFloorDiv: Sized {
fn checked_floor_div(&self, rhs: Self) -> Option<Self>;
}
macro_rules! checked_impl {
($t:ty) => {
impl CheckedFloorDiv for $t {
#[track_caller]
#[inline]
fn checked_floor_div(&self, rhs: $t) -> Option<$t> {
let quotient = self.checked_div(rhs)?;
let remainder = self.checked_rem(rhs)?;
if remainder != <$t>::zero() && (remainder > <$t>::zero()) != (rhs > <$t>::zero()) {
quotient.checked_sub(<$t>::one())
} else {
Some(quotient)
}
}
}
};
}
checked_impl!(i128);
checked_impl!(i64);
checked_impl!(i32);
checked_impl!(i16);
checked_impl!(i8);
#[cfg(test)]
mod test {
use crate::math::floor_div::CheckedFloorDiv;
#[test]
fn test_basic_cases() {
let x = -3_i128;
assert_eq!(x.checked_floor_div(2), Some(-2));
assert_eq!(x.checked_floor_div(0), None);
}
#[test]
fn test_positive_dividend_positive_divisor() {
assert_eq!(4_i128.checked_floor_div(2), Some(2));
assert_eq!(6_i128.checked_floor_div(3), Some(2));
assert_eq!(5_i128.checked_floor_div(2), Some(2));
assert_eq!(7_i128.checked_floor_div(3), Some(2));
assert_eq!(1_i128.checked_floor_div(2), Some(0));
assert_eq!(3_i128.checked_floor_div(2), Some(1));
}
#[test]
fn test_negative_dividend_positive_divisor() {
assert_eq!((-4_i128).checked_floor_div(2), Some(-2));
assert_eq!((-6_i128).checked_floor_div(3), Some(-2));
assert_eq!((-5_i128).checked_floor_div(2), Some(-3)); assert_eq!((-7_i128).checked_floor_div(3), Some(-3)); assert_eq!((-1_i128).checked_floor_div(2), Some(-1)); assert_eq!((-3_i128).checked_floor_div(2), Some(-2)); }
#[test]
fn test_positive_dividend_negative_divisor() {
assert_eq!(4_i128.checked_floor_div(-2), Some(-2));
assert_eq!(6_i128.checked_floor_div(-3), Some(-2));
assert_eq!(5_i128.checked_floor_div(-2), Some(-3)); assert_eq!(7_i128.checked_floor_div(-3), Some(-3)); assert_eq!(1_i128.checked_floor_div(-2), Some(-1)); }
#[test]
fn test_negative_dividend_negative_divisor() {
assert_eq!((-4_i128).checked_floor_div(-2), Some(2));
assert_eq!((-6_i128).checked_floor_div(-3), Some(2));
assert_eq!((-5_i128).checked_floor_div(-2), Some(2)); assert_eq!((-7_i128).checked_floor_div(-3), Some(2)); assert_eq!((-1_i128).checked_floor_div(-2), Some(0)); assert_eq!((-3_i128).checked_floor_div(-2), Some(1)); }
#[test]
fn test_edge_cases() {
assert_eq!(0_i128.checked_floor_div(5), Some(0));
assert_eq!(0_i128.checked_floor_div(-5), Some(0));
assert_eq!(5_i128.checked_floor_div(0), None);
assert_eq!((-5_i128).checked_floor_div(0), None);
assert_eq!(0_i128.checked_floor_div(0), None);
assert_eq!(5_i128.checked_floor_div(1), Some(5));
assert_eq!((-5_i128).checked_floor_div(1), Some(-5));
assert_eq!(5_i128.checked_floor_div(-1), Some(-5));
assert_eq!((-5_i128).checked_floor_div(-1), Some(5));
}
#[test]
fn test_boundary_values() {
assert_eq!(i128::MAX.checked_floor_div(1), Some(i128::MAX));
assert_eq!(i128::MIN.checked_floor_div(1), Some(i128::MIN));
assert_eq!(i128::MIN.checked_floor_div(-1), None);
assert_eq!(i128::MAX.checked_floor_div(2), Some(i128::MAX / 2));
assert_eq!(i128::MIN.checked_floor_div(2), Some(i128::MIN / 2));
}
#[test]
fn test_different_integer_types() {
assert_eq!((-3_i64).checked_floor_div(2), Some(-2));
assert_eq!((-5_i64).checked_floor_div(2), Some(-3));
assert_eq!((-3_i32).checked_floor_div(2), Some(-2));
assert_eq!((-5_i32).checked_floor_div(2), Some(-3));
assert_eq!((-3_i16).checked_floor_div(2), Some(-2));
assert_eq!((-5_i16).checked_floor_div(2), Some(-3));
assert_eq!((-3_i8).checked_floor_div(2), Some(-2));
assert_eq!((-5_i8).checked_floor_div(2), Some(-3));
}
#[test]
fn test_comparison_with_standard_div() {
let test_cases = vec![
(7, 3), (-7, 3), (7, -3), (-7, -3), ];
for (dividend, divisor) in test_cases {
let standard_div = dividend / divisor;
let floor_div = dividend.checked_floor_div(divisor).unwrap();
println!(
"dividend: {}, divisor: {}, standard: {}, floor: {}",
dividend, divisor, standard_div, floor_div
);
assert!(floor_div <= standard_div);
}
}
}