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    /// Whether this mode rounds toward negative infinity (IEEE roundTowardNegative).
91    /// Used to determine the sign of a zero produced by exact cancellation: per IEEE 754,
92    /// `x + (-x)` yields `-0` only under roundTowardNegative, `+0` otherwise.
93    const IS_ROUND_TOWARD_NEGATIVE: bool;
94
95    /// Calculate the rounding of the number (integer + rem), assuming rem != 0 and |rem| < 1.
96    /// `low_half_test` should tell |rem|.cmp(0.5)
97    fn round_low_part<F: FnOnce() -> Ordering>(
98        integer: &IBig,
99        low_sign: Sign,
100        low_half_test: F,
101    ) -> Rounding;
102
103    /// Calculate the rounding of the number (integer + fract / X^precision),
104    /// assuming |fract| / X^precision < 1. Return the adjustment.
105    #[inline]
106    fn round_fract<const B: Word>(integer: &IBig, fract: IBig, precision: usize) -> Rounding {
107        // this assertion is costly, so only check in debug mode
108        debug_assert!(fract.clone().unsigned_abs() < UBig::from_word(B).pow(precision));
109
110        if fract.is_zero() {
111            return Rounding::NoOp;
112        }
113        let (fsign, fmag) = fract.into_parts();
114
115        let test = || {
116            // first use the estimated log2 to do coarse comparison, then do the exact comparison
117            let (lb, ub) = fmag.log2_bounds();
118            let (b_lb, b_ub) = B.log2_bounds();
119
120            // 0.999 and 1.001 are used here to prevent the influence of the precision loss of the multiplcations
121            if lb + 0.999 > b_ub * precision as f32 {
122                Ordering::Greater
123            } else if ub + 1.001 < b_lb * precision as f32 {
124                Ordering::Less
125            } else {
126                (fmag << 1).cmp(&UBig::from_word(B).pow(precision))
127            }
128        };
129        Self::round_low_part::<_>(integer, fsign, test)
130    }
131
132    /// Calculate the rounding of the number (integer + numerator / denominator),
133    /// assuming |numerator / denominator| < 1. Return the adjustment.
134    #[inline]
135    fn round_ratio(integer: &IBig, num: IBig, den: &IBig) -> Rounding {
136        assert!(!den.is_zero() && num.abs_cmp(den).is_le());
137
138        if num.is_zero() {
139            return Rounding::NoOp;
140        }
141        let (nsign, nmag) = num.into_parts();
142        Self::round_low_part::<_>(integer, nsign * den.sign(), || {
143            if den.is_positive() {
144                IBig::from(nmag << 1).cmp(den)
145            } else {
146                den.cmp(&-(nmag << 1))
147            }
148        })
149    }
150}
151
152/// A trait providing the function to retrieve the error bounds of the rounded value.
153pub trait ErrorBounds: Round {
154    /// Given a floating point number `f`, the output (L, R, incl_L, incl_R) represents the relative
155    /// error range with left bound `f - L` and right bound `f + R`. The two boolean values `incl_L`
156    /// and `incl_R` represents whether the bounds `f - L` and `f + R` are inclusive respectively.
157    ///
158    /// When the input number has unlimited precision, the output must be (ZERO, ZERO, true, true).
159    fn error_bounds<const B: Word>(f: &FBig<Self, B>)
160        -> (FBig<Self, B>, FBig<Self, B>, bool, bool);
161}
162
163impl Round for mode::Zero {
164    type Reverse = mode::Away;
165    const IS_ROUND_TOWARD_NEGATIVE: bool = false;
166
167    #[inline]
168    fn round_low_part<F: FnOnce() -> Ordering>(
169        integer: &IBig,
170        low_sign: Sign,
171        _low_half_test: F,
172    ) -> Rounding {
173        if integer.is_zero() {
174            return Rounding::NoOp;
175        }
176        match (integer.sign(), low_sign) {
177            (Sign::Positive, Sign::Positive) | (Sign::Negative, Sign::Negative) => Rounding::NoOp,
178            (Sign::Positive, Sign::Negative) => Rounding::SubOne,
179            (Sign::Negative, Sign::Positive) => Rounding::AddOne,
180        }
181    }
182}
183
184impl ErrorBounds for mode::Zero {
185    #[inline]
186    fn error_bounds<const B: Word>(
187        f: &FBig<Self, B>,
188    ) -> (FBig<Self, B>, FBig<Self, B>, bool, bool) {
189        if f.precision() == 0 {
190            (FBig::ZERO, FBig::ZERO, true, true)
191        } else if f.repr().is_pos_zero() {
192            // `+0` is the canonical zero: its error interval is the full symmetric "rounds to
193            // zero" range (−ulp, +ulp). `-0` deliberately does NOT match here — it carries a
194            // sign (a distinct rounding target, produced by roundTowardNegative cancellation,
195            // `sqrt(−0)`, `1/−inf`, …), so it falls through to the sign arm for its one-sided
196            // preimage. (`+0` and `-0` are the same value; this split is about which values
197            // round *to* each signed-zero target under this directed mode.)
198            (f.ulp(), f.ulp(), false, false)
199        } else {
200            match f.repr().sign() {
201                Sign::Positive => (FBig::ZERO, f.ulp(), true, false),
202                Sign::Negative => (f.ulp(), FBig::ZERO, false, true),
203            }
204        }
205    }
206}
207
208impl Round for mode::Away {
209    type Reverse = mode::Zero;
210    const IS_ROUND_TOWARD_NEGATIVE: bool = false;
211
212    #[inline]
213    fn round_low_part<F: FnOnce() -> Ordering>(
214        integer: &IBig,
215        low_sign: Sign,
216        _low_half_test: F,
217    ) -> Rounding {
218        if integer.is_zero() {
219            match low_sign {
220                Sign::Positive => Rounding::AddOne,
221                Sign::Negative => Rounding::SubOne,
222            }
223        } else {
224            match (integer.sign(), low_sign) {
225                (Sign::Positive, Sign::Positive) => Rounding::AddOne,
226                (Sign::Negative, Sign::Negative) => Rounding::SubOne,
227                (Sign::Positive, Sign::Negative) | (Sign::Negative, Sign::Positive) => {
228                    Rounding::NoOp
229                }
230            }
231        }
232    }
233}
234
235impl ErrorBounds for mode::Away {
236    #[inline]
237    fn error_bounds<const B: Word>(
238        f: &FBig<Self, B>,
239    ) -> (FBig<Self, B>, FBig<Self, B>, bool, bool) {
240        if f.precision() == 0 {
241            // Unlimited precision → exact value, so the error range is zero (ErrorBounds contract).
242            // This must be tested before the sign arms, which would otherwise return a nonzero ulp
243            // for `-0` (and any other precision-0 value).
244            (FBig::ZERO, FBig::ZERO, true, true)
245        } else {
246            match f.repr().sign() {
247                Sign::Positive => (f.ulp(), FBig::ZERO, false, true),
248                Sign::Negative => (FBig::ZERO, f.ulp(), true, false),
249            }
250        }
251    }
252}
253
254impl Round for mode::Down {
255    type Reverse = mode::Up;
256    const IS_ROUND_TOWARD_NEGATIVE: bool = true;
257
258    #[inline]
259    fn round_low_part<F: FnOnce() -> Ordering>(
260        _integer: &IBig,
261        low_sign: Sign,
262        _low_half_test: F,
263    ) -> Rounding {
264        // -1 if fract < 0, otherwise 0
265        if low_sign == Sign::Negative {
266            Rounding::SubOne
267        } else {
268            Rounding::NoOp
269        }
270    }
271}
272
273impl ErrorBounds for mode::Down {
274    #[inline]
275    fn error_bounds<const B: Word>(
276        f: &FBig<Self, B>,
277    ) -> (FBig<Self, B>, FBig<Self, B>, bool, bool) {
278        (FBig::ZERO, f.ulp(), true, false)
279    }
280}
281
282impl Round for mode::Up {
283    type Reverse = mode::Down;
284    const IS_ROUND_TOWARD_NEGATIVE: bool = false;
285
286    #[inline]
287    fn round_low_part<F: FnOnce() -> Ordering>(
288        _integer: &IBig,
289        low_sign: Sign,
290        _low_half_test: F,
291    ) -> Rounding {
292        // +1 if fract > 0, otherwise 0
293        if low_sign == Sign::Positive {
294            Rounding::AddOne
295        } else {
296            Rounding::NoOp
297        }
298    }
299}
300
301impl ErrorBounds for mode::Up {
302    #[inline]
303    fn error_bounds<const B: Word>(
304        f: &FBig<Self, B>,
305    ) -> (FBig<Self, B>, FBig<Self, B>, bool, bool) {
306        (f.ulp(), FBig::ZERO, false, true)
307    }
308}
309
310impl Round for mode::HalfAway {
311    type Reverse = Self;
312    const IS_ROUND_TOWARD_NEGATIVE: bool = false;
313
314    #[inline]
315    fn round_low_part<F: FnOnce() -> Ordering>(
316        integer: &IBig,
317        low_sign: Sign,
318        low_half_test: F,
319    ) -> Rounding {
320        match low_half_test() {
321            // |rem| < 1/2
322            Ordering::Less => Rounding::NoOp,
323            // |rem| = 1/2
324            Ordering::Equal => {
325                // +1 if integer and rem >= 0, -1 if integer and rem <= 0
326                if integer >= &IBig::ZERO && low_sign == Sign::Positive {
327                    Rounding::AddOne
328                } else if integer <= &IBig::ZERO && low_sign == Sign::Negative {
329                    Rounding::SubOne
330                } else {
331                    Rounding::NoOp
332                }
333            }
334            // |rem| > 1/2
335            Ordering::Greater => {
336                // +1 if rem > 0, -1 if rem < 0
337                match low_sign {
338                    Sign::Positive => Rounding::AddOne,
339                    Sign::Negative => Rounding::SubOne,
340                }
341            }
342        }
343    }
344}
345
346impl ErrorBounds for mode::HalfAway {
347    #[inline]
348    fn error_bounds<const B: Word>(
349        f: &FBig<Self, B>,
350    ) -> (FBig<Self, B>, FBig<Self, B>, bool, bool) {
351        if f.precision() == 0 {
352            return (FBig::ZERO, FBig::ZERO, true, true);
353        }
354
355        let mut half_ulp = f.ulp();
356        half_ulp.repr.exponent -= 1;
357        half_ulp.repr.significand = UBig::from_word((B + 1) / 2).into(); // ceil division
358
359        let (incl_l, incl_r) = if f.repr.is_pos_zero() {
360            // `+0` is the canonical zero with the symmetric half-ulp interval on both sides.
361            // `-0` intentionally falls through: it carries a sign (a distinct rounding target),
362            // so it takes the sign-specific one-sided interval from the arms below. See
363            // `mode::Zero::error_bounds` for the same `+0`/`-0` split.
364            (false, false)
365        } else if f.repr.sign() == Sign::Negative {
366            (false, true)
367        } else {
368            (true, false)
369        };
370        (half_ulp.clone(), half_ulp, incl_l, incl_r)
371    }
372}
373
374impl Round for mode::HalfEven {
375    type Reverse = Self;
376    const IS_ROUND_TOWARD_NEGATIVE: bool = false;
377
378    #[inline]
379    fn round_low_part<F: FnOnce() -> Ordering>(
380        integer: &IBig,
381        low_sign: Sign,
382        low_half_test: F,
383    ) -> Rounding {
384        match low_half_test() {
385            // |rem| < 1/2
386            Ordering::Less => Rounding::NoOp,
387            // |rem| = 1/2
388            Ordering::Equal => {
389                // if integer is odd, +1 if rem > 0, -1 if rem < 0
390                if integer.bit(0) {
391                    match low_sign {
392                        Sign::Positive => Rounding::AddOne,
393                        Sign::Negative => Rounding::SubOne,
394                    }
395                } else {
396                    Rounding::NoOp
397                }
398            }
399            // |rem| > 1/2
400            Ordering::Greater => {
401                // +1 if rem > 0, -1 if rem < 0
402                match low_sign {
403                    Sign::Positive => Rounding::AddOne,
404                    Sign::Negative => Rounding::SubOne,
405                }
406            }
407        }
408    }
409}
410
411impl ErrorBounds for mode::HalfEven {
412    #[inline]
413    fn error_bounds<const B: Word>(
414        f: &FBig<Self, B>,
415    ) -> (FBig<Self, B>, FBig<Self, B>, bool, bool) {
416        if f.precision() == 0 {
417            return (FBig::ZERO, FBig::ZERO, true, true);
418        }
419
420        let mut half_ulp = f.ulp();
421        half_ulp.repr.exponent -= 1;
422        half_ulp.repr.significand = UBig::from_word((B + 1) / 2).into(); // ceil division
423
424        // `-0` carries a sign (a distinct rounding target), so it takes the one-sided (Negative)
425        // interval, matching `mode::HalfAway::error_bounds`. `+0` (significand 0, even) and all
426        // nonzero values fall through to the round-to-even parity rule.
427        let (incl_l, incl_r) = if f.repr.is_neg_zero() {
428            (false, true)
429        } else {
430            let incl = f.repr.significand.bit(0);
431            (incl, incl)
432        };
433        (half_ulp.clone(), half_ulp, incl_l, incl_r)
434    }
435}
436
437impl Add<Rounding> for IBig {
438    type Output = IBig;
439    #[inline]
440    fn add(self, rhs: Rounding) -> Self::Output {
441        match rhs {
442            Rounding::NoOp => self,
443            Rounding::AddOne => self + IBig::ONE,
444            Rounding::SubOne => self - IBig::ONE,
445        }
446    }
447}
448
449impl Add<Rounding> for &IBig {
450    type Output = IBig;
451    #[inline]
452    fn add(self, rhs: Rounding) -> Self::Output {
453        match rhs {
454            Rounding::NoOp => self.clone(),
455            Rounding::AddOne => self + IBig::ONE,
456            Rounding::SubOne => self - IBig::ONE,
457        }
458    }
459}
460
461impl AddAssign<Rounding> for IBig {
462    #[inline]
463    fn add_assign(&mut self, rhs: Rounding) {
464        match rhs {
465            Rounding::NoOp => {}
466            Rounding::AddOne => *self += IBig::ONE,
467            Rounding::SubOne => *self -= IBig::ONE,
468        }
469    }
470}
471
472#[cfg(test)]
473mod tests {
474    use super::*;
475    use super::{mode::*, Rounding::*};
476
477    #[test]
478    fn test_from_fract() {
479        #[rustfmt::skip]
480        fn test_all_rounding<const B: Word, const D: usize>(
481            input: &(i32, i32, Rounding, Rounding, Rounding, Rounding, Rounding, Rounding),
482        ) {
483            let (value, fract, rnd_zero, rnd_away, rnd_up, rnd_down, rnd_halfeven, rnd_halfaway) = *input;
484            let (value, fract) = (IBig::from(value), IBig::from(fract));
485            assert_eq!(Zero::round_fract::<B>(&value, fract.clone(), D), rnd_zero);
486            assert_eq!(Away::round_fract::<B>(&value, fract.clone(), D), rnd_away);
487            assert_eq!(Up::round_fract::<B>(&value, fract.clone(), D), rnd_up);
488            assert_eq!(Down::round_fract::<B>(&value, fract.clone(), D), rnd_down);
489            assert_eq!(HalfEven::round_fract::<B>(&value, fract.clone(), D), rnd_halfeven);
490            assert_eq!(HalfAway::round_fract::<B>(&value, fract, D), rnd_halfaway);
491        }
492
493        // cases for radix = 2, 2 digit fraction
494        #[rustfmt::skip]
495        let binary_cases = [
496            // (integer value, fraction part, roundings...)
497            // Mode: Zero  , Away  , Up    , Down  , HEven,  HAway
498            ( 0,  3, NoOp  , AddOne, AddOne, NoOp  , AddOne, AddOne),
499            ( 0,  2, NoOp  , AddOne, AddOne, NoOp  , NoOp  , AddOne),
500            ( 0,  1, NoOp  , AddOne, AddOne, NoOp  , NoOp  , NoOp  ),
501            ( 0,  0, NoOp  , NoOp  , NoOp  , NoOp  , NoOp  , NoOp  ),
502            ( 0, -1, NoOp  , SubOne, NoOp  , SubOne, NoOp  , NoOp  ),
503            ( 0, -2, NoOp  , SubOne, NoOp  , SubOne, NoOp  , SubOne),
504            ( 0, -3, NoOp  , SubOne, NoOp  , SubOne, SubOne, SubOne),
505            ( 1,  3, NoOp  , AddOne, AddOne, NoOp  , AddOne, AddOne),
506            ( 1,  2, NoOp  , AddOne, AddOne, NoOp  , AddOne, AddOne),
507            ( 1,  1, NoOp  , AddOne, AddOne, NoOp  , NoOp  , NoOp  ),
508            ( 1,  0, NoOp  , NoOp  , NoOp  , NoOp  , NoOp  , NoOp  ),
509            ( 1, -1, SubOne, NoOp  , NoOp  , SubOne, NoOp  , NoOp  ),
510            ( 1, -2, SubOne, NoOp  , NoOp  , SubOne, SubOne, NoOp  ),
511            ( 1, -3, SubOne, NoOp  , NoOp  , SubOne, SubOne, SubOne),
512            (-1,  3, AddOne, NoOp  , AddOne, NoOp  , AddOne, AddOne),
513            (-1,  2, AddOne, NoOp  , AddOne, NoOp  , AddOne, NoOp  ),
514            (-1,  1, AddOne, NoOp  , AddOne, NoOp  , NoOp  , NoOp  ),
515            (-1,  0, NoOp  , NoOp  , NoOp  , NoOp  , NoOp  , NoOp  ),
516            (-1, -1, NoOp  , SubOne, NoOp  , SubOne, NoOp  , NoOp  ),
517            (-1, -2, NoOp  , SubOne, NoOp  , SubOne, SubOne, SubOne),
518            (-1, -3, NoOp  , SubOne, NoOp  , SubOne, SubOne, SubOne),
519        ];
520        binary_cases.iter().for_each(test_all_rounding::<2, 2>);
521
522        // cases for radix = 3, 1 digit fraction
523        #[rustfmt::skip]
524        let tenary_cases = [
525            // (integer value, fraction part, roundings...)
526            // Mode: Zero,   Away  , Up    , Down  , HEven , HAway
527            ( 0,  2, NoOp,   AddOne, AddOne, NoOp  , AddOne, AddOne),
528            ( 0,  1, NoOp,   AddOne, AddOne, NoOp  , NoOp  , NoOp  ),
529            ( 0,  0, NoOp,   NoOp  , NoOp  , NoOp  , NoOp  , NoOp  ),
530            ( 0, -1, NoOp,   SubOne, NoOp  , SubOne, NoOp  , NoOp  ),
531            ( 0, -2, NoOp,   SubOne, NoOp  , SubOne, SubOne, SubOne),
532            ( 1,  2, NoOp,   AddOne, AddOne, NoOp  , AddOne, AddOne),
533            ( 1,  1, NoOp,   AddOne, AddOne, NoOp  , NoOp  , NoOp  ),
534            ( 1,  0, NoOp,   NoOp  , NoOp  , NoOp  , NoOp  , NoOp  ),
535            ( 1, -1, SubOne, NoOp  , NoOp  , SubOne, NoOp  , NoOp  ),
536            ( 1, -2, SubOne, NoOp  , NoOp  , SubOne, SubOne, SubOne),
537            (-1,  2, AddOne, NoOp  , AddOne, NoOp  , AddOne, AddOne),
538            (-1,  1, AddOne, NoOp  , AddOne, NoOp  , NoOp  , NoOp  ),
539            (-1,  0, NoOp,   NoOp  , NoOp  , NoOp  , NoOp  , NoOp  ),
540            (-1, -1, NoOp,   SubOne, NoOp  , SubOne, NoOp  , NoOp  ),
541            (-1, -2, NoOp,   SubOne, NoOp  , SubOne, SubOne, SubOne),
542        ];
543        tenary_cases.iter().for_each(test_all_rounding::<3, 1>);
544
545        // cases for radix = 10, 1 digit fraction
546        #[rustfmt::skip]
547        let decimal_cases = [
548            // (integer value, fraction part, roundings...)
549            // Mode: Zero  , Away  , Up    , Down  , HEven , HAway
550            ( 0,  7, NoOp  , AddOne, AddOne, NoOp  , AddOne, AddOne),
551            ( 0,  5, NoOp  , AddOne, AddOne, NoOp  , NoOp  , AddOne),
552            ( 0,  2, NoOp  , AddOne, AddOne, NoOp  , NoOp  , NoOp  ),
553            ( 0,  0, NoOp  , NoOp  , NoOp  , NoOp  , NoOp  , NoOp  ),
554            ( 0, -2, NoOp  , SubOne, NoOp  , SubOne, NoOp  , NoOp  ),
555            ( 0, -5, NoOp  , SubOne, NoOp  , SubOne, NoOp  , SubOne),
556            ( 0, -7, NoOp  , SubOne, NoOp  , SubOne, SubOne, SubOne),
557            ( 1,  7, NoOp  , AddOne, AddOne, NoOp  , AddOne, AddOne),
558            ( 1,  5, NoOp  , AddOne, AddOne, NoOp  , AddOne, AddOne),
559            ( 1,  2, NoOp  , AddOne, AddOne, NoOp  , NoOp  , NoOp  ),
560            ( 1,  0, NoOp  , NoOp  , NoOp  , NoOp  , NoOp  , NoOp  ),
561            ( 1, -2, SubOne, NoOp  , NoOp  , SubOne, NoOp  , NoOp  ),
562            ( 1, -5, SubOne, NoOp  , NoOp  , SubOne, SubOne, NoOp  ),
563            ( 1, -7, SubOne, NoOp  , NoOp  , SubOne, SubOne, SubOne),
564            (-1,  7, AddOne, NoOp  , AddOne, NoOp  , AddOne, AddOne),
565            (-1,  5, AddOne, NoOp  , AddOne, NoOp  , AddOne, NoOp  ),
566            (-1,  2, AddOne, NoOp  , AddOne, NoOp  , NoOp  , NoOp  ),
567            (-1,  0, NoOp  , NoOp  , NoOp  , NoOp  , NoOp  , NoOp  ),
568            (-1, -2, NoOp  , SubOne, NoOp  , SubOne, NoOp  , NoOp  ),
569            (-1, -5, NoOp  , SubOne, NoOp  , SubOne, SubOne, SubOne),
570            (-1, -7, NoOp  , SubOne, NoOp  , SubOne, SubOne, SubOne),
571        ];
572        decimal_cases.iter().for_each(test_all_rounding::<10, 1>);
573    }
574
575    #[test]
576    fn test_from_ratio() {
577        #[rustfmt::skip]
578        fn test_all_rounding(
579            input: &(i32, i32, i32, Rounding, Rounding, Rounding, Rounding, Rounding, Rounding),
580        ) {
581            let (value, num, den, rnd_zero, rnd_away, rnd_up, rnd_down, rnd_halfeven, rnd_halfaway) = *input;
582            let (value, num, den) = (IBig::from(value), IBig::from(num), IBig::from(den));
583            assert_eq!(Zero::round_ratio(&value, num.clone(), &den), rnd_zero);
584            assert_eq!(Away::round_ratio(&value, num.clone(), &den), rnd_away);
585            assert_eq!(Up::round_ratio(&value, num.clone(), &den), rnd_up);
586            assert_eq!(Down::round_ratio(&value, num.clone(), &den), rnd_down);
587            assert_eq!(HalfEven::round_ratio(&value, num.clone(), &den), rnd_halfeven);
588            assert_eq!(HalfAway::round_ratio(&value, num, &den), rnd_halfaway);
589        }
590
591        // cases for radix = 2, 2 digit fraction
592        #[rustfmt::skip]
593        let test_cases = [
594            // (integer value, mumerator, denominator, roundings...)
595            // Mode:     Zero  , Away  , Up    , Down  , HEven , HAway
596            ( 0,  0,  2, NoOp  , NoOp  , NoOp  , NoOp  , NoOp  , NoOp  ),
597            ( 0,  1,  2, NoOp  , AddOne, AddOne, NoOp  , NoOp  , AddOne),
598            ( 0, -1,  2, NoOp  , SubOne, NoOp  , SubOne, NoOp  , SubOne),
599            ( 0,  0, -2, NoOp  , NoOp  , NoOp  , NoOp  , NoOp  , NoOp  ),
600            ( 0,  1, -2, NoOp  , SubOne, NoOp  , SubOne, NoOp  , SubOne),
601            ( 0, -1, -2, NoOp  , AddOne, AddOne, NoOp  , NoOp  , AddOne),
602            ( 1,  0,  2, NoOp  , NoOp  , NoOp  , NoOp  , NoOp  , NoOp  ),
603            ( 1,  1,  2, NoOp  , AddOne, AddOne, NoOp  , AddOne, AddOne),
604            ( 1, -1,  2, SubOne, NoOp  , NoOp  , SubOne, SubOne, NoOp  ),
605            ( 1,  0, -2, NoOp  , NoOp  , NoOp  , NoOp  , NoOp  , NoOp  ),
606            ( 1,  1, -2, SubOne, NoOp  , NoOp  , SubOne, SubOne, NoOp  ),
607            ( 1, -1, -2, NoOp  , AddOne, AddOne, NoOp  , AddOne, AddOne),
608            (-1,  0,  2, NoOp  , NoOp  , NoOp  , NoOp  , NoOp  , NoOp  ),
609            (-1,  1,  2, AddOne, NoOp  , AddOne, NoOp  , AddOne, NoOp  ),
610            (-1, -1,  2, NoOp  , SubOne, NoOp  , SubOne, SubOne, SubOne),
611            (-1,  0, -2, NoOp  , NoOp  , NoOp  , NoOp  , NoOp  , NoOp  ),
612            (-1,  1, -2, NoOp  , SubOne, NoOp  , SubOne, SubOne, SubOne),
613            (-1, -1, -2, AddOne, NoOp  , AddOne, NoOp  , AddOne, NoOp  ),
614
615            ( 0, -2,  3, NoOp  , SubOne, NoOp  , SubOne, SubOne, SubOne),
616            ( 0, -1,  3, NoOp  , SubOne, NoOp  , SubOne, NoOp  , NoOp  ),
617            ( 0,  0,  3, NoOp  , NoOp  , NoOp  , NoOp  , NoOp  , NoOp  ),
618            ( 0,  1,  3, NoOp  , AddOne, AddOne, NoOp  , NoOp  , NoOp  ),
619            ( 0,  2,  3, NoOp  , AddOne, AddOne, NoOp  , AddOne, AddOne),
620            ( 0, -2, -3, NoOp  , AddOne, AddOne, NoOp  , AddOne, AddOne),
621            ( 0, -1, -3, NoOp  , AddOne, AddOne, NoOp  , NoOp  , NoOp  ),
622            ( 0,  0, -3, NoOp  , NoOp  , NoOp  , NoOp  , NoOp  , NoOp  ),
623            ( 0,  1, -3, NoOp  , SubOne, NoOp  , SubOne, NoOp  , NoOp  ),
624            ( 0,  2, -3, NoOp  , SubOne, NoOp  , SubOne, SubOne, SubOne),
625            ( 1, -2,  3, SubOne, NoOp  , NoOp  , SubOne, SubOne, SubOne),
626            ( 1, -1,  3, SubOne, NoOp  , NoOp  , SubOne, NoOp  , NoOp  ),
627            ( 1,  0,  3, NoOp  , NoOp  , NoOp  , NoOp  , NoOp  , NoOp  ),
628            ( 1,  1,  3, NoOp  , AddOne, AddOne, NoOp  , NoOp  , NoOp  ),
629            ( 1,  2,  3, NoOp  , AddOne, AddOne, NoOp  , AddOne, AddOne),
630            ( 1, -2, -3, NoOp  , AddOne, AddOne, NoOp  , AddOne, AddOne),
631            ( 1, -1, -3, NoOp  , AddOne, AddOne, NoOp  , NoOp  , NoOp  ),
632            ( 1,  0, -3, NoOp  , NoOp  , NoOp  , NoOp  , NoOp  , NoOp  ),
633            ( 1,  1, -3, SubOne, NoOp  , NoOp  , SubOne, NoOp  , NoOp  ),
634            ( 1,  2, -3, SubOne, NoOp  , NoOp  , SubOne, SubOne, SubOne),
635            (-1, -2,  3, NoOp  , SubOne, NoOp  , SubOne, SubOne, SubOne),
636            (-1, -1,  3, NoOp  , SubOne, NoOp  , SubOne, NoOp  , NoOp  ),
637            (-1,  0,  3, NoOp  , NoOp  , NoOp  , NoOp  , NoOp  , NoOp  ),
638            (-1,  1,  3, AddOne, NoOp  , AddOne, NoOp  , NoOp  , NoOp  ),
639            (-1,  2,  3, AddOne, NoOp  , AddOne, NoOp  , AddOne, AddOne),
640            (-1, -2, -3, AddOne, NoOp  , AddOne, NoOp  , AddOne, AddOne),
641            (-1, -1, -3, AddOne, NoOp  , AddOne, NoOp  , NoOp  , NoOp  ),
642            (-1,  0, -3, NoOp  , NoOp  , NoOp  , NoOp  , NoOp  , NoOp  ),
643            (-1,  1, -3, NoOp  , SubOne, NoOp  , SubOne, NoOp  , NoOp  ),
644            (-1,  2, -3, NoOp  , SubOne, NoOp  , SubOne, SubOne, SubOne),
645        ];
646        test_cases.iter().for_each(test_all_rounding);
647    }
648}