rust_finprim/rate/
apr_ear.rs

1use crate::ONE;
2use rust_decimal::prelude::*;
3
4/// APR - Annual (Nominal) Percentage Rate
5///
6/// Calculated from the effective interest rate and the number of compounding periods per year.
7/// Similar behavior and usage to the `NOMINAL` function in Excel.
8///
9/// The APR is the annualized interest rate that you are charged on your loan.
10/// It is expressed as a percentage that represents the actual yearly cost of funds over the term of a loan.
11/// This includes any fees or additional costs associated with the transaction but does not take compounding into account.
12///
13/// # Arguments
14/// * `ear` - The effective interest rate (EAR)
15/// * `npery` - The number of compounding periods per year
16///
17/// # Returns
18/// * The annual percentage rate (nominal interest rate)
19///
20/// # Example
21/// * EAR of 5% with 12 compounding periods per year
22/// ```
23/// use rust_finprim::rate::apr;
24/// use rust_decimal_macros::*;
25/// let ear = dec!(0.05); let npery = dec!(12);
26/// apr(ear, npery);
27/// ```
28///
29/// # Formula
30/// $$APR=n(\sqrt\[n\]{1+EAR}-1)$$
31///
32/// Where:
33/// * \\(n\\) = number of compounding periods per year
34/// * \\(EAR\\) = effective annual Rate
35pub fn apr(ear: Decimal, npery: Decimal) -> Decimal {
36    let nth_root = (ONE + ear).powd(ONE / npery);
37    npery * (nth_root - ONE)
38}
39
40/// EAR - Effective Annual Rate
41///
42/// The effective annual rate (EAR) is the interest rate on a loan or financial product restated
43/// from the nominal interest rate with compounding taken into account.
44/// Similar behavior and usage to the `EFFECT` function in Excel.
45///
46/// The EAR is the rate actually paid or earned on an investment, loan or other financial product
47/// due to the result of compounding over a given time period.
48///
49/// # Arguments
50/// * `apr` - The annual percentage rate (APR, nominal interest rate)
51/// * `npery` - The number of compounding periods per year
52///
53/// # Returns
54/// * The effective annual rate (EAR)
55///
56/// # Example
57/// * APR of 5% with 12 compounding periods per year
58/// ```
59/// use rust_finprim::rate::ear;
60/// use rust_decimal_macros::*;
61///
62/// let apr = dec!(0.05); let npery = dec!(12);
63/// ear(apr, npery);
64/// ```
65///
66/// # Formula
67/// $$EAR=(1+\frac{APR}{n})^n-1$$
68///
69/// Where:
70/// * \\(APR\\) = annual percentage rate (nominal interest rate)
71/// * \\(n\\) = number of compounding periods per year
72pub fn ear(apr: Decimal, npery: Decimal) -> Decimal {
73    let nth_root = ONE + apr / npery;
74    nth_root.powd(npery) - ONE
75}
76
77#[cfg(test)]
78mod tests {
79    #[cfg(not(feature = "std"))]
80    extern crate std;
81    use super::*;
82    use rust_decimal_macros::dec;
83    #[cfg(not(feature = "std"))]
84    use std::assert;
85    #[cfg(not(feature = "std"))]
86    use std::prelude::v1::*;
87
88    #[test]
89    fn test_apr() {
90        struct TestCase {
91            n: Decimal,
92            ear: Decimal,
93            expected: Decimal,
94            description: &'static str,
95        }
96        impl TestCase {
97            fn new(n: f64, ear: f64, expected: f64, description: &'static str) -> TestCase {
98                TestCase {
99                    n: Decimal::from_f64(n).unwrap(),
100                    ear: Decimal::from_f64(ear).unwrap(),
101                    expected: Decimal::from_f64(expected).unwrap(),
102                    description,
103                }
104            }
105        }
106
107        let test_cases = [
108            TestCase::new(
109                12.0,
110                0.05,
111                0.04889,
112                "Standard case with EAR of 0.05 and monthly compounding",
113            ),
114            TestCase::new(12.0, 0.0, 0.0, "Zero EAR should result in zero APR"),
115            TestCase::new(12.0, 0.2, 0.18371, "High EAR of 0.2 with monthly compounding"),
116        ];
117
118        for case in &test_cases {
119            let calculated_apr = apr(case.ear, case.n);
120            assert!(
121                (calculated_apr - case.expected).abs() < dec!(1e-5),
122                "Failed on case: {}. Expected {}, got {}",
123                case.description,
124                case.expected,
125                calculated_apr
126            );
127        }
128    }
129
130    #[test]
131    fn test_ear() {
132        struct TestCase {
133            n: Decimal,
134            apr: Decimal,
135            expected: Decimal,
136            description: &'static str,
137        }
138        impl TestCase {
139            fn new(n: f64, apr: f64, expected: f64, description: &'static str) -> TestCase {
140                TestCase {
141                    n: Decimal::from_f64(n).unwrap(),
142                    apr: Decimal::from_f64(apr).unwrap(),
143                    expected: Decimal::from_f64(expected).unwrap(),
144                    description,
145                }
146            }
147        }
148
149        let test_cases = [
150            TestCase::new(
151                12.0,
152                0.05,
153                0.05116,
154                "Standard case with APR of 0.05 and monthly compounding",
155            ),
156            TestCase::new(12.0, 0.0, 0.0, "Zero APR should result in zero EAR"),
157            TestCase::new(12.0, 0.2, 0.21939, "High APR of 0.2 with monthly compounding"),
158        ];
159
160        for case in &test_cases {
161            let calculated_ear = ear(case.apr, case.n);
162            assert!(
163                (calculated_ear - case.expected).abs() < dec!(1e-5),
164                "Failed on case: {}. Expected {}, got {}",
165                case.description,
166                case.expected,
167                calculated_ear
168            );
169        }
170    }
171}