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);