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}