rust_fixed_point_decimal/binops/
div_rounded.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/div_rounded.rs $
8// $Revision: 2021-11-01T15:06:20+01:00 $
9
10use std::cmp::Ordering;
11
12use num::Zero;
13use rust_fixed_point_decimal_core::ten_pow;
14
15use crate::{
16    prec_constraints::{PrecLimitCheck, True},
17    rounding::div_i128_rounded,
18    Decimal, DecimalError, MAX_PREC,
19};
20
21/// Division giving a result rounded to fit a `Result` type.
22pub trait DivRounded<Rhs, Result = Self> {
23    /// Returns `self` / `other`, rounded as `Result`.
24    fn div_rounded(self, rhs: Rhs) -> Result;
25}
26
27impl<const P: u8, const Q: u8, const R: u8> DivRounded<Decimal<Q>, Decimal<R>>
28    for Decimal<P>
29where
30    PrecLimitCheck<{ P <= MAX_PREC }>: True,
31    PrecLimitCheck<{ Q <= MAX_PREC }>: True,
32    PrecLimitCheck<{ R <= MAX_PREC }>: True,
33{
34    #[inline(always)]
35    fn div_rounded(self, other: Decimal<Q>) -> Decimal<R> {
36        if other.eq_zero() {
37            panic!("{}", DecimalError::DivisionByZero);
38        }
39        match P.cmp(&(Q + R)) {
40            Ordering::Equal => Decimal::<R> {
41                coeff: div_i128_rounded(self.coeff, other.coeff, None),
42            },
43            Ordering::Less => Decimal::<R> {
44                coeff: div_i128_rounded(
45                    self.coeff * ten_pow(R + Q - P),
46                    other.coeff,
47                    None,
48                ),
49            },
50            Ordering::Greater => Decimal::<R> {
51                coeff: div_i128_rounded(
52                    self.coeff,
53                    other.coeff * ten_pow(P - Q - R),
54                    None,
55                ),
56            },
57        }
58    }
59}
60
61forward_ref_binop_rounded!(impl DivRounded, div_rounded);
62
63#[cfg(test)]
64mod div_rounded_decimal_tests {
65    use rust_fixed_point_decimal_core::mul_pow_ten;
66
67    use super::*;
68
69    #[test]
70    fn test_div_rounded() {
71        let x = Decimal::<0>::new_raw(17);
72        let y = Decimal::<2>::new_raw(-201);
73        let z: Decimal<2> = x.div_rounded(y);
74        assert_eq!(z.coeff, -846);
75        let x = Decimal::<8>::new_raw(17);
76        let y = Decimal::<3>::new_raw(204);
77        let z: Decimal<8> = x.div_rounded(y);
78        assert_eq!(z.coeff, 83);
79        let x = Decimal::<2>::new_raw(12345678901234567890);
80        let y = Decimal::<6>::new_raw(244140625);
81        let z = x / y;
82        assert_eq!(z.coeff, 505679007794567900774400);
83    }
84
85    #[test]
86    fn test_div_rounded_by_one() {
87        let x = Decimal::<5>::new_raw(17);
88        let y = Decimal::<2>::ONE;
89        let z: Decimal<4> = x.div_rounded(y);
90        assert_eq!(z.coeff, 2);
91        let y = Decimal::<9>::ONE;
92        let z: Decimal<4> = x.div_rounded(y);
93        assert_eq!(z.coeff, 2);
94    }
95
96    #[test]
97    #[should_panic]
98    fn test_div_rounded_by_zero() {
99        let x = Decimal::<5>::new_raw(17);
100        let y = Decimal::<2>::ZERO;
101        let _z: Decimal<5> = x.div_rounded(y);
102    }
103
104    #[test]
105    #[should_panic]
106    fn test_div_rounded_overflow() {
107        let x = Decimal::<0>::new_raw(mul_pow_ten(17, 20));
108        let y = Decimal::<9>::new_raw(2);
109        let _z: Decimal<9> = x.div_rounded(y);
110    }
111
112    #[test]
113    fn test_div_rounded_ref() {
114        let x = Decimal::<3>::new_raw(12345);
115        let y = Decimal::<4>::new_raw(12345);
116        let z: Decimal<2> = x.div_rounded(y);
117        let a: Decimal<2> = DivRounded::div_rounded(&x, y);
118        assert_eq!(a.coeff, z.coeff);
119        let a: Decimal<2> = DivRounded::div_rounded(x, &y);
120        assert_eq!(a.coeff, z.coeff);
121        let a: Decimal<2> = DivRounded::div_rounded(&x, &y);
122        assert_eq!(a.coeff, z.coeff);
123    }
124}
125
126macro_rules! impl_div_rounded_decimal_and_int {
127    () => {
128        impl_div_rounded_decimal_and_int!(
129            u8, i8, u16, i16, u32, i32, u64, i64, i128
130        );
131    };
132    ($($t:ty),*) => {
133        $(
134        impl<const P: u8, const R: u8> DivRounded<$t, Decimal<R>> for Decimal<P>
135        where
136            PrecLimitCheck<{ P <= MAX_PREC }>: True,
137            PrecLimitCheck<{ R <= MAX_PREC }>: True,
138        {
139            fn div_rounded(self, other: $t) -> Decimal<R> {
140                if other.is_zero() {
141                    panic!("{}", DecimalError::DivisionByZero);
142                }
143                match P.cmp(&R) {
144                    Ordering::Equal => Decimal::<R> {
145                        coeff: div_i128_rounded(
146                            self.coeff,
147                            other as i128,
148                            None),
149                    },
150                    Ordering::Less => Decimal::<R> {
151                        coeff: div_i128_rounded(
152                            self.coeff * ten_pow(R - P),
153                            other as i128,
154                            None,
155                        ),
156                    },
157                    Ordering::Greater => Decimal::<R> {
158                        coeff: div_i128_rounded(
159                            self.coeff,
160                            other as i128 * ten_pow(P - R),
161                            None,
162                        ),
163                    },
164                }
165            }
166        }
167
168        impl<'a, const P: u8, const R: u8> DivRounded<$t, Decimal<R>>
169        for &'a Decimal<P>
170        where
171            PrecLimitCheck<{ P <= MAX_PREC }>: True,
172            PrecLimitCheck<{ R <= MAX_PREC }>: True,
173            Decimal<P>: DivRounded<$t, Decimal<R>>,
174        {
175            #[inline(always)]
176            fn div_rounded(self, other: $t) -> Decimal<R> {
177                DivRounded::div_rounded(*self, other)
178            }
179        }
180
181        impl<const P: u8, const R: u8> DivRounded<&$t, Decimal<R>>
182        for Decimal<P>
183        where
184            PrecLimitCheck<{ P <= MAX_PREC }>: True,
185            PrecLimitCheck<{ R <= MAX_PREC }>: True,
186            Decimal<P>: DivRounded<$t, Decimal<R>>,
187        {
188            #[inline(always)]
189            fn div_rounded(self, other: &$t) -> Decimal<R> {
190                DivRounded::div_rounded(self, *other)
191            }
192        }
193
194        impl<const P: u8, const R: u8> DivRounded<&$t, Decimal<R>>
195        for &Decimal<P>
196        where
197            PrecLimitCheck<{ P <= MAX_PREC }>: True,
198            PrecLimitCheck<{ R <= MAX_PREC }>: True,
199            Decimal<P>: DivRounded<$t, Decimal<R>>,
200        {
201            #[inline(always)]
202            fn div_rounded(self, other: &$t) -> Decimal<R> {
203                DivRounded::div_rounded(*self, *other)
204            }
205        }
206
207        impl<const P: u8, const R: u8> DivRounded<Decimal<P>, Decimal<R>>
208        for $t
209        where
210            PrecLimitCheck<{ P <= MAX_PREC }>: True,
211            PrecLimitCheck<{ R <= MAX_PREC }>: True,
212        {
213            fn div_rounded(self, other: Decimal<P>) -> Decimal::<R> {
214                if other.eq_zero() {
215                    panic!("{}", DecimalError::DivisionByZero);
216                }
217                Decimal::<R> {
218                    coeff: div_i128_rounded(
219                        self as i128 * ten_pow(P + R),
220                        other.coeff,
221                        None,
222                    ),
223                }
224            }
225        }
226
227        impl<'a, const P: u8, const R: u8> DivRounded<Decimal<P>, Decimal<R>>
228        for &'a $t
229        where
230            PrecLimitCheck<{ P <= MAX_PREC }>: True,
231            PrecLimitCheck<{ R <= MAX_PREC }>: True,
232            $t: DivRounded<Decimal<P>, Decimal<R>>,
233        {
234            #[inline(always)]
235            fn div_rounded(self, other: Decimal<P>) -> Decimal<R> {
236                DivRounded::div_rounded(*self, other)
237            }
238        }
239
240        impl<const P: u8, const R: u8> DivRounded<&Decimal<P>, Decimal<R>>
241        for $t
242        where
243            PrecLimitCheck<{ P <= MAX_PREC }>: True,
244            PrecLimitCheck<{ R <= MAX_PREC }>: True,
245            $t: DivRounded<Decimal<P>, Decimal<R>>,
246        {
247            #[inline(always)]
248            fn div_rounded(self, other: &Decimal<P>) -> Decimal<R> {
249                DivRounded::div_rounded(self, *other)
250            }
251        }
252
253        impl<const P: u8, const R: u8> DivRounded<&Decimal<P>, Decimal<R>>
254        for &$t
255        where
256            PrecLimitCheck<{ P <= MAX_PREC }>: True,
257            PrecLimitCheck<{ R <= MAX_PREC }>: True,
258            $t: DivRounded<Decimal<P>, Decimal<R>>,
259        {
260            #[inline(always)]
261            fn div_rounded(self, other: &Decimal<P>) -> Decimal<R> {
262                DivRounded::div_rounded(*self, *other)
263            }
264        }
265        )*
266    }
267}
268
269impl_div_rounded_decimal_and_int!();
270
271#[cfg(test)]
272#[allow(clippy::neg_multiply)]
273mod div_rounded_decimal_by_int_tests {
274    use super::*;
275
276    macro_rules! gen_div_rounded_decimal_by_int_tests {
277        ($func:ident, $p:expr, $coeff:expr, $i:expr, $r:expr,
278         $res_coeff:expr) => {
279            #[test]
280            fn $func() {
281                let d = Decimal::<$p>::new_raw($coeff);
282                let i = $i;
283                let r: Decimal<$r> = d.div_rounded(i);
284                assert_eq!(r.coeff, $res_coeff);
285                let r: Decimal<$r> = (&d).div_rounded(i);
286                assert_eq!(r.coeff, $res_coeff);
287                let r: Decimal<$r> = d.div_rounded(&i);
288                assert_eq!(r.coeff, $res_coeff);
289                let r: Decimal<$r> = (&d).div_rounded(&i);
290                assert_eq!(r.coeff, $res_coeff);
291            }
292        };
293    }
294
295    gen_div_rounded_decimal_by_int_tests!(test_u8, 2, -1, 3_u8, 5, -333);
296    gen_div_rounded_decimal_by_int_tests!(test_i8, 0, -12, -3_i8, 5, 400000);
297    gen_div_rounded_decimal_by_int_tests!(test_u16, 2, -1, 3_u16, 5, -333);
298    gen_div_rounded_decimal_by_int_tests!(test_i16, 3, -12, -3_i16, 5, 400);
299    gen_div_rounded_decimal_by_int_tests!(
300        test_u32,
301        4,
302        u32::MAX as i128,
303        1_u32,
304        5,
305        u32::MAX as i128 * 10_i128
306    );
307    gen_div_rounded_decimal_by_int_tests!(
308        test_i32, 3, 12345, -328_i32, 5, -3764
309    );
310    gen_div_rounded_decimal_by_int_tests!(test_u64, 9, -1, 2_u64, 5, 0);
311    gen_div_rounded_decimal_by_int_tests!(
312        test_i64,
313        3,
314        u64::MAX as i128,
315        i64::MIN,
316        2,
317        0
318    );
319    gen_div_rounded_decimal_by_int_tests!(
320        test_i128,
321        0,
322        12345678901234567890,
323        987654321_i128,
324        5,
325        1249999988734375
326    );
327}
328
329#[cfg(test)]
330#[allow(clippy::neg_multiply)]
331mod div_rounded_int_by_decimal_tests {
332    use super::*;
333
334    macro_rules! gen_div_rounded_int_by_decimal_tests {
335        ($func:ident, $p:expr, $coeff:expr, $i:expr, $r:expr,
336         $res_coeff:expr) => {
337            #[test]
338            fn $func() {
339                let d = Decimal::<$p>::new_raw($coeff);
340                let i = $i;
341                let r: Decimal<$r> = i.div_rounded(d);
342                assert_eq!(r.coeff, $res_coeff);
343                let r: Decimal<$r> = (&i).div_rounded(d);
344                assert_eq!(r.coeff, $res_coeff);
345                let r: Decimal<$r> = i.div_rounded(&d);
346                assert_eq!(r.coeff, $res_coeff);
347                let r: Decimal<$r> = (&i).div_rounded(&d);
348                assert_eq!(r.coeff, $res_coeff);
349            }
350        };
351    }
352
353    gen_div_rounded_int_by_decimal_tests!(test_u8, 2, -14, 3_u8, 5, -2142857);
354    gen_div_rounded_int_by_decimal_tests!(test_i8, 0, -12, -3_i8, 5, 25000);
355    gen_div_rounded_int_by_decimal_tests!(test_u16, 2, -17, 3_u16, 5, -1764706);
356    gen_div_rounded_int_by_decimal_tests!(test_i16, 3, -12, -3_i16, 2, 25000);
357    gen_div_rounded_int_by_decimal_tests!(
358        test_u32,
359        4,
360        u32::MAX as i128,
361        1_u32,
362        9,
363        2328
364    );
365    gen_div_rounded_int_by_decimal_tests!(
366        test_i32, 3, 12345, -328_i32, 5, -2656946
367    );
368    gen_div_rounded_int_by_decimal_tests!(
369        test_u64,
370        9,
371        -1,
372        2_u64,
373        5,
374        -200000000000000
375    );
376    gen_div_rounded_int_by_decimal_tests!(
377        test_i64,
378        3,
379        u64::MAX as i128,
380        i64::MIN,
381        2,
382        -50000
383    );
384    gen_div_rounded_int_by_decimal_tests!(
385        test_i128,
386        0,
387        1234567890,
388        987654321987654321_i128,
389        1,
390        8000000081
391    );
392}