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