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