Skip to main content

use_arithmetic/
division.rs

1/// Returns the mathematical floor of `dividend / divisor`.
2///
3/// This helper follows floor semantics over signed integers instead of Rust's
4/// truncating division semantics.
5///
6/// Returns `None` when `divisor` is zero or when the exact quotient does not
7/// fit in `i64`.
8///
9/// # Examples
10///
11/// ```rust
12/// use use_arithmetic::checked_div_floor;
13///
14/// assert_eq!(checked_div_floor(-7, 3), Some(-3));
15/// assert_eq!(checked_div_floor(7, -3), Some(-3));
16/// assert_eq!(checked_div_floor(7, 0), None);
17/// ```
18#[must_use]
19pub fn checked_div_floor(dividend: i64, divisor: i64) -> Option<i64> {
20    let quotient = floor_quotient(dividend, divisor)?;
21
22    i64::try_from(quotient).ok()
23}
24
25/// Returns the mathematical ceiling of `dividend / divisor`.
26///
27/// Returns `None` when `divisor` is zero or when the exact quotient does not
28/// fit in `i64`.
29///
30/// # Examples
31///
32/// ```rust
33/// use use_arithmetic::checked_div_ceil;
34///
35/// assert_eq!(checked_div_ceil(-7, 3), Some(-2));
36/// assert_eq!(checked_div_ceil(7, -3), Some(-2));
37/// assert_eq!(checked_div_ceil(7, 0), None);
38/// ```
39#[must_use]
40pub fn checked_div_ceil(dividend: i64, divisor: i64) -> Option<i64> {
41    let quotient = ceil_quotient(dividend, divisor)?;
42
43    i64::try_from(quotient).ok()
44}
45
46/// Returns the mathematical floor-style remainder of `dividend / divisor`.
47///
48/// The result satisfies `dividend = divisor * div_floor(dividend, divisor) + mod_floor(dividend, divisor)`.
49///
50/// Returns `None` when `divisor` is zero or when the matching quotient does
51/// not fit in `i64`.
52///
53/// # Examples
54///
55/// ```rust
56/// use use_arithmetic::checked_mod_floor;
57///
58/// assert_eq!(checked_mod_floor(-7, 3), Some(2));
59/// assert_eq!(checked_mod_floor(7, -3), Some(-2));
60/// assert_eq!(checked_mod_floor(7, 0), None);
61/// ```
62#[must_use]
63pub fn checked_mod_floor(dividend: i64, divisor: i64) -> Option<i64> {
64    let quotient = i128::from(checked_div_floor(dividend, divisor)?);
65    let remainder = i128::from(dividend) - (i128::from(divisor) * quotient);
66
67    i64::try_from(remainder).ok()
68}
69
70/// Returns the mathematical floor of `dividend / divisor`.
71///
72/// # Panics
73///
74/// Panics when `divisor` is zero or when the exact quotient does not fit in
75/// `i64`.
76#[must_use]
77pub fn div_floor(dividend: i64, divisor: i64) -> i64 {
78    checked_div_floor(dividend, divisor)
79        .unwrap_or_else(|| panic!("div_floor requires a non-zero divisor and an in-range quotient"))
80}
81
82/// Returns the mathematical ceiling of `dividend / divisor`.
83///
84/// # Panics
85///
86/// Panics when `divisor` is zero or when the exact quotient does not fit in
87/// `i64`.
88#[must_use]
89pub fn div_ceil(dividend: i64, divisor: i64) -> i64 {
90    checked_div_ceil(dividend, divisor)
91        .unwrap_or_else(|| panic!("div_ceil requires a non-zero divisor and an in-range quotient"))
92}
93
94/// Returns the mathematical floor-style remainder of `dividend / divisor`.
95///
96/// # Panics
97///
98/// Panics when `divisor` is zero or when the matching quotient does not fit in
99/// `i64`.
100#[must_use]
101pub fn mod_floor(dividend: i64, divisor: i64) -> i64 {
102    checked_mod_floor(dividend, divisor)
103        .unwrap_or_else(|| panic!("mod_floor requires a non-zero divisor and an in-range quotient"))
104}
105
106fn floor_quotient(dividend: i64, divisor: i64) -> Option<i128> {
107    if divisor == 0 {
108        return None;
109    }
110
111    let dividend = i128::from(dividend);
112    let divisor = i128::from(divisor);
113    let quotient = dividend / divisor;
114    let remainder = dividend % divisor;
115
116    if remainder != 0 && ((remainder > 0) != (divisor > 0)) {
117        Some(quotient - 1)
118    } else {
119        Some(quotient)
120    }
121}
122
123fn ceil_quotient(dividend: i64, divisor: i64) -> Option<i128> {
124    if divisor == 0 {
125        return None;
126    }
127
128    let dividend = i128::from(dividend);
129    let divisor = i128::from(divisor);
130    let quotient = dividend / divisor;
131    let remainder = dividend % divisor;
132
133    if remainder != 0 && ((remainder > 0) == (divisor > 0)) {
134        Some(quotient + 1)
135    } else {
136        Some(quotient)
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use super::{
143        checked_div_ceil, checked_div_floor, checked_mod_floor, div_ceil, div_floor, mod_floor,
144    };
145
146    #[test]
147    fn matches_floor_semantics_for_negative_values() {
148        assert_eq!(checked_div_floor(-7, 3), Some(-3));
149        assert_eq!(checked_div_floor(7, -3), Some(-3));
150        assert_eq!(checked_div_floor(-7, -3), Some(2));
151        assert_eq!(checked_div_ceil(-7, 3), Some(-2));
152        assert_eq!(checked_div_ceil(7, -3), Some(-2));
153        assert_eq!(checked_mod_floor(-7, 3), Some(2));
154        assert_eq!(checked_mod_floor(7, -3), Some(-2));
155        assert_eq!(div_floor(-7, 3), -3);
156        assert_eq!(div_ceil(-7, 3), -2);
157        assert_eq!(mod_floor(-7, 3), 2);
158    }
159
160    #[test]
161    fn rejects_zero_divisors_and_overflowing_quotients() {
162        assert_eq!(checked_div_floor(7, 0), None);
163        assert_eq!(checked_div_ceil(7, 0), None);
164        assert_eq!(checked_mod_floor(7, 0), None);
165        assert_eq!(checked_div_floor(i64::MIN, -1), None);
166        assert_eq!(checked_div_ceil(i64::MIN, -1), None);
167        assert_eq!(checked_mod_floor(i64::MIN, -1), None);
168    }
169
170    #[test]
171    #[should_panic(expected = "div_floor requires a non-zero divisor and an in-range quotient")]
172    fn plain_div_floor_panics_on_zero_divisor() {
173        let _ = div_floor(7, 0);
174    }
175
176    #[test]
177    #[should_panic(expected = "div_ceil requires a non-zero divisor and an in-range quotient")]
178    fn plain_div_ceil_panics_on_zero_divisor() {
179        let _ = div_ceil(7, 0);
180    }
181
182    #[test]
183    #[should_panic(expected = "mod_floor requires a non-zero divisor and an in-range quotient")]
184    fn plain_mod_floor_panics_on_zero_divisor() {
185        let _ = mod_floor(7, 0);
186    }
187}