Skip to main content

fpdec/binops/
checked_div.rs

1// ---------------------------------------------------------------------------
2// Copyright:   (c) 2021 ff. Michael Amrhein (michael@adrhinum.de)
3// License:     This program is part of a larger application. For license
4//              details please read the file LICENSE.TXT provided together
5//              with the application.
6// ---------------------------------------------------------------------------
7// $Source: src/binops/checked_div.rs $
8// $Revision: 2023-06-15T21:17:48+02:00 $
9
10use fpdec_core::MAX_N_FRAC_DIGITS;
11
12use crate::{binops::div_rounded::checked_div_rounded, normalize, Decimal};
13
14/// Checked division.
15/// Computes `self / rhs`.
16/// Returns `None` if the result can not be represented by the `Output` type.
17pub trait CheckedDiv<Rhs = Self> {
18    /// The resulting type after applying `checked_div`.
19    type Output;
20    /// Returns `Some(self / rhs)` or `None` if the result can not be
21    /// represented by the `Output` type.
22    fn checked_div(self, rhs: Rhs) -> Self::Output;
23}
24
25impl CheckedDiv<Self> for Decimal {
26    type Output = Option<Self>;
27
28    fn checked_div(self, rhs: Self) -> Self::Output {
29        if rhs.eq_zero() {
30            return None;
31        }
32        if self.eq_zero() {
33            return Some(Self::ZERO);
34        }
35        if rhs.eq_one() {
36            return Some(self);
37        }
38        let mut n_frac_digits = MAX_N_FRAC_DIGITS;
39        let mut coeff = checked_div_rounded(
40            self.coeff,
41            self.n_frac_digits,
42            rhs.coeff,
43            rhs.n_frac_digits,
44            n_frac_digits,
45        )?;
46        normalize(&mut coeff, &mut n_frac_digits);
47        Some(Self {
48            coeff,
49            n_frac_digits,
50        })
51    }
52}
53
54forward_ref_binop!(impl CheckedDiv, checked_div);
55
56#[cfg(test)]
57mod checked_div_decimal_tests {
58    use fpdec_core::mul_pow_ten;
59
60    use super::*;
61
62    #[test]
63    fn test_checked_div() {
64        let x = Decimal::new_raw(17, 0);
65        let y = Decimal::new_raw(-200, 2);
66        let z = x.checked_div(y).unwrap();
67        assert_eq!(z.coefficient(), -85);
68        assert_eq!(z.n_frac_digits(), 1);
69        let x = Decimal::new_raw(17, 17);
70        let y = Decimal::new_raw(2, 0);
71        let z = x.checked_div(y).unwrap();
72        assert_eq!(z.coefficient(), 85);
73        assert_eq!(z.n_frac_digits(), 18);
74        let x = Decimal::new_raw(12345678901234567890, 2);
75        let y = Decimal::new_raw(244140625, 6);
76        let z = x.checked_div(y).unwrap();
77        assert_eq!(z.coefficient(), 5056790077945679007744);
78        assert_eq!(z.n_frac_digits(), 7);
79    }
80
81    #[test]
82    fn test_checked_div_frac_limit_exceeded() {
83        let x = Decimal::new_raw(17, 1);
84        let y = Decimal::new_raw(3, 0);
85        let z = x.checked_div(y).unwrap();
86        assert_eq!(z.coefficient(), 566666666666666667);
87        assert_eq!(z.n_frac_digits(), 18);
88    }
89
90    #[test]
91    fn test_checked_div_by_one() {
92        let x = Decimal::new_raw(17, 5);
93        let y = Decimal::ONE;
94        let z = x.checked_div(y).unwrap();
95        assert_eq!(z.coefficient(), x.coefficient());
96        let y = Decimal::new_raw(100000, 5);
97        let z = x.checked_div(y).unwrap();
98        assert_eq!(z.coefficient(), x.coefficient());
99    }
100
101    #[test]
102    fn test_checked_div_by_zero() {
103        let x = Decimal::new_raw(17, 5);
104        let y = Decimal::ZERO;
105        let z = x.checked_div(y);
106        assert!(z.is_none());
107    }
108
109    // corner case where divident * shift overflows, but result doesn't
110    #[test]
111    fn test_checked_div_stepwise() {
112        let x = Decimal::new_raw(mul_pow_ten(17, 17), 0);
113        let y = Decimal::new_raw(20498, 5);
114        let z = x.checked_div(y).unwrap();
115        assert_eq!(
116            z.coefficient(),
117            8293492048004683383744755585910820568_i128
118        );
119        assert_eq!(z.n_frac_digits(), 18);
120    }
121
122    #[test]
123    fn test_checked_div_overflow() {
124        let x = Decimal::new_raw(mul_pow_ten(17, 20), 0);
125        let y = Decimal::new_raw(2, 18);
126        let z = x.checked_div(y);
127        assert!(z.is_none());
128    }
129
130    #[test]
131    fn test_checked_div_ref() {
132        let x = Decimal::new_raw(12345, 3);
133        let y = Decimal::new_raw(12345, 1);
134        let z = x.checked_div(y).unwrap();
135        assert_eq!(
136            z.coefficient(),
137            (&x).checked_div(y).unwrap().coefficient()
138        );
139        assert_eq!(z.coefficient(), x.checked_div(&y).unwrap().coefficient());
140        assert_eq!(
141            z.coefficient(),
142            (&x).checked_div(&y).unwrap().coefficient()
143        );
144    }
145}
146
147macro_rules! impl_div_decimal_and_int {
148    () => {
149        impl_div_decimal_and_int!(u8, i8, u16, i16, u32, i32, u64, i64, i128);
150    };
151    ($($t:ty),*) => {
152        $(
153        impl CheckedDiv<$t> for Decimal {
154            type Output = Option<Self>;
155
156            fn checked_div(self, rhs: $t) -> Self::Output {
157                if rhs == 0 {
158                    return None;
159                }
160                if self.eq_zero() {
161                    return Some(Self::ZERO);
162                }
163                if rhs == 1 {
164                    return Some(self);
165                }
166                let mut n_frac_digits = MAX_N_FRAC_DIGITS;
167                let mut coeff = checked_div_rounded(
168                    self.coeff,
169                    self.n_frac_digits,
170                    i128::from(rhs),
171                    0,
172                    n_frac_digits,
173                )?;
174                normalize(&mut coeff, &mut n_frac_digits);
175                Some(Self {
176                    coeff,
177                    n_frac_digits,
178                })
179            }
180        }
181
182        impl CheckedDiv<Decimal> for $t {
183            type Output = Option<Decimal>;
184
185            fn checked_div(self, rhs: Decimal) -> Self::Output {
186                if rhs.eq_zero() {
187                    return None;
188                }
189                if self == 0 {
190                    return Some(Decimal::ZERO);
191                }
192                if rhs.eq_one() {
193                    return Some(Decimal {
194                        coeff: i128::from(self),
195                        n_frac_digits: 0
196                    });
197                }
198                let mut n_frac_digits = MAX_N_FRAC_DIGITS;
199                let mut coeff = checked_div_rounded(
200                    i128::from(self),
201                    0,
202                    rhs.coeff,
203                    rhs.n_frac_digits,
204                    n_frac_digits,
205                )?;
206                normalize(&mut coeff, &mut n_frac_digits);
207                Some(Decimal {
208                    coeff,
209                    n_frac_digits,
210                })
211            }
212        }
213        )*
214    }
215}
216
217impl_div_decimal_and_int!();
218forward_ref_binop_decimal_int!(impl CheckedDiv, checked_div);
219
220#[cfg(test)]
221#[allow(clippy::neg_multiply)]
222mod checked_div_integer_tests {
223    use fpdec_core::mul_pow_ten;
224
225    use super::*;
226
227    macro_rules! gen_checked_div_integer_tests {
228        ($func:ident, $t:ty, $den:expr, $p:expr, $num:expr, $q:expr,
229         $quot:expr) => {
230            #[test]
231            fn $func() {
232                let d = Decimal::new_raw($num, $p);
233                let i: $t = $den;
234                let r = d.checked_div(i).unwrap();
235                assert_eq!(r.coefficient(), $quot);
236                assert_eq!(r.n_frac_digits(), $q);
237                assert_eq!(
238                    r.coefficient(),
239                    (&d).checked_div(i).unwrap().coefficient()
240                );
241                assert_eq!(
242                    r.coefficient(),
243                    d.checked_div(&i).unwrap().coefficient()
244                );
245                assert_eq!(
246                    r.coefficient(),
247                    (&d).checked_div(&i).unwrap().coefficient()
248                );
249                let z = CheckedDiv::checked_div(i, d).unwrap();
250                assert_eq!(z, CheckedDiv::checked_div(1_u8, r).unwrap());
251                assert_eq!(
252                    z.coefficient(),
253                    CheckedDiv::checked_div(&i, d).unwrap().coefficient()
254                );
255                assert_eq!(
256                    z.coefficient(),
257                    CheckedDiv::checked_div(i, &d).unwrap().coefficient()
258                );
259                assert_eq!(
260                    z.coefficient(),
261                    CheckedDiv::checked_div(&i, &d).unwrap().coefficient()
262                );
263            }
264        };
265    }
266
267    gen_checked_div_integer_tests!(test_checked_div_u8, u8, 5, 2, -1, 3, -2);
268    gen_checked_div_integer_tests!(
269        test_checked_div_i8,
270        i8,
271        115,
272        0,
273        230,
274        0,
275        2
276    );
277    gen_checked_div_integer_tests!(
278        test_checked_div_u16,
279        u16,
280        160,
281        4,
282        80,
283        5,
284        5
285    );
286    gen_checked_div_integer_tests!(
287        test_checked_div_i16,
288        i16,
289        25,
290        4,
291        390625,
292        4,
293        15625
294    );
295    gen_checked_div_integer_tests!(
296        test_checked_div_u32,
297        u32,
298        40,
299        1,
300        10,
301        3,
302        25
303    );
304    gen_checked_div_integer_tests!(
305        test_checked_div_i32,
306        i32,
307        -100,
308        9,
309        -1000,
310        8,
311        1
312    );
313    gen_checked_div_integer_tests!(
314        test_checked_div_u64,
315        u64,
316        1250,
317        4,
318        31250,
319        4,
320        25
321    );
322    gen_checked_div_integer_tests!(
323        test_checked_div_i64,
324        i64,
325        9765625,
326        7,
327        -488281250,
328        6,
329        -5
330    );
331    gen_checked_div_integer_tests!(
332        test_checked_div_i128,
333        i128,
334        5005,
335        0,
336        2002,
337        1,
338        4
339    );
340
341    #[test]
342    fn test_checked_div_decimal_by_int_one() {
343        let x = Decimal::new_raw(17, 5);
344        let y = 1_i64;
345        let z = x.checked_div(y).unwrap();
346        assert_eq!(z.coefficient(), x.coefficient());
347        assert_eq!(z.n_frac_digits(), x.n_frac_digits());
348        let y = 1_u8;
349        let z = x.checked_div(y).unwrap();
350        assert_eq!(z.coefficient(), x.coefficient());
351        assert_eq!(z.n_frac_digits(), x.n_frac_digits());
352    }
353
354    #[test]
355    fn test_checked_div_int_by_decimal_one() {
356        let x = 17_i32;
357        let y = Decimal::ONE;
358        let z = CheckedDiv::checked_div(x, y).unwrap();
359        assert_eq!(z.coefficient(), 17);
360        assert_eq!(z.n_frac_digits(), 0);
361        let x = 1_u64;
362        let z = CheckedDiv::checked_div(x, y).unwrap();
363        assert_eq!(z.coefficient(), 1);
364        assert_eq!(z.n_frac_digits(), 0);
365    }
366
367    #[test]
368    fn test_checked_div_int_by_decimal_frac_limit_exceeded() {
369        let x = 3_i8;
370        let y = Decimal::new_raw(17, 2);
371        let z = CheckedDiv::checked_div(x, y).unwrap();
372        assert_eq!(z.coefficient(), 17647058823529411765);
373        assert_eq!(z.n_frac_digits(), 18);
374    }
375
376    #[test]
377    fn test_checked_div_decimal_by_int_frac_limit_exceeded() {
378        let x = Decimal::new_raw(17, 12);
379        let y = 3_u8;
380        let z = x.checked_div(y).unwrap();
381        assert_eq!(z.coefficient(), 5666667);
382        assert_eq!(z.n_frac_digits(), 18);
383    }
384
385    #[test]
386    fn test_checked_div_decimal_by_int_zero() {
387        let x = Decimal::new_raw(17, 5);
388        let y = 0_i32;
389        let z = x.checked_div(y);
390        assert!(z.is_none());
391    }
392
393    #[test]
394    fn test_checked_div_int_by_decimal_zero() {
395        let x = 25_i64;
396        let y = Decimal::ZERO;
397        let z = CheckedDiv::checked_div(x, y);
398        assert!(z.is_none());
399    }
400
401    #[test]
402    fn test_checked_div_int_by_decimal_overflow() {
403        let x = mul_pow_ten(17, 20);
404        let y = Decimal::new_raw(2, 18);
405        let z = CheckedDiv::checked_div(x, y);
406        assert!(z.is_none());
407    }
408}