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