const_num_traits/ops/rounding.rs
1//! Division-rounding and related operations: `div_ceil`, `div_floor`,
2//! exact division, multiple-of computations and overflow-free `midpoint`,
3//! mirroring the inherent methods on the primitive integer types.
4//!
5//! Stability in std (as of nightly 2026): `div_ceil` / `next_multiple_of`
6//! (unsigned) are stable since 1.73, `is_multiple_of` since 1.87, `midpoint`
7//! since 1.85/1.87; the signed `div_ceil` / `div_floor` /
8//! `next_multiple_of` and `div_exact` / `checked_div_exact` are still
9//! nightly-only. Anything newer than the crate's MSRV is hand-rolled here
10//! with the same algorithm as core.
11//!
12//! **CT tiers**: [`Midpoint`] is Tier A (branchless); everything else here
13//! is Tier C (division-based).
14
15c0nst::c0nst! {
16/// Performs division, rounding the quotient towards positive infinity.
17pub c0nst trait DivCeil: Sized {
18 /// The quotient type (`Self` for the primitive impls).
19 type Output;
20 /// Calculates the quotient of `self` and `rhs`, rounding the result
21 /// towards positive infinity.
22 ///
23 /// # Panics
24 ///
25 /// Panics if `rhs` is zero, or — with overflow checks enabled — on
26 /// `MIN / -1` for signed types.
27 ///
28 /// ```
29 /// use const_num_traits::DivCeil;
30 ///
31 /// assert_eq!(DivCeil::div_ceil(7u8, 2), 4);
32 /// assert_eq!(DivCeil::div_ceil(7i8, -2), -3);
33 /// assert_eq!(DivCeil::div_ceil(-7i8, 2), -3);
34 /// ```
35 fn div_ceil(self, rhs: Self) -> Self::Output;
36}
37}
38
39macro_rules! div_ceil_impl {
40 (unsigned $($t:ty)*) => {$(
41 c0nst::c0nst! {
42 c0nst impl DivCeil for $t {
43 type Output = $t;
44 #[inline]
45 fn div_ceil(self, rhs: Self) -> Self {
46 <$t>::div_ceil(self, rhs)
47 }
48 }
49 }
50 )*};
51 // signed div_ceil is still unstable in std; same branchless algorithm
52 (signed $($t:ty)*) => {$(
53 c0nst::c0nst! {
54 c0nst impl DivCeil for $t {
55 type Output = $t;
56 #[inline]
57 fn div_ceil(self, rhs: Self) -> Self {
58 let d = self / rhs;
59 let r = self % rhs;
60 // `(self ^ rhs) >> (BITS - 1)` is -1 when the signs differ,
61 // 0 when they match; +1 only when rounding up is needed.
62 let correction = 1 + ((self ^ rhs) >> (<$t>::BITS - 1));
63 if r != 0 {
64 d + correction
65 } else {
66 d
67 }
68 }
69 }
70 }
71 )*};
72}
73
74div_ceil_impl!(unsigned usize u8 u16 u32 u64 u128);
75div_ceil_impl!(signed isize i8 i16 i32 i64 i128);
76
77c0nst::c0nst! {
78/// Performs division, rounding the quotient towards negative infinity.
79pub c0nst trait DivFloor: Sized {
80 /// The quotient type (`Self` for the primitive impls).
81 type Output;
82 /// Calculates the quotient of `self` and `rhs`, rounding the result
83 /// towards negative infinity. For unsigned types this is the normal
84 /// integer division.
85 ///
86 /// # Panics
87 ///
88 /// Panics if `rhs` is zero, or — with overflow checks enabled — on
89 /// `MIN / -1` for signed types.
90 ///
91 /// ```
92 /// use const_num_traits::DivFloor;
93 ///
94 /// assert_eq!(DivFloor::div_floor(7u8, 2), 3);
95 /// assert_eq!(DivFloor::div_floor(7i8, -2), -4);
96 /// assert_eq!(DivFloor::div_floor(-7i8, 2), -4);
97 /// ```
98 fn div_floor(self, rhs: Self) -> Self::Output;
99}
100}
101
102macro_rules! div_floor_impl {
103 // unsigned div_floor is plain division (and still unstable in std)
104 (unsigned $($t:ty)*) => {$(
105 c0nst::c0nst! {
106 c0nst impl DivFloor for $t {
107 type Output = $t;
108 #[inline]
109 fn div_floor(self, rhs: Self) -> Self {
110 self / rhs
111 }
112 }
113 }
114 )*};
115 (signed $($t:ty)*) => {$(
116 c0nst::c0nst! {
117 c0nst impl DivFloor for $t {
118 type Output = $t;
119 #[inline]
120 fn div_floor(self, rhs: Self) -> Self {
121 let d = self / rhs;
122 let r = self % rhs;
123 // all-ones (i.e. -1) iff the signs differ
124 let correction = (self ^ rhs) >> (<$t>::BITS - 1);
125 if r != 0 {
126 d + correction
127 } else {
128 d
129 }
130 }
131 }
132 }
133 )*};
134}
135
136div_floor_impl!(unsigned usize u8 u16 u32 u64 u128);
137div_floor_impl!(signed isize i8 i16 i32 i64 i128);
138
139c0nst::c0nst! {
140/// Performs division without remainder.
141pub c0nst trait DivExact: Sized {
142 /// The quotient type (`Self` for the primitive impls).
143 type Output;
144 /// Integer division without remainder. Computes `self / rhs`, returning
145 /// `None` if `self % rhs != 0`.
146 ///
147 /// # Panics
148 ///
149 /// Panics if `rhs` is zero, or — with overflow checks enabled — on
150 /// `MIN / -1` for signed types.
151 ///
152 /// ```
153 /// use const_num_traits::DivExact;
154 ///
155 /// assert_eq!(DivExact::div_exact(64u8, 2), Some(32));
156 /// assert_eq!(DivExact::div_exact(65u8, 2), None);
157 /// ```
158 fn div_exact(self, rhs: Self) -> Option<Self::Output>;
159
160 /// Checked integer division without remainder. Computes `self / rhs`,
161 /// returning `None` if `rhs == 0`, `self % rhs != 0`, or the division
162 /// overflowed (`MIN / -1` on a signed type).
163 fn checked_div_exact(self, rhs: Self) -> Option<Self::Output>;
164}
165}
166
167macro_rules! div_exact_impl {
168 ($($t:ty)*) => {$(
169 c0nst::c0nst! {
170 c0nst impl DivExact for $t {
171 type Output = $t;
172 #[inline]
173 fn div_exact(self, rhs: Self) -> Option<Self> {
174 if self % rhs != 0 {
175 None
176 } else {
177 Some(self / rhs)
178 }
179 }
180
181 #[inline]
182 fn checked_div_exact(self, rhs: Self) -> Option<Self> {
183 // checked_rem rejects rhs == 0 and the MIN % -1 overflow,
184 // checked_div rejects MIN / -1; together they cover all the
185 // None cases without panicking.
186 match <$t>::checked_rem(self, rhs) {
187 None => None,
188 Some(r) => {
189 if r != 0 {
190 None
191 } else {
192 <$t>::checked_div(self, rhs)
193 }
194 }
195 }
196 }
197 }
198 }
199 )*};
200}
201
202div_exact_impl!(usize u8 u16 u32 u64 u128);
203div_exact_impl!(isize i8 i16 i32 i64 i128);
204
205c0nst::c0nst! {
206/// Checks divisibility, mirroring `is_multiple_of` on the unsigned primitives.
207pub c0nst trait MultipleOf: Sized {
208 /// Returns `true` if `self` is an integer multiple of `rhs`, and `false`
209 /// otherwise.
210 ///
211 /// This function is equivalent to `self % rhs == 0`, except that it
212 /// will not panic for `rhs == 0`: instead, `0.is_multiple_of(0) == true`
213 /// and `x.is_multiple_of(0) == false` for any nonzero `x`.
214 ///
215 /// Like std, this is only provided for unsigned types.
216 ///
217 /// ```
218 /// use const_num_traits::MultipleOf;
219 ///
220 /// assert!(MultipleOf::is_multiple_of(6u8, 2));
221 /// assert!(!MultipleOf::is_multiple_of(5u8, 2));
222 /// assert!(MultipleOf::is_multiple_of(0u8, 0));
223 /// assert!(!MultipleOf::is_multiple_of(5u8, 0));
224 /// ```
225 fn is_multiple_of(self, rhs: Self) -> bool;
226}
227}
228
229macro_rules! multiple_of_impl {
230 ($($t:ty)*) => {$(
231 c0nst::c0nst! {
232 c0nst impl MultipleOf for $t {
233 #[inline]
234 fn is_multiple_of(self, rhs: Self) -> bool {
235 match rhs {
236 0 => self == 0,
237 _ => self % rhs == 0,
238 }
239 }
240 }
241 }
242 )*};
243}
244
245multiple_of_impl!(usize u8 u16 u32 u64 u128);
246
247c0nst::c0nst! {
248/// Rounds up to the nearest multiple of a value.
249pub c0nst trait NextMultipleOf: Sized {
250 /// The result type (`Self` for the primitive impls).
251 type Output;
252 /// Calculates the smallest value greater than or equal to `self` that is
253 /// a multiple of `rhs` (for negative `rhs` on signed types: the closest
254 /// to negative infinity).
255 ///
256 /// # Panics
257 ///
258 /// Panics if `rhs` is zero, or — with overflow checks enabled — if the
259 /// result overflows.
260 ///
261 /// ```
262 /// use const_num_traits::NextMultipleOf;
263 ///
264 /// assert_eq!(NextMultipleOf::next_multiple_of(16u8, 8), 16);
265 /// assert_eq!(NextMultipleOf::next_multiple_of(23u8, 8), 24);
266 /// assert_eq!(NextMultipleOf::next_multiple_of(-16i8, -5), -20);
267 /// ```
268 fn next_multiple_of(self, rhs: Self) -> Self::Output;
269
270 /// Calculates the smallest value greater than or equal to `self` that is
271 /// a multiple of `rhs`, returning `None` if `rhs` is zero or the
272 /// operation would result in overflow.
273 fn checked_next_multiple_of(self, rhs: Self) -> Option<Self::Output>;
274}
275}
276
277macro_rules! next_multiple_of_impl {
278 (unsigned $($t:ty)*) => {$(
279 c0nst::c0nst! {
280 c0nst impl NextMultipleOf for $t {
281 type Output = $t;
282 #[inline]
283 fn next_multiple_of(self, rhs: Self) -> Self {
284 <$t>::next_multiple_of(self, rhs)
285 }
286
287 #[inline]
288 fn checked_next_multiple_of(self, rhs: Self) -> Option<Self> {
289 <$t>::checked_next_multiple_of(self, rhs)
290 }
291 }
292 }
293 )*};
294 // signed next_multiple_of is still unstable in std; same algorithm
295 (signed $($t:ty)*) => {$(
296 c0nst::c0nst! {
297 c0nst impl NextMultipleOf for $t {
298 type Output = $t;
299 #[inline]
300 fn next_multiple_of(self, rhs: Self) -> Self {
301 // rhs == -1 would otherwise fail computing `MIN % -1`
302 if rhs == -1 {
303 return self;
304 }
305 let r = self % rhs;
306 let m = if (r > 0 && rhs < 0) || (r < 0 && rhs > 0) {
307 r + rhs
308 } else {
309 r
310 };
311 if m == 0 {
312 self
313 } else {
314 self + (rhs - m)
315 }
316 }
317
318 #[inline]
319 fn checked_next_multiple_of(self, rhs: Self) -> Option<Self> {
320 if rhs == -1 {
321 return Some(self);
322 }
323 let r = match <$t>::checked_rem(self, rhs) {
324 Some(r) => r,
325 None => return None,
326 };
327 let m = if (r > 0 && rhs < 0) || (r < 0 && rhs > 0) {
328 // r + rhs cannot overflow: opposite signs
329 r + rhs
330 } else {
331 r
332 };
333 if m == 0 {
334 Some(self)
335 } else {
336 // rhs - m cannot overflow: m has the same sign as rhs
337 <$t>::checked_add(self, rhs - m)
338 }
339 }
340 }
341 }
342 )*};
343}
344
345next_multiple_of_impl!(unsigned usize u8 u16 u32 u64 u128);
346next_multiple_of_impl!(signed isize i8 i16 i32 i64 i128);
347
348c0nst::c0nst! {
349/// Computes the midpoint (average) of two values without overflowing.
350pub c0nst trait Midpoint: Sized {
351 /// Calculates the midpoint `(self + rhs) / 2` as if it were performed in
352 /// a sufficiently-large type, so it never overflows. The result is
353 /// rounded towards zero.
354 ///
355 /// ```
356 /// use const_num_traits::Midpoint;
357 ///
358 /// assert_eq!(Midpoint::midpoint(u8::MAX, u8::MAX), u8::MAX);
359 /// assert_eq!(Midpoint::midpoint(0u8, 7), 3);
360 /// assert_eq!(Midpoint::midpoint(-7i8, 0), -3);
361 /// ```
362 type Output;
363 fn midpoint(self, rhs: Self) -> Self::Output;
364}
365}
366
367macro_rules! midpoint_impl {
368 (unsigned $($t:ty)*) => {$(
369 c0nst::c0nst! {
370 c0nst impl Midpoint for $t {
371 type Output = $t;
372 #[inline]
373 fn midpoint(self, rhs: Self) -> Self {
374 <$t>::midpoint(self, rhs)
375 }
376 }
377 }
378 )*};
379 // signed midpoint is stable since 1.87, newer than the MSRV; same
380 // branchless Hacker's Delight algorithm as core
381 (signed $($t:ty)*) => {$(
382 c0nst::c0nst! {
383 c0nst impl Midpoint for $t {
384 type Output = $t;
385 #[inline]
386 fn midpoint(self, rhs: Self) -> Self {
387 let t = ((self ^ rhs) >> 1) + (self & rhs);
388 // The floor average of two integers whose sum is an odd
389 // negative number is one below their truncated average;
390 // bump it back towards zero.
391 t + (if t < 0 { 1 } else { 0 } & (self ^ rhs))
392 }
393 }
394 }
395 )*};
396}
397
398midpoint_impl!(unsigned usize u8 u16 u32 u64 u128);
399midpoint_impl!(signed isize i8 i16 i32 i64 i128);
400
401#[cfg(test)]
402mod tests {
403 use super::*;
404
405 #[test]
406 fn div_ceil_floor() {
407 // cross-check the signed hand-rolled bodies against all small values
408 for a in -50i32..=50 {
409 for b in -50i32..=50 {
410 if b == 0 {
411 continue;
412 }
413 let ceil = DivCeil::div_ceil(a, b);
414 let floor = DivFloor::div_floor(a, b);
415 let exact = (a as f64) / (b as f64);
416 assert_eq!(ceil, exact.ceil() as i32, "{a} div_ceil {b}");
417 assert_eq!(floor, exact.floor() as i32, "{a} div_floor {b}");
418 }
419 }
420 assert_eq!(DivCeil::div_ceil(7u8, 2), 4);
421 assert_eq!(DivFloor::div_floor(7u8, 2), 3);
422 }
423
424 #[test]
425 fn div_exact() {
426 assert_eq!(DivExact::div_exact(64u8, 4), Some(16));
427 assert_eq!(DivExact::div_exact(66u8, 4), None);
428 assert_eq!(DivExact::checked_div_exact(64u8, 0), None);
429 assert_eq!(DivExact::checked_div_exact(i8::MIN, -1), None);
430 assert_eq!(DivExact::checked_div_exact(-64i8, -4), Some(16));
431 assert_eq!(DivExact::checked_div_exact(-65i8, -4), None);
432 }
433
434 #[test]
435 fn multiples() {
436 assert!(MultipleOf::is_multiple_of(12u32, 4));
437 assert!(!MultipleOf::is_multiple_of(12u32, 5));
438 assert!(MultipleOf::is_multiple_of(0u32, 0));
439 assert!(!MultipleOf::is_multiple_of(12u32, 0));
440
441 assert_eq!(NextMultipleOf::next_multiple_of(23u8, 8), 24);
442 assert_eq!(NextMultipleOf::checked_next_multiple_of(250u8, 8), None);
443 assert_eq!(NextMultipleOf::checked_next_multiple_of(23u8, 0), None);
444 // match std's signed semantics
445 assert_eq!(NextMultipleOf::next_multiple_of(16i8, 8), 16);
446 assert_eq!(NextMultipleOf::next_multiple_of(23i8, 8), 24);
447 assert_eq!(NextMultipleOf::next_multiple_of(16i8, -8), 16);
448 assert_eq!(NextMultipleOf::next_multiple_of(23i8, -8), 16);
449 assert_eq!(NextMultipleOf::next_multiple_of(-16i8, 8), -16);
450 assert_eq!(NextMultipleOf::next_multiple_of(-23i8, 8), -16);
451 assert_eq!(NextMultipleOf::next_multiple_of(-16i8, -8), -16);
452 assert_eq!(NextMultipleOf::next_multiple_of(-23i8, -8), -24);
453 assert_eq!(NextMultipleOf::next_multiple_of(i8::MIN, -1), i8::MIN);
454 assert_eq!(NextMultipleOf::checked_next_multiple_of(i8::MAX, 2), None);
455 }
456
457 #[test]
458 fn midpoint() {
459 assert_eq!(Midpoint::midpoint(u8::MAX, u8::MAX), u8::MAX);
460 assert_eq!(Midpoint::midpoint(0u8, 1), 0);
461 // signed: rounded towards zero, matching std's documented examples
462 assert_eq!(Midpoint::midpoint(-1i8, 2), 0);
463 assert_eq!(Midpoint::midpoint(-7i8, 0), -3);
464 assert_eq!(Midpoint::midpoint(0i8, 7), 3);
465 assert_eq!(Midpoint::midpoint(i8::MIN, i8::MAX), 0);
466 assert_eq!(Midpoint::midpoint(i8::MIN, i8::MIN), i8::MIN);
467 }
468}