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