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}