1pub trait CheckedDivCeil {
3 fn checked_div_ceil(self, rhs: Self) -> Option<Self>
4 where
5 Self: Sized;
6}
7
8impl CheckedDivCeil for u128 {
9 fn checked_div_ceil(self, divisor: u128) -> Option<u128> {
10 if divisor == 0 {
11 return None;
12 }
13 let quotient = self.checked_div(divisor)?;
14 let product = quotient.checked_mul(divisor)?;
15 let remainder = self.checked_sub(product)?;
16 if remainder == 0 {
17 Some(quotient)
18 } else {
19 quotient.checked_add(1)
20 }
21 }
22}
23
24impl CheckedDivCeil for i128 {
25 fn checked_div_ceil(self, divisor: i128) -> Option<i128> {
26 if divisor == 0 {
27 return None;
28 }
29 let quotient = self.checked_div(divisor)?;
30 let quotient_times_divisor = quotient.checked_mul(divisor)?;
31 let remainder = self.checked_sub(quotient_times_divisor)?;
32 if remainder == 0 {
33 return Some(quotient);
34 }
35 let same_sign = (self > 0 && divisor > 0) || (self < 0 && divisor < 0);
37 if same_sign {
38 quotient.checked_add(1)
39 } else {
40 Some(quotient)
41 }
42 }
43}
44
45impl CheckedDivCeil for i64 {
46 fn checked_div_ceil(self, divisor: i64) -> Option<i64> {
47 if divisor == 0 {
48 return None;
49 }
50 let quotient = self.checked_div(divisor)?;
51 let quotient_times_divisor = quotient.checked_mul(divisor)?;
52 let remainder = self.checked_sub(quotient_times_divisor)?;
53 if remainder == 0 {
54 return Some(quotient);
55 }
56 let same_sign = (self > 0 && divisor > 0) || (self < 0 && divisor < 0);
57 if same_sign {
58 quotient.checked_add(1)
59 } else {
60 Some(quotient)
61 }
62 }
63}
64
65#[cfg(test)]
66#[allow(
67 clippy::allow_attributes,
68 clippy::allow_attributes_without_reason,
69 clippy::unwrap_used,
70 clippy::expect_used,
71 clippy::panic,
72 clippy::arithmetic_side_effects,
73 reason = "test code"
74)]
75mod tests {
76 use super::*;
77
78 #[test]
79 fn u128_div_ceil_exact() {
80 assert_eq!(10u128.checked_div_ceil(5), Some(2));
81 }
82
83 #[test]
84 fn u128_div_ceil_rounds_up() {
85 assert_eq!(11u128.checked_div_ceil(5), Some(3));
86 }
87
88 #[test]
89 fn u128_div_ceil_zero_divisor() {
90 assert_eq!(10u128.checked_div_ceil(0), None);
91 }
92
93 #[test]
94 fn i128_div_ceil_positive() {
95 assert_eq!(11i128.checked_div_ceil(5), Some(3));
96 }
97
98 #[test]
99 fn i128_div_ceil_negative_no_roundup() {
100 assert_eq!((-11i128).checked_div_ceil(5), Some(-2));
102 }
103
104 #[test]
105 fn i128_div_ceil_both_negative_rounds_up() {
106 assert_eq!((-11i128).checked_div_ceil(-5), Some(3));
108 }
109
110 #[test]
111 fn i64_div_ceil_exact() {
112 assert_eq!(10i64.checked_div_ceil(5), Some(2));
113 }
114
115 #[test]
116 fn i64_div_ceil_rounds_up() {
117 assert_eq!(11i64.checked_div_ceil(5), Some(3));
118 }
119}
120
121#[cfg(test)]
122#[allow(
123 clippy::allow_attributes,
124 clippy::allow_attributes_without_reason,
125 clippy::unwrap_used,
126 clippy::expect_used,
127 clippy::panic,
128 clippy::arithmetic_side_effects,
129 reason = "test code"
130)]
131mod proptests {
132 use super::*;
133 use proptest::prelude::*;
134
135 proptest! {
136 #[test]
137 fn u128_div_ceil_never_panics(a: u128, b: u128) {
138 let _ = a.checked_div_ceil(b);
139 }
140
141 #[test]
142 fn u128_div_ceil_correct(a in 0u128..=u128::MAX / 2, b in 1u128..=1_000_000_000) {
143 let result = a.checked_div_ceil(b).unwrap();
144 let floor = a / b;
146 let remainder = a % b;
147 if remainder == 0 {
148 prop_assert_eq!(result, floor);
149 } else {
150 prop_assert_eq!(result, floor + 1);
151 }
152 }
153
154 #[test]
155 fn i128_div_ceil_never_panics(a: i64, b: i64) {
156 let _ = (a as i128).checked_div_ceil(b as i128);
157 }
158
159 #[test]
160 fn i128_div_ceil_rounds_toward_positive_infinity_for_same_sign(
161 a in 1i128..=1_000_000_000_000,
162 b in 1i128..=1_000_000_000,
163 ) {
164 let result = a.checked_div_ceil(b).unwrap();
165 prop_assert!(result * b >= a);
167 prop_assert!((result - 1) * b < a);
169 }
170 }
171}