malachite_base/num/arithmetic/
round_to_multiple.rs

1// Copyright © 2025 Mikhail Hogrefe
2//
3// This file is part of Malachite.
4//
5// Malachite is free software: you can redistribute it and/or modify it under the terms of the GNU
6// Lesser General Public License (LGPL) as published by the Free Software Foundation; either version
7// 3 of the License, or (at your option) any later version. See <https://www.gnu.org/licenses/>.
8
9use crate::num::arithmetic::traits::{RoundToMultiple, RoundToMultipleAssign, UnsignedAbs};
10use crate::num::basic::signeds::PrimitiveSigned;
11use crate::num::basic::unsigneds::PrimitiveUnsigned;
12use crate::num::conversion::traits::ExactFrom;
13use crate::rounding_modes::RoundingMode::{self, *};
14use core::cmp::Ordering::{self, *};
15
16fn round_to_multiple_unsigned<T: PrimitiveUnsigned>(
17    x: T,
18    other: T,
19    rm: RoundingMode,
20) -> (T, Ordering) {
21    match (x, other) {
22        (x, y) if x == y => (x, Equal),
23        (x, y) if y == T::ZERO => match rm {
24            Down | Floor | Nearest => (T::ZERO, Less),
25            _ => panic!("Cannot round {x} to zero using RoundingMode {rm}"),
26        },
27        (x, y) => {
28            let r = x % y;
29            if r == T::ZERO {
30                (x, Equal)
31            } else {
32                let floor = x - r;
33                match rm {
34                    Down | Floor => (floor, Less),
35                    Up | Ceiling => (floor.checked_add(y).unwrap(), Greater),
36                    Nearest => {
37                        match r.cmp(&(y >> 1)) {
38                            Less => (floor, Less),
39                            Greater => (floor.checked_add(y).unwrap(), Greater),
40                            Equal => {
41                                if y.odd() {
42                                    (floor, Less)
43                                } else {
44                                    // The even multiple of y will have more trailing zeros.
45                                    let (ceiling, overflow) = floor.overflowing_add(y);
46                                    if floor.trailing_zeros() > ceiling.trailing_zeros() {
47                                        (floor, Less)
48                                    } else if overflow {
49                                        panic!("Cannot round {x} to {y} using RoundingMode {rm}");
50                                    } else {
51                                        (ceiling, Greater)
52                                    }
53                                }
54                            }
55                        }
56                    }
57                    Exact => {
58                        panic!("Cannot round {x} to {y} using RoundingMode {rm}")
59                    }
60                }
61            }
62        }
63    }
64}
65
66macro_rules! impl_round_to_multiple_unsigned {
67    ($t:ident) => {
68        impl RoundToMultiple<$t> for $t {
69            type Output = $t;
70
71            /// Rounds a number to a multiple of another number, according to a specified rounding
72            /// mode. An [`Ordering`] is also returned, indicating whether the returned value is
73            /// less than, equal to, or greater than the original value.
74            ///
75            /// The only rounding modes that are guaranteed to return without a panic are `Down` and
76            /// `Floor`.
77            ///
78            /// Let $q = \frac{x}{y}$:
79            ///
80            /// $f(x, y, \mathrm{Down}) = f(x, y, \mathrm{Floor}) = y \lfloor q \rfloor.$
81            ///
82            /// $f(x, y, \mathrm{Up}) = f(x, y, \mathrm{Ceiling}) = y \lceil q \rceil.$
83            ///
84            /// $$
85            /// f(x, y, \mathrm{Nearest}) = \begin{cases}
86            ///     y \lfloor q \rfloor & \text{if} \\quad
87            ///         q - \lfloor q \rfloor < \frac{1}{2} \\\\
88            ///     y \lceil q \rceil & \text{if} \\quad q - \lfloor q \rfloor > \frac{1}{2} \\\\
89            ///     y \lfloor q \rfloor &
90            ///     \text{if} \\quad q - \lfloor q \rfloor =
91            ///         \frac{1}{2} \\ \text{and} \\ \lfloor q \rfloor
92            ///     \\ \text{is even} \\\\
93            ///     y \lceil q \rceil &
94            ///     \text{if} \\quad q - \lfloor q \rfloor = \frac{1}{2}
95            ///         \\ \text{and} \\ \lfloor q \rfloor \\ \text{is odd.}
96            /// \end{cases}
97            /// $$
98            ///
99            /// $f(x, y, \mathrm{Exact}) = x$, but panics if $q \notin \N$.
100            ///
101            /// The following two expressions are equivalent:
102            /// - `x.round_to_multiple(other, Exact)`
103            /// - `{ assert!(x.divisible_by(other)); x }`
104            ///
105            /// but the latter should be used as it is clearer and more efficient.
106            ///
107            /// # Worst-case complexity
108            /// Constant time and additional memory.
109            ///
110            /// # Panics
111            /// - If `rm` is `Exact`, but `self` is not a multiple of `other`.
112            /// - If the multiple is outside the representable range.
113            /// - If `self` is nonzero, `other` is zero, and `rm` is trying to round away from zero.
114            ///
115            /// # Examples
116            /// See [here](super::round_to_multiple#round_to_multiple).
117            #[inline]
118            fn round_to_multiple(self, other: $t, rm: RoundingMode) -> ($t, Ordering) {
119                round_to_multiple_unsigned(self, other, rm)
120            }
121        }
122
123        impl RoundToMultipleAssign<$t> for $t {
124            /// Rounds a number to a multiple of another number in place, according to a specified
125            /// rounding mode. An [`Ordering`] is returned, indicating whether the returned value is
126            /// less than, equal to, or greater than the original value.
127            ///
128            /// The only rounding modes that are guaranteed to return without a panic are `Down` and
129            /// `Floor`.
130            ///
131            /// See the [`RoundToMultiple`] documentation for details.
132            ///
133            /// The following two expressions are equivalent:
134            /// - `x.round_to_multiple_assign(other, Exact);`
135            /// - `assert!(x.divisible_by(other));`
136            ///
137            /// but the latter should be used as it is clearer and more efficient.
138            ///
139            /// # Worst-case complexity
140            /// Constant time and additional memory.
141            ///
142            /// # Panics
143            /// - If `rm` is `Exact`, but `self` is not a multiple of `other`.
144            /// - If the multiple is outside the representable range.
145            /// - If `self` is nonzero, `other` is zero, and `rm` is trying to round away from zero.
146            ///
147            /// # Examples
148            /// See [here](super::round_to_multiple#round_to_multiple_assign).
149            #[inline]
150            fn round_to_multiple_assign(&mut self, other: $t, rm: RoundingMode) -> Ordering {
151                let o;
152                (*self, o) = self.round_to_multiple(other, rm);
153                o
154            }
155        }
156    };
157}
158apply_to_unsigneds!(impl_round_to_multiple_unsigned);
159
160fn round_to_multiple_signed<
161    U: PrimitiveUnsigned,
162    S: ExactFrom<U> + PrimitiveSigned + UnsignedAbs<Output = U>,
163>(
164    x: S,
165    other: S,
166    rm: RoundingMode,
167) -> (S, Ordering) {
168    if x >= S::ZERO {
169        let (m, o) = x.unsigned_abs().round_to_multiple(other.unsigned_abs(), rm);
170        (S::exact_from(m), o)
171    } else {
172        let (abs_result, o) = x
173            .unsigned_abs()
174            .round_to_multiple(other.unsigned_abs(), -rm);
175        (
176            if abs_result == S::MIN.unsigned_abs() {
177                S::MIN
178            } else {
179                S::exact_from(abs_result).checked_neg().unwrap()
180            },
181            o.reverse(),
182        )
183    }
184}
185
186macro_rules! impl_round_to_multiple_signed {
187    ($t:ident) => {
188        impl RoundToMultiple<$t> for $t {
189            type Output = $t;
190
191            /// Rounds a number to a multiple of another number, according to a specified rounding
192            /// mode. An [`Ordering`] is also returned, indicating whether the returned value is
193            /// less than, equal to, or greater than the original value.
194            ///
195            /// The only rounding mode that is guaranteed to return without a panic is `Down`.
196            ///
197            /// Let $q = \frac{x}{|y|}$:
198            ///
199            /// $f(x, y, \mathrm{Down}) =  \operatorname{sgn}(q) |y| \lfloor |q| \rfloor.$
200            ///
201            /// $f(x, y, \mathrm{Up}) = \operatorname{sgn}(q) |y| \lceil |q| \rceil.$
202            ///
203            /// $f(x, y, \mathrm{Floor}) = |y| \lfloor q \rfloor.$
204            ///
205            /// $f(x, y, \mathrm{Ceiling}) = |y| \lceil q \rceil.$
206            ///
207            /// $$
208            /// f(x, y, \mathrm{Nearest}) = \begin{cases}
209            ///     y \lfloor q \rfloor & \text{if} \\quad
210            ///         q - \lfloor q \rfloor < \frac{1}{2} \\\\
211            ///     y \lceil q \rceil & \text{if} \\quad q - \lfloor q \rfloor > \frac{1}{2} \\\\
212            ///     y \lfloor q \rfloor &
213            ///     \text{if} \\quad q - \lfloor q \rfloor =
214            ///         \frac{1}{2} \\ \text{and} \\ \lfloor q \rfloor
215            ///     \\ \text{is even} \\\\
216            ///     y \lceil q \rceil &
217            ///     \text{if} \\quad q - \lfloor q \rfloor = \frac{1}{2}
218            ///         \\ \text{and} \\ \lfloor q \rfloor \\ \text{is odd.}
219            /// \end{cases}
220            /// $$
221            ///
222            /// $f(x, y, \mathrm{Exact}) = q$, but panics if $q \notin \Z$.
223            ///
224            /// The following two expressions are equivalent:
225            /// - `x.round_to_multiple(other, Exact)`
226            /// - `{ assert!(x.divisible_by(other)); x }`
227            ///
228            /// but the latter should be used as it is clearer and more efficient.
229            ///
230            /// # Worst-case complexity
231            /// Constant time and additional memory.
232            ///
233            /// # Panics
234            /// - If `rm` is `Exact`, but `self` is not a multiple of `other`.
235            /// - If the multiple is outside the representable range.
236            /// - If `self` is nonzero, `other` is zero, and `rm` is trying to round away from zero.
237            ///
238            /// # Examples
239            /// See [here](super::round_to_multiple#round_to_multiple).
240            #[inline]
241            fn round_to_multiple(self, other: $t, rm: RoundingMode) -> ($t, Ordering) {
242                round_to_multiple_signed(self, other, rm)
243            }
244        }
245
246        impl RoundToMultipleAssign<$t> for $t {
247            /// Rounds a number to a multiple of another number in place, according to a specified
248            /// rounding mode. An [`Ordering`] is returned, indicating whether the returned value is
249            /// less than, equal to, or greater than the original value.
250            ///
251            /// The only rounding mode that is guaranteed to return without a panic is `Down`.
252            ///
253            /// See the [`RoundToMultiple`] documentation for details.
254            ///
255            /// The following two expressions are equivalent:
256            /// - `x.round_to_multiple_assign(other, Exact);`
257            /// - `assert!(x.divisible_by(other));`
258            ///
259            /// but the latter should be used as it is clearer and more efficient.
260            ///
261            /// # Worst-case complexity
262            /// Constant time and additional memory.
263            ///
264            /// # Panics
265            /// - If `rm` is `Exact`, but `self` is not a multiple of `other`.
266            /// - If the multiple is outside the representable range.
267            /// - If `self` is nonzero, `other` is zero, and `rm` is trying to round away from zero.
268            ///
269            /// # Examples
270            /// See [here](super::round_to_multiple#round_to_multiple_assign).
271            #[inline]
272            fn round_to_multiple_assign(&mut self, other: $t, rm: RoundingMode) -> Ordering {
273                let o;
274                (*self, o) = self.round_to_multiple(other, rm);
275                o
276            }
277        }
278    };
279}
280apply_to_signeds!(impl_round_to_multiple_signed);