Skip to main content

fpdec/binops/
mul_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/mul_rounded.rs $
8// $Revision: 2023-06-15T21:17:48+02:00 $
9
10use fpdec_core::{
11    i128_div_rounded, i128_mul_div_ten_pow_rounded, ten_pow,
12    MAX_N_FRAC_DIGITS,
13};
14
15use crate::{Decimal, DecimalError};
16
17/// Multiplication giving a result rounded to a given number of fractional
18/// digits.
19pub trait MulRounded<Rhs = Self> {
20    /// The resulting type after applying `mul_rounded`.
21    type Output;
22
23    /// Returns `self` * `rhs`, rounded to `n_frac_digits`.
24    fn mul_rounded(self, rhs: Rhs, n_frac_digits: u8) -> Self::Output;
25}
26
27pub(crate) fn checked_mul_rounded(
28    x: Decimal,
29    y: Decimal,
30    n_frac_digits: u8,
31) -> Option<Decimal> {
32    let max_n_frac_digits = x.n_frac_digits + y.n_frac_digits;
33    if n_frac_digits >= max_n_frac_digits {
34        // no need for rounding
35        Some(Decimal {
36            coeff: x.coeff.checked_mul(y.coeff)?,
37            n_frac_digits: max_n_frac_digits,
38        })
39    } else {
40        let shift = max_n_frac_digits - n_frac_digits;
41        if let Some(coeff) = x.coeff.checked_mul(y.coeff) {
42            Some(Decimal {
43                coeff: i128_div_rounded(coeff, ten_pow(shift), None),
44                n_frac_digits,
45            })
46        } else {
47            let coeff =
48                i128_mul_div_ten_pow_rounded(x.coeff, y.coeff, shift, None)?;
49            Some(Decimal {
50                coeff,
51                n_frac_digits,
52            })
53        }
54    }
55}
56
57impl MulRounded<Self> for Decimal {
58    type Output = Self;
59
60    #[inline]
61    fn mul_rounded(self, rhs: Self, n_frac_digits: u8) -> Self::Output {
62        #[allow(clippy::manual_assert)]
63        if n_frac_digits > MAX_N_FRAC_DIGITS {
64            panic!("{}", DecimalError::MaxNFracDigitsExceeded);
65        }
66        if self.eq_zero() || rhs.eq_zero() {
67            return Self::ZERO;
68        }
69        if let Some(res) = checked_mul_rounded(self, rhs, n_frac_digits) {
70            res
71        } else {
72            panic!("{}", DecimalError::InternalOverflow);
73        }
74    }
75}
76
77forward_ref_binop_rounded!(impl MulRounded, mul_rounded);
78
79#[cfg(test)]
80mod mul_rounded_decimal_tests {
81    use super::*;
82
83    #[test]
84    fn test_mul_rounded_less_n_frac_digits() {
85        let x = Decimal::new_raw(12345, 2);
86        let z = x.mul_rounded(x, 2);
87        assert_eq!(z.coefficient(), 1523990);
88        assert_eq!(z.n_frac_digits(), 2);
89        let y = Decimal::new_raw(5781, 4);
90        let z = x.mul_rounded(y, 1);
91        assert_eq!(z.coefficient(), 714);
92        assert_eq!(z.n_frac_digits(), 1);
93        let z = y.mul_rounded(x, 1);
94        assert_eq!(z.coefficient(), 714);
95        assert_eq!(z.n_frac_digits(), 1);
96    }
97
98    #[test]
99    fn test_mul_rounded_no_adj_needed() {
100        let x = Decimal::new_raw(12345, 2);
101        let z = x.mul_rounded(x, 4);
102        assert_eq!(z.coefficient(), 152399025);
103        assert_eq!(z.n_frac_digits(), 4);
104        let y = Decimal::new_raw(5781, 4);
105        let z = x.mul_rounded(y, 10);
106        assert_eq!(z.coefficient(), 71366445);
107        assert_eq!(z.n_frac_digits(), 6);
108        let z = y.mul_rounded(x, 7);
109        assert_eq!(z.coefficient(), 71366445);
110        assert_eq!(z.n_frac_digits(), 6);
111    }
112
113    #[test]
114    fn test_mul_rounded_ref() {
115        let x = Decimal::new_raw(12345, 3);
116        let y = Decimal::new_raw(12345, 1);
117        let z = x.mul_rounded(y, 2);
118        let a = MulRounded::mul_rounded(&x, y, 2);
119        assert_eq!(a.coefficient(), z.coefficient());
120        let a = MulRounded::mul_rounded(x, &y, 2);
121        assert_eq!(a.coefficient(), z.coefficient());
122        let a = MulRounded::mul_rounded(&x, &y, 2);
123        assert_eq!(a.coefficient(), z.coefficient());
124    }
125}