Skip to main content

dashu_float/
round.rs

1//! Traits and implementations for rounding during operations.
2
3use core::cmp::Ordering;
4use core::ops::{Add, AddAssign};
5use dashu_base::{AbsOrd, Approximation, BitTest, EstimatedLog2, Sign, Signed, UnsignedAbs};
6use dashu_int::{IBig, UBig, Word};
7
8use crate::FBig;
9
10/// Built-in rounding modes of the floating numbers.
11///
12/// # Rounding Error
13///
14/// For different rounding modes, the [Rounding] error
15/// in the output of operations tells the error range, as described in
16/// the table below.
17///
18/// | Mode     | Rounding | Error (truth - estimation) Range |
19/// |----------|----------|----------------------------------|
20/// | Zero     | NoOp     | `(-1 ulp 0)` or `(0, 1 ulp)`*    |
21/// | Away     | AddOne   | `(-1 ulp, 0)`                    |
22/// | Away     | SubOne   | `(0, 1 ulp)`                     |
23/// | Down     | SubOne   | `(0, 1 ulp)`                     |
24/// | Up       | AddOne   | `(-1 ulp, 0)`                    |
25/// | HalfAway | AddOne   | `[-1/2 ulp, 0)`                  |
26/// | HalfAway | NoOp     | `(-1/2 ulp, 1/2 ulp)`            |
27/// | HalfAway | SubOne   | `(0, 1/2 ulp]`                   |
28/// | HalfEven | AddOne   | `[-1/2 ulp, 0)`                  |
29/// | HalfEven | NoOp     | `[-1/2 ulp, 1/2 ulp]`            |
30/// | HalfEven | SubOne   | `(0, 1/2 ulp]`                   |
31///
32/// *: Dependends on the sign of the result
33///
34pub mod mode {
35    /// Round toward 0 (default mode for binary float)
36    #[derive(Clone, Copy)]
37    pub struct Zero;
38
39    /// Round away from 0
40    #[derive(Clone, Copy)]
41    pub struct Away;
42
43    /// Round toward +∞
44    #[derive(Clone, Copy)]
45    pub struct Up;
46
47    /// Round toward -∞
48    #[derive(Clone, Copy)]
49    pub struct Down;
50
51    /// Round to the nearest value, ties are rounded to an even value. (default mode for decimal float)
52    #[derive(Clone, Copy)]
53    pub struct HalfEven;
54
55    /// Round to the nearest value, ties away from zero
56    #[derive(Clone, Copy)]
57    pub struct HalfAway;
58}
59
60/// The adjustment of a rounding operation
61///
62/// This enum represents the adjustment applied to the truncated significand
63/// (`NoOp = 0`, `AddOne = +1`, `SubOne = -1`), **not** the direction of the error
64/// relative to the true value.
65///
66/// See [the `mode` module][mode] for the corresponding error bounds.
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub enum Rounding {
69    /// No adjustment
70    NoOp,
71
72    /// Add one
73    AddOne,
74
75    /// Subtract one
76    SubOne,
77}
78
79/// A type representing float operation result
80///
81/// If the operation result is inexact, the adjustment from the final rounding
82/// will be returned along with the result.
83pub type Rounded<T> = Approximation<T, Rounding>;
84
85/// A trait describing the rounding strategy
86pub trait Round: Copy {
87    /// The rounding operation that rounds to an opposite direction
88    type Reverse: Round;
89
90    /// Calculate the rounding of the number (integer + rem), assuming rem != 0 and |rem| < 1.
91    /// `low_half_test` should tell |rem|.cmp(0.5)
92    fn round_low_part<F: FnOnce() -> Ordering>(
93        integer: &IBig,
94        low_sign: Sign,
95        low_half_test: F,
96    ) -> Rounding;
97
98    /// Calculate the rounding of the number (integer + fract / X^precision),
99    /// assuming |fract| / X^precision < 1. Return the adjustment.
100    #[inline]
101    fn round_fract<const B: Word>(integer: &IBig, fract: IBig, precision: usize) -> Rounding {
102        // this assertion is costly, so only check in debug mode
103        debug_assert!(fract.clone().unsigned_abs() < UBig::from_word(B).pow(precision));
104
105        if fract.is_zero() {
106            return Rounding::NoOp;
107        }
108        let (fsign, fmag) = fract.into_parts();
109
110        let test = || {
111            // first use the estimated log2 to do coarse comparison, then do the exact comparison
112            let (lb, ub) = fmag.log2_bounds();
113            let (b_lb, b_ub) = B.log2_bounds();
114
115            // 0.999 and 1.001 are used here to prevent the influence of the precision loss of the multiplcations
116            if lb + 0.999 > b_ub * precision as f32 {
117                Ordering::Greater
118            } else if ub + 1.001 < b_lb * precision as f32 {
119                Ordering::Less
120            } else {
121                (fmag << 1).cmp(&UBig::from_word(B).pow(precision))
122            }
123        };
124        Self::round_low_part::<_>(integer, fsign, test)
125    }
126
127    /// Calculate the rounding of the number (integer + numerator / denominator),
128    /// assuming |numerator / denominator| < 1. Return the adjustment.
129    #[inline]
130    fn round_ratio(integer: &IBig, num: IBig, den: &IBig) -> Rounding {
131        assert!(!den.is_zero() && num.abs_cmp(den).is_le());
132
133        if num.is_zero() {
134            return Rounding::NoOp;
135        }
136        let (nsign, nmag) = num.into_parts();
137        Self::round_low_part::<_>(integer, nsign * den.sign(), || {
138            if den.is_positive() {
139                IBig::from(nmag << 1).cmp(den)
140            } else {
141                den.cmp(&-(nmag << 1))
142            }
143        })
144    }
145}
146
147/// A trait providing the function to retrieve the error bounds of the rounded value.
148pub trait ErrorBounds: Round {
149    /// Given a floating point number `f`, the output (L, R, incl_L, incl_R) represents the relative
150    /// error range with left bound `f - L` and right bound `f + R`. The two boolean values `incl_L`
151    /// and `incl_R` represents whether the bounds `f - L` and `f + R` are inclusive respectively.
152    ///
153    /// When the input number has unlimited precision, the output must be (ZERO, ZERO, true, true).
154    fn error_bounds<const B: Word>(f: &FBig<Self, B>)
155        -> (FBig<Self, B>, FBig<Self, B>, bool, bool);
156}
157
158impl Round for mode::Zero {
159    type Reverse = mode::Away;
160
161    #[inline]
162    fn round_low_part<F: FnOnce() -> Ordering>(
163        integer: &IBig,
164        low_sign: Sign,
165        _low_half_test: F,
166    ) -> Rounding {
167        if integer.is_zero() {
168            return Rounding::NoOp;
169        }
170        match (integer.sign(), low_sign) {
171            (Sign::Positive, Sign::Positive) | (Sign::Negative, Sign::Negative) => Rounding::NoOp,
172            (Sign::Positive, Sign::Negative) => Rounding::SubOne,
173            (Sign::Negative, Sign::Positive) => Rounding::AddOne,
174        }
175    }
176}
177
178impl ErrorBounds for mode::Zero {
179    #[inline]
180    fn error_bounds<const B: Word>(
181        f: &FBig<Self, B>,
182    ) -> (FBig<Self, B>, FBig<Self, B>, bool, bool) {
183        if f.precision() == 0 {
184            (FBig::ZERO, FBig::ZERO, true, true)
185        } else if f.repr().is_zero() {
186            (f.ulp(), f.ulp(), false, false)
187        } else {
188            match f.repr().sign() {
189                Sign::Positive => (FBig::ZERO, f.ulp(), true, false),
190                Sign::Negative => (f.ulp(), FBig::ZERO, false, true),
191            }
192        }
193    }
194}
195
196impl Round for mode::Away {
197    type Reverse = mode::Zero;
198
199    #[inline]
200    fn round_low_part<F: FnOnce() -> Ordering>(
201        integer: &IBig,
202        low_sign: Sign,
203        _low_half_test: F,
204    ) -> Rounding {
205        if integer.is_zero() {
206            match low_sign {
207                Sign::Positive => Rounding::AddOne,
208                Sign::Negative => Rounding::SubOne,
209            }
210        } else {
211            match (integer.sign(), low_sign) {
212                (Sign::Positive, Sign::Positive) => Rounding::AddOne,
213                (Sign::Negative, Sign::Negative) => Rounding::SubOne,
214                (Sign::Positive, Sign::Negative) | (Sign::Negative, Sign::Positive) => {
215                    Rounding::NoOp
216                }
217            }
218        }
219    }
220}
221
222impl ErrorBounds for mode::Away {
223    #[inline]
224    fn error_bounds<const B: Word>(
225        f: &FBig<Self, B>,
226    ) -> (FBig<Self, B>, FBig<Self, B>, bool, bool) {
227        if f.precision() == 0 && f.repr().is_zero() {
228            (FBig::ZERO, FBig::ZERO, true, true)
229        } else {
230            match f.repr().sign() {
231                Sign::Positive => (f.ulp(), FBig::ZERO, false, true),
232                Sign::Negative => (FBig::ZERO, f.ulp(), true, false),
233            }
234        }
235    }
236}
237
238impl Round for mode::Down {
239    type Reverse = mode::Up;
240
241    #[inline]
242    fn round_low_part<F: FnOnce() -> Ordering>(
243        _integer: &IBig,
244        low_sign: Sign,
245        _low_half_test: F,
246    ) -> Rounding {
247        // -1 if fract < 0, otherwise 0
248        if low_sign == Sign::Negative {
249            Rounding::SubOne
250        } else {
251            Rounding::NoOp
252        }
253    }
254}
255
256impl ErrorBounds for mode::Down {
257    #[inline]
258    fn error_bounds<const B: Word>(
259        f: &FBig<Self, B>,
260    ) -> (FBig<Self, B>, FBig<Self, B>, bool, bool) {
261        (FBig::ZERO, f.ulp(), true, false)
262    }
263}
264
265impl Round for mode::Up {
266    type Reverse = mode::Down;
267
268    #[inline]
269    fn round_low_part<F: FnOnce() -> Ordering>(
270        _integer: &IBig,
271        low_sign: Sign,
272        _low_half_test: F,
273    ) -> Rounding {
274        // +1 if fract > 0, otherwise 0
275        if low_sign == Sign::Positive {
276            Rounding::AddOne
277        } else {
278            Rounding::NoOp
279        }
280    }
281}
282
283impl ErrorBounds for mode::Up {
284    #[inline]
285    fn error_bounds<const B: Word>(
286        f: &FBig<Self, B>,
287    ) -> (FBig<Self, B>, FBig<Self, B>, bool, bool) {
288        (f.ulp(), FBig::ZERO, false, true)
289    }
290}
291
292impl Round for mode::HalfAway {
293    type Reverse = Self;
294
295    #[inline]
296    fn round_low_part<F: FnOnce() -> Ordering>(
297        integer: &IBig,
298        low_sign: Sign,
299        low_half_test: F,
300    ) -> Rounding {
301        match low_half_test() {
302            // |rem| < 1/2
303            Ordering::Less => Rounding::NoOp,
304            // |rem| = 1/2
305            Ordering::Equal => {
306                // +1 if integer and rem >= 0, -1 if integer and rem <= 0
307                if integer >= &IBig::ZERO && low_sign == Sign::Positive {
308                    Rounding::AddOne
309                } else if integer <= &IBig::ZERO && low_sign == Sign::Negative {
310                    Rounding::SubOne
311                } else {
312                    Rounding::NoOp
313                }
314            }
315            // |rem| > 1/2
316            Ordering::Greater => {
317                // +1 if rem > 0, -1 if rem < 0
318                match low_sign {
319                    Sign::Positive => Rounding::AddOne,
320                    Sign::Negative => Rounding::SubOne,
321                }
322            }
323        }
324    }
325}
326
327impl ErrorBounds for mode::HalfAway {
328    #[inline]
329    fn error_bounds<const B: Word>(
330        f: &FBig<Self, B>,
331    ) -> (FBig<Self, B>, FBig<Self, B>, bool, bool) {
332        if f.precision() == 0 {
333            return (FBig::ZERO, FBig::ZERO, true, true);
334        }
335
336        let mut half_ulp = f.ulp();
337        half_ulp.repr.exponent -= 1;
338        half_ulp.repr.significand = UBig::from_word((B + 1) / 2).into(); // ceil division
339
340        let (incl_l, incl_r) = if f.repr.is_zero() {
341            (false, false)
342        } else if f.repr.sign() == Sign::Negative {
343            (false, true)
344        } else {
345            (true, false)
346        };
347        (half_ulp.clone(), half_ulp, incl_l, incl_r)
348    }
349}
350
351impl Round for mode::HalfEven {
352    type Reverse = Self;
353
354    #[inline]
355    fn round_low_part<F: FnOnce() -> Ordering>(
356        integer: &IBig,
357        low_sign: Sign,
358        low_half_test: F,
359    ) -> Rounding {
360        match low_half_test() {
361            // |rem| < 1/2
362            Ordering::Less => Rounding::NoOp,
363            // |rem| = 1/2
364            Ordering::Equal => {
365                // if integer is odd, +1 if rem > 0, -1 if rem < 0
366                if integer.bit(0) {
367                    match low_sign {
368                        Sign::Positive => Rounding::AddOne,
369                        Sign::Negative => Rounding::SubOne,
370                    }
371                } else {
372                    Rounding::NoOp
373                }
374            }
375            // |rem| > 1/2
376            Ordering::Greater => {
377                // +1 if rem > 0, -1 if rem < 0
378                match low_sign {
379                    Sign::Positive => Rounding::AddOne,
380                    Sign::Negative => Rounding::SubOne,
381                }
382            }
383        }
384    }
385}
386
387impl ErrorBounds for mode::HalfEven {
388    #[inline]
389    fn error_bounds<const B: Word>(
390        f: &FBig<Self, B>,
391    ) -> (FBig<Self, B>, FBig<Self, B>, bool, bool) {
392        if f.precision() == 0 {
393            return (FBig::ZERO, FBig::ZERO, true, true);
394        }
395
396        let mut half_ulp = f.ulp();
397        half_ulp.repr.exponent -= 1;
398        half_ulp.repr.significand = UBig::from_word((B + 1) / 2).into(); // ceil division
399
400        let incl = f.repr.significand.bit(0);
401        (half_ulp.clone(), half_ulp, incl, incl)
402    }
403}
404
405impl Add<Rounding> for IBig {
406    type Output = IBig;
407    #[inline]
408    fn add(self, rhs: Rounding) -> Self::Output {
409        match rhs {
410            Rounding::NoOp => self,
411            Rounding::AddOne => self + IBig::ONE,
412            Rounding::SubOne => self - IBig::ONE,
413        }
414    }
415}
416
417impl Add<Rounding> for &IBig {
418    type Output = IBig;
419    #[inline]
420    fn add(self, rhs: Rounding) -> Self::Output {
421        match rhs {
422            Rounding::NoOp => self.clone(),
423            Rounding::AddOne => self + IBig::ONE,
424            Rounding::SubOne => self - IBig::ONE,
425        }
426    }
427}
428
429impl AddAssign<Rounding> for IBig {
430    #[inline]
431    fn add_assign(&mut self, rhs: Rounding) {
432        match rhs {
433            Rounding::NoOp => {}
434            Rounding::AddOne => *self += IBig::ONE,
435            Rounding::SubOne => *self -= IBig::ONE,
436        }
437    }
438}
439
440#[cfg(test)]
441mod tests {
442    use super::*;
443    use super::{mode::*, Rounding::*};
444
445    #[test]
446    fn test_from_fract() {
447        #[rustfmt::skip]
448        fn test_all_rounding<const B: Word, const D: usize>(
449            input: &(i32, i32, Rounding, Rounding, Rounding, Rounding, Rounding, Rounding),
450        ) {
451            let (value, fract, rnd_zero, rnd_away, rnd_up, rnd_down, rnd_halfeven, rnd_halfaway) = *input;
452            let (value, fract) = (IBig::from(value), IBig::from(fract));
453            assert_eq!(Zero::round_fract::<B>(&value, fract.clone(), D), rnd_zero);
454            assert_eq!(Away::round_fract::<B>(&value, fract.clone(), D), rnd_away);
455            assert_eq!(Up::round_fract::<B>(&value, fract.clone(), D), rnd_up);
456            assert_eq!(Down::round_fract::<B>(&value, fract.clone(), D), rnd_down);
457            assert_eq!(HalfEven::round_fract::<B>(&value, fract.clone(), D), rnd_halfeven);
458            assert_eq!(HalfAway::round_fract::<B>(&value, fract, D), rnd_halfaway);
459        }
460
461        // cases for radix = 2, 2 digit fraction
462        #[rustfmt::skip]
463        let binary_cases = [
464            // (integer value, fraction part, roundings...)
465            // Mode: Zero  , Away  , Up    , Down  , HEven,  HAway
466            ( 0,  3, NoOp  , AddOne, AddOne, NoOp  , AddOne, AddOne),
467            ( 0,  2, NoOp  , AddOne, AddOne, NoOp  , NoOp  , AddOne),
468            ( 0,  1, NoOp  , AddOne, AddOne, NoOp  , NoOp  , NoOp  ),
469            ( 0,  0, NoOp  , NoOp  , NoOp  , NoOp  , NoOp  , NoOp  ),
470            ( 0, -1, NoOp  , SubOne, NoOp  , SubOne, NoOp  , NoOp  ),
471            ( 0, -2, NoOp  , SubOne, NoOp  , SubOne, NoOp  , SubOne),
472            ( 0, -3, NoOp  , SubOne, NoOp  , SubOne, SubOne, SubOne),
473            ( 1,  3, NoOp  , AddOne, AddOne, NoOp  , AddOne, AddOne),
474            ( 1,  2, NoOp  , AddOne, AddOne, NoOp  , AddOne, AddOne),
475            ( 1,  1, NoOp  , AddOne, AddOne, NoOp  , NoOp  , NoOp  ),
476            ( 1,  0, NoOp  , NoOp  , NoOp  , NoOp  , NoOp  , NoOp  ),
477            ( 1, -1, SubOne, NoOp  , NoOp  , SubOne, NoOp  , NoOp  ),
478            ( 1, -2, SubOne, NoOp  , NoOp  , SubOne, SubOne, NoOp  ),
479            ( 1, -3, SubOne, NoOp  , NoOp  , SubOne, SubOne, SubOne),
480            (-1,  3, AddOne, NoOp  , AddOne, NoOp  , AddOne, AddOne),
481            (-1,  2, AddOne, NoOp  , AddOne, NoOp  , AddOne, NoOp  ),
482            (-1,  1, AddOne, NoOp  , AddOne, NoOp  , NoOp  , NoOp  ),
483            (-1,  0, NoOp  , NoOp  , NoOp  , NoOp  , NoOp  , NoOp  ),
484            (-1, -1, NoOp  , SubOne, NoOp  , SubOne, NoOp  , NoOp  ),
485            (-1, -2, NoOp  , SubOne, NoOp  , SubOne, SubOne, SubOne),
486            (-1, -3, NoOp  , SubOne, NoOp  , SubOne, SubOne, SubOne),
487        ];
488        binary_cases.iter().for_each(test_all_rounding::<2, 2>);
489
490        // cases for radix = 3, 1 digit fraction
491        #[rustfmt::skip]
492        let tenary_cases = [
493            // (integer value, fraction part, roundings...)
494            // Mode: Zero,   Away  , Up    , Down  , HEven , HAway
495            ( 0,  2, NoOp,   AddOne, AddOne, NoOp  , AddOne, AddOne),
496            ( 0,  1, NoOp,   AddOne, AddOne, NoOp  , NoOp  , NoOp  ),
497            ( 0,  0, NoOp,   NoOp  , NoOp  , NoOp  , NoOp  , NoOp  ),
498            ( 0, -1, NoOp,   SubOne, NoOp  , SubOne, NoOp  , NoOp  ),
499            ( 0, -2, NoOp,   SubOne, NoOp  , SubOne, SubOne, SubOne),
500            ( 1,  2, NoOp,   AddOne, AddOne, NoOp  , AddOne, AddOne),
501            ( 1,  1, NoOp,   AddOne, AddOne, NoOp  , NoOp  , NoOp  ),
502            ( 1,  0, NoOp,   NoOp  , NoOp  , NoOp  , NoOp  , NoOp  ),
503            ( 1, -1, SubOne, NoOp  , NoOp  , SubOne, NoOp  , NoOp  ),
504            ( 1, -2, SubOne, NoOp  , NoOp  , SubOne, SubOne, SubOne),
505            (-1,  2, AddOne, NoOp  , AddOne, NoOp  , AddOne, AddOne),
506            (-1,  1, AddOne, NoOp  , AddOne, NoOp  , NoOp  , NoOp  ),
507            (-1,  0, NoOp,   NoOp  , NoOp  , NoOp  , NoOp  , NoOp  ),
508            (-1, -1, NoOp,   SubOne, NoOp  , SubOne, NoOp  , NoOp  ),
509            (-1, -2, NoOp,   SubOne, NoOp  , SubOne, SubOne, SubOne),
510        ];
511        tenary_cases.iter().for_each(test_all_rounding::<3, 1>);
512
513        // cases for radix = 10, 1 digit fraction
514        #[rustfmt::skip]
515        let decimal_cases = [
516            // (integer value, fraction part, roundings...)
517            // Mode: Zero  , Away  , Up    , Down  , HEven , HAway
518            ( 0,  7, NoOp  , AddOne, AddOne, NoOp  , AddOne, AddOne),
519            ( 0,  5, NoOp  , AddOne, AddOne, NoOp  , NoOp  , AddOne),
520            ( 0,  2, NoOp  , AddOne, AddOne, NoOp  , NoOp  , NoOp  ),
521            ( 0,  0, NoOp  , NoOp  , NoOp  , NoOp  , NoOp  , NoOp  ),
522            ( 0, -2, NoOp  , SubOne, NoOp  , SubOne, NoOp  , NoOp  ),
523            ( 0, -5, NoOp  , SubOne, NoOp  , SubOne, NoOp  , SubOne),
524            ( 0, -7, NoOp  , SubOne, NoOp  , SubOne, SubOne, SubOne),
525            ( 1,  7, NoOp  , AddOne, AddOne, NoOp  , AddOne, AddOne),
526            ( 1,  5, NoOp  , AddOne, AddOne, NoOp  , AddOne, AddOne),
527            ( 1,  2, NoOp  , AddOne, AddOne, NoOp  , NoOp  , NoOp  ),
528            ( 1,  0, NoOp  , NoOp  , NoOp  , NoOp  , NoOp  , NoOp  ),
529            ( 1, -2, SubOne, NoOp  , NoOp  , SubOne, NoOp  , NoOp  ),
530            ( 1, -5, SubOne, NoOp  , NoOp  , SubOne, SubOne, NoOp  ),
531            ( 1, -7, SubOne, NoOp  , NoOp  , SubOne, SubOne, SubOne),
532            (-1,  7, AddOne, NoOp  , AddOne, NoOp  , AddOne, AddOne),
533            (-1,  5, AddOne, NoOp  , AddOne, NoOp  , AddOne, NoOp  ),
534            (-1,  2, AddOne, NoOp  , AddOne, NoOp  , NoOp  , NoOp  ),
535            (-1,  0, NoOp  , NoOp  , NoOp  , NoOp  , NoOp  , NoOp  ),
536            (-1, -2, NoOp  , SubOne, NoOp  , SubOne, NoOp  , NoOp  ),
537            (-1, -5, NoOp  , SubOne, NoOp  , SubOne, SubOne, SubOne),
538            (-1, -7, NoOp  , SubOne, NoOp  , SubOne, SubOne, SubOne),
539        ];
540        decimal_cases.iter().for_each(test_all_rounding::<10, 1>);
541    }
542
543    #[test]
544    fn test_from_ratio() {
545        #[rustfmt::skip]
546        fn test_all_rounding(
547            input: &(i32, i32, i32, Rounding, Rounding, Rounding, Rounding, Rounding, Rounding),
548        ) {
549            let (value, num, den, rnd_zero, rnd_away, rnd_up, rnd_down, rnd_halfeven, rnd_halfaway) = *input;
550            let (value, num, den) = (IBig::from(value), IBig::from(num), IBig::from(den));
551            assert_eq!(Zero::round_ratio(&value, num.clone(), &den), rnd_zero);
552            assert_eq!(Away::round_ratio(&value, num.clone(), &den), rnd_away);
553            assert_eq!(Up::round_ratio(&value, num.clone(), &den), rnd_up);
554            assert_eq!(Down::round_ratio(&value, num.clone(), &den), rnd_down);
555            assert_eq!(HalfEven::round_ratio(&value, num.clone(), &den), rnd_halfeven);
556            assert_eq!(HalfAway::round_ratio(&value, num, &den), rnd_halfaway);
557        }
558
559        // cases for radix = 2, 2 digit fraction
560        #[rustfmt::skip]
561        let test_cases = [
562            // (integer value, mumerator, denominator, roundings...)
563            // Mode:     Zero  , Away  , Up    , Down  , HEven , HAway
564            ( 0,  0,  2, NoOp  , NoOp  , NoOp  , NoOp  , NoOp  , NoOp  ),
565            ( 0,  1,  2, NoOp  , AddOne, AddOne, NoOp  , NoOp  , AddOne),
566            ( 0, -1,  2, NoOp  , SubOne, NoOp  , SubOne, NoOp  , SubOne),
567            ( 0,  0, -2, NoOp  , NoOp  , NoOp  , NoOp  , NoOp  , NoOp  ),
568            ( 0,  1, -2, NoOp  , SubOne, NoOp  , SubOne, NoOp  , SubOne),
569            ( 0, -1, -2, NoOp  , AddOne, AddOne, NoOp  , NoOp  , AddOne),
570            ( 1,  0,  2, NoOp  , NoOp  , NoOp  , NoOp  , NoOp  , NoOp  ),
571            ( 1,  1,  2, NoOp  , AddOne, AddOne, NoOp  , AddOne, AddOne),
572            ( 1, -1,  2, SubOne, NoOp  , NoOp  , SubOne, SubOne, NoOp  ),
573            ( 1,  0, -2, NoOp  , NoOp  , NoOp  , NoOp  , NoOp  , NoOp  ),
574            ( 1,  1, -2, SubOne, NoOp  , NoOp  , SubOne, SubOne, NoOp  ),
575            ( 1, -1, -2, NoOp  , AddOne, AddOne, NoOp  , AddOne, AddOne),
576            (-1,  0,  2, NoOp  , NoOp  , NoOp  , NoOp  , NoOp  , NoOp  ),
577            (-1,  1,  2, AddOne, NoOp  , AddOne, NoOp  , AddOne, NoOp  ),
578            (-1, -1,  2, NoOp  , SubOne, NoOp  , SubOne, SubOne, SubOne),
579            (-1,  0, -2, NoOp  , NoOp  , NoOp  , NoOp  , NoOp  , NoOp  ),
580            (-1,  1, -2, NoOp  , SubOne, NoOp  , SubOne, SubOne, SubOne),
581            (-1, -1, -2, AddOne, NoOp  , AddOne, NoOp  , AddOne, NoOp  ),
582
583            ( 0, -2,  3, NoOp  , SubOne, NoOp  , SubOne, SubOne, SubOne),
584            ( 0, -1,  3, NoOp  , SubOne, NoOp  , SubOne, NoOp  , NoOp  ),
585            ( 0,  0,  3, NoOp  , NoOp  , NoOp  , NoOp  , NoOp  , NoOp  ),
586            ( 0,  1,  3, NoOp  , AddOne, AddOne, NoOp  , NoOp  , NoOp  ),
587            ( 0,  2,  3, NoOp  , AddOne, AddOne, NoOp  , AddOne, AddOne),
588            ( 0, -2, -3, NoOp  , AddOne, AddOne, NoOp  , AddOne, AddOne),
589            ( 0, -1, -3, NoOp  , AddOne, AddOne, NoOp  , NoOp  , NoOp  ),
590            ( 0,  0, -3, NoOp  , NoOp  , NoOp  , NoOp  , NoOp  , NoOp  ),
591            ( 0,  1, -3, NoOp  , SubOne, NoOp  , SubOne, NoOp  , NoOp  ),
592            ( 0,  2, -3, NoOp  , SubOne, NoOp  , SubOne, SubOne, SubOne),
593            ( 1, -2,  3, SubOne, NoOp  , NoOp  , SubOne, SubOne, SubOne),
594            ( 1, -1,  3, SubOne, NoOp  , NoOp  , SubOne, NoOp  , NoOp  ),
595            ( 1,  0,  3, NoOp  , NoOp  , NoOp  , NoOp  , NoOp  , NoOp  ),
596            ( 1,  1,  3, NoOp  , AddOne, AddOne, NoOp  , NoOp  , NoOp  ),
597            ( 1,  2,  3, NoOp  , AddOne, AddOne, NoOp  , AddOne, AddOne),
598            ( 1, -2, -3, NoOp  , AddOne, AddOne, NoOp  , AddOne, AddOne),
599            ( 1, -1, -3, NoOp  , AddOne, AddOne, NoOp  , NoOp  , NoOp  ),
600            ( 1,  0, -3, NoOp  , NoOp  , NoOp  , NoOp  , NoOp  , NoOp  ),
601            ( 1,  1, -3, SubOne, NoOp  , NoOp  , SubOne, NoOp  , NoOp  ),
602            ( 1,  2, -3, SubOne, NoOp  , NoOp  , SubOne, SubOne, SubOne),
603            (-1, -2,  3, NoOp  , SubOne, NoOp  , SubOne, SubOne, SubOne),
604            (-1, -1,  3, NoOp  , SubOne, NoOp  , SubOne, NoOp  , NoOp  ),
605            (-1,  0,  3, NoOp  , NoOp  , NoOp  , NoOp  , NoOp  , NoOp  ),
606            (-1,  1,  3, AddOne, NoOp  , AddOne, NoOp  , NoOp  , NoOp  ),
607            (-1,  2,  3, AddOne, NoOp  , AddOne, NoOp  , AddOne, AddOne),
608            (-1, -2, -3, AddOne, NoOp  , AddOne, NoOp  , AddOne, AddOne),
609            (-1, -1, -3, AddOne, NoOp  , AddOne, NoOp  , NoOp  , NoOp  ),
610            (-1,  0, -3, NoOp  , NoOp  , NoOp  , NoOp  , NoOp  , NoOp  ),
611            (-1,  1, -3, NoOp  , SubOne, NoOp  , SubOne, NoOp  , NoOp  ),
612            (-1,  2, -3, NoOp  , SubOne, NoOp  , SubOne, SubOne, SubOne),
613        ];
614        test_cases.iter().for_each(test_all_rounding);
615    }
616}