1use crate::{
2 util::{
3 rangeint::{RFrom, RInto},
4 t::{NoUnits, NoUnits128, C, C128},
5 },
6 Unit,
7};
8
9#[non_exhaustive]
44#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
45pub enum RoundMode {
46 Ceil,
52 Floor,
58 Expand,
61 Trunc,
67 HalfCeil,
70 HalfFloor,
73 HalfExpand,
82 HalfTrunc,
85 HalfEven,
91}
92
93impl RoundMode {
94 pub(crate) fn round_by_unit_in_nanoseconds(
98 self,
99 quantity: impl RInto<NoUnits128>,
100 unit: Unit,
101 increment: impl RInto<NoUnits128>,
102 ) -> NoUnits128 {
103 let quantity = quantity.rinto();
104 let increment = unit.nanoseconds() * increment.rinto();
105 let rounded = self.round(quantity, increment);
106 rounded
107 }
108
109 fn round(
110 self,
111 quantity: impl RInto<NoUnits128>,
112 increment: impl RInto<NoUnits128>,
113 ) -> NoUnits128 {
114 fn inner(
116 mode: RoundMode,
117 quantity: NoUnits128,
118 increment: NoUnits128,
119 ) -> NoUnits128 {
120 let mut quotient = quantity.div_ceil(increment);
121 let remainder = quantity.rem_ceil(increment);
122 if remainder == 0 {
123 return quantity;
124 }
125 let sign = if remainder < 0 { C128(-1) } else { C128(1) };
126 let tiebreaker = (remainder * C128(2)).abs();
127 let tie = tiebreaker == increment;
128 let expand_is_nearer = tiebreaker > increment;
129 match mode {
131 RoundMode::Ceil => {
132 if sign > 0 {
133 quotient += sign;
134 }
135 }
136 RoundMode::Floor => {
137 if sign < 0 {
138 quotient += sign;
139 }
140 }
141 RoundMode::Expand => {
142 quotient += sign;
143 }
144 RoundMode::Trunc => {}
145 RoundMode::HalfCeil => {
146 if expand_is_nearer || (tie && sign > 0) {
147 quotient += sign;
148 }
149 }
150 RoundMode::HalfFloor => {
151 if expand_is_nearer || (tie && sign < 0) {
152 quotient += sign;
153 }
154 }
155 RoundMode::HalfExpand => {
156 if expand_is_nearer || tie {
157 quotient += sign;
158 }
159 }
160 RoundMode::HalfTrunc => {
161 if expand_is_nearer {
162 quotient += sign;
163 }
164 }
165 RoundMode::HalfEven => {
166 if expand_is_nearer || (tie && quotient % C(2) == 1) {
167 quotient += sign;
168 }
169 }
170 }
171 quotient.saturating_mul(increment)
177 }
178 inner(self, quantity.rinto(), increment.rinto())
179 }
180
181 pub(crate) fn round_float(
182 self,
183 quantity: f64,
184 increment: NoUnits128,
185 ) -> NoUnits128 {
186 #[cfg(not(feature = "std"))]
187 use crate::util::libm::Float;
188
189 let quotient = quantity / (increment.get() as f64);
190 let rounded = match self {
191 RoundMode::Ceil => quotient.ceil(),
192 RoundMode::Floor => quotient.floor(),
193 RoundMode::Expand => {
194 if quotient < 0.0 {
195 quotient.floor()
196 } else {
197 quotient.ceil()
198 }
199 }
200 RoundMode::Trunc => quotient.trunc(),
201 RoundMode::HalfCeil => {
202 if quotient % 1.0 == 0.5 {
203 quotient.ceil()
204 } else {
205 quotient.round()
206 }
207 }
208 RoundMode::HalfFloor => {
209 if quotient % 1.0 == 0.5 {
210 quotient.floor()
211 } else {
212 quotient.round()
213 }
214 }
215 RoundMode::HalfExpand => {
216 quotient.signum() * quotient.abs().round()
217 }
218 RoundMode::HalfTrunc => {
219 if quotient % 1.0 == 0.5 {
220 quotient.trunc()
221 } else {
222 quotient.round()
223 }
224 }
225 RoundMode::HalfEven => {
226 if quotient % 1.0 == 0.5 {
227 quotient.trunc() + (quotient % 2.0)
228 } else {
229 quotient.round()
230 }
231 }
232 };
233 let rounded = NoUnits::new(rounded as i64).unwrap();
234 NoUnits128::rfrom(rounded.saturating_mul(increment))
235 }
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241
242 #[test]
244 fn round_to_increment_half_expand_ad_hoc() {
245 let round = |quantity: i64, increment: i64| -> i64 {
246 let quantity = NoUnits::new(quantity).unwrap();
247 let increment = NoUnits::new(increment).unwrap();
248 i64::from(RoundMode::HalfExpand.round(quantity, increment))
249 };
250 assert_eq!(26, round(20, 13));
251
252 assert_eq!(0, round(29, 60));
253 assert_eq!(60, round(30, 60));
254 assert_eq!(60, round(31, 60));
255
256 assert_eq!(0, round(3, 7));
257 assert_eq!(7, round(4, 7));
258 }
259
260 #[test]
267 fn round_to_increment_temporal_table_ceil() {
268 let round = |quantity: i64, increment: i64| -> i64 {
269 let quantity = NoUnits::new(quantity).unwrap();
270 let increment = NoUnits::new(increment).unwrap();
271 RoundMode::Ceil.round(quantity, increment).into()
272 };
273 assert_eq!(-10, round(-15, 10));
274 assert_eq!(0, round(-5, 10));
275 assert_eq!(10, round(4, 10));
276 assert_eq!(10, round(5, 10));
277 assert_eq!(10, round(6, 10));
278 assert_eq!(20, round(15, 10));
279 }
280
281 #[test]
282 fn round_to_increment_temporal_table_floor() {
283 let round = |quantity: i64, increment: i64| -> i64 {
284 let quantity = NoUnits::new(quantity).unwrap();
285 let increment = NoUnits::new(increment).unwrap();
286 RoundMode::Floor.round(quantity, increment).into()
287 };
288 assert_eq!(-20, round(-15, 10));
289 assert_eq!(-10, round(-5, 10));
290 assert_eq!(0, round(4, 10));
291 assert_eq!(0, round(5, 10));
292 assert_eq!(0, round(6, 10));
293 assert_eq!(10, round(15, 10));
294 }
295
296 #[test]
297 fn round_to_increment_temporal_table_expand() {
298 let round = |quantity: i64, increment: i64| -> i64 {
299 let quantity = NoUnits::new(quantity).unwrap();
300 let increment = NoUnits::new(increment).unwrap();
301 RoundMode::Expand.round(quantity, increment).into()
302 };
303 assert_eq!(-20, round(-15, 10));
304 assert_eq!(-10, round(-5, 10));
305 assert_eq!(10, round(4, 10));
306 assert_eq!(10, round(5, 10));
307 assert_eq!(10, round(6, 10));
308 assert_eq!(20, round(15, 10));
309 }
310
311 #[test]
312 fn round_to_increment_temporal_table_trunc() {
313 let round = |quantity: i64, increment: i64| -> i64 {
314 let quantity = NoUnits::new(quantity).unwrap();
315 let increment = NoUnits::new(increment).unwrap();
316 RoundMode::Trunc.round(quantity, increment).into()
317 };
318 assert_eq!(-10, round(-15, 10));
319 assert_eq!(0, round(-5, 10));
320 assert_eq!(0, round(4, 10));
321 assert_eq!(0, round(5, 10));
322 assert_eq!(0, round(6, 10));
323 assert_eq!(10, round(15, 10));
324 }
325
326 #[test]
327 fn round_to_increment_temporal_table_half_ceil() {
328 let round = |quantity: i64, increment: i64| -> i64 {
329 let quantity = NoUnits::new(quantity).unwrap();
330 let increment = NoUnits::new(increment).unwrap();
331 RoundMode::HalfCeil.round(quantity, increment).into()
332 };
333 assert_eq!(-10, round(-15, 10));
334 assert_eq!(0, round(-5, 10));
335 assert_eq!(0, round(4, 10));
336 assert_eq!(10, round(5, 10));
337 assert_eq!(10, round(6, 10));
338 assert_eq!(20, round(15, 10));
339 }
340
341 #[test]
342 fn round_to_increment_temporal_table_half_floor() {
343 let round = |quantity: i64, increment: i64| -> i64 {
344 let quantity = NoUnits::new(quantity).unwrap();
345 let increment = NoUnits::new(increment).unwrap();
346 RoundMode::HalfFloor.round(quantity, increment).into()
347 };
348 assert_eq!(-20, round(-15, 10));
349 assert_eq!(-10, round(-5, 10));
350 assert_eq!(0, round(4, 10));
351 assert_eq!(0, round(5, 10));
352 assert_eq!(10, round(6, 10));
353 assert_eq!(10, round(15, 10));
354 }
355
356 #[test]
357 fn round_to_increment_temporal_table_half_expand() {
358 let round = |quantity: i64, increment: i64| -> i64 {
359 let quantity = NoUnits::new(quantity).unwrap();
360 let increment = NoUnits::new(increment).unwrap();
361 RoundMode::HalfExpand.round(quantity, increment).into()
362 };
363 assert_eq!(-20, round(-15, 10));
364 assert_eq!(-10, round(-5, 10));
365 assert_eq!(0, round(4, 10));
366 assert_eq!(10, round(5, 10));
367 assert_eq!(10, round(6, 10));
368 assert_eq!(20, round(15, 10));
369 }
370
371 #[test]
372 fn round_to_increment_temporal_table_half_trunc() {
373 let round = |quantity: i64, increment: i64| -> i64 {
374 let quantity = NoUnits::new(quantity).unwrap();
375 let increment = NoUnits::new(increment).unwrap();
376 RoundMode::HalfTrunc.round(quantity, increment).into()
377 };
378 assert_eq!(-10, round(-15, 10));
379 assert_eq!(0, round(-5, 10));
380 assert_eq!(0, round(4, 10));
381 assert_eq!(0, round(5, 10));
382 assert_eq!(10, round(6, 10));
383 assert_eq!(10, round(15, 10));
384 }
385
386 #[test]
387 fn round_to_increment_temporal_table_half_even() {
388 let round = |quantity: i64, increment: i64| -> i64 {
389 let quantity = NoUnits::new(quantity).unwrap();
390 let increment = NoUnits::new(increment).unwrap();
391 RoundMode::HalfEven.round(quantity, increment).into()
392 };
393 assert_eq!(-20, round(-15, 10));
394 assert_eq!(0, round(-5, 10));
395 assert_eq!(0, round(4, 10));
396 assert_eq!(0, round(5, 10));
397 assert_eq!(10, round(6, 10));
398 assert_eq!(20, round(15, 10));
399 }
400}