1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
use crate::ONE;
use rust_decimal::prelude::*;

/// APR - Annual (Nominal) Percentage Rate
///
/// Calculated from the effective interest rate and the number of compounding periods per year.
/// Similar behavior and usage to the `NOMINAL` function in Excel.
///
/// The APR is the annualized interest rate that you are charged on your loan.
/// It is expressed as a percentage that represents the actual yearly cost of funds over the term of a loan.
/// This includes any fees or additional costs associated with the transaction but does not take compounding into account.
///
/// # Arguments
/// * `ear` - The effective interest rate (EAR)
/// * `npery` - The number of compounding periods per year
///
/// # Returns
/// * The annual percentage rate (nominal interest rate)
///
/// # Example
/// * EAR of 5% with 12 compounding periods per year
/// ```
/// use rust_finprim::rate::apr;
/// use rust_decimal_macros::*;
/// let ear = dec!(0.05); let npery = dec!(12);
/// apr(ear, npery);
/// ```
///
/// # Formula
/// $$APR=n(\sqrt\[n\]{1+EAR}-1)$$
///
/// Where:
/// * \\(n\\) = number of compounding periods per year
/// * \\(EAR\\) = effective annual Rate
pub fn apr(ear: Decimal, npery: Decimal) -> Decimal {
    let nth_root = (ONE + ear).powd(ONE / npery);
    npery * (nth_root - ONE)
}

/// EAR - Effective Annual Rate
///
/// The effective annual rate (EAR) is the interest rate on a loan or financial product restated
/// from the nominal interest rate with compounding taken into account.
/// Similar behavior and usage to the `EFFECT` function in Excel.
///
/// The EAR is the rate actually paid or earned on an investment, loan or other financial product
/// due to the result of compounding over a given time period.
///
/// # Arguments
/// * `apr` - The annual percentage rate (APR, nominal interest rate)
/// * `npery` - The number of compounding periods per year
///
/// # Returns
/// * The effective annual rate (EAR)
///
/// # Example
/// * APR of 5% with 12 compounding periods per year
/// ```
/// use rust_finprim::rate::ear;
/// use rust_decimal_macros::*;
///
/// let apr = dec!(0.05); let npery = dec!(12);
/// ear(apr, npery);
/// ```
///
/// # Formula
/// $$EAR=(1+\frac{APR}{n})^n-1$$
///
/// Where:
/// * \\(APR\\) = annual percentage rate (nominal interest rate)
/// * \\(n\\) = number of compounding periods per year
pub fn ear(apr: Decimal, npery: Decimal) -> Decimal {
    let nth_root = ONE + apr / npery;
    nth_root.powd(npery) - ONE
}

#[cfg(test)]
mod tests {
    #[cfg(not(feature = "std"))]
    extern crate std;
    use super::*;
    use rust_decimal_macros::dec;
    #[cfg(not(feature = "std"))]
    use std::assert;
    #[cfg(not(feature = "std"))]
    use std::prelude::v1::*;

    #[test]
    fn test_apr() {
        struct TestCase {
            n: Decimal,
            ear: Decimal,
            expected: Decimal,
            description: &'static str,
        }
        impl TestCase {
            fn new(n: f64, ear: f64, expected: f64, description: &'static str) -> TestCase {
                TestCase {
                    n: Decimal::from_f64(n).unwrap(),
                    ear: Decimal::from_f64(ear).unwrap(),
                    expected: Decimal::from_f64(expected).unwrap(),
                    description,
                }
            }
        }

        let test_cases = [
            TestCase::new(
                12.0,
                0.05,
                0.04889,
                "Standard case with EAR of 0.05 and monthly compounding",
            ),
            TestCase::new(12.0, 0.0, 0.0, "Zero EAR should result in zero APR"),
            TestCase::new(12.0, 0.2, 0.18371, "High EAR of 0.2 with monthly compounding"),
        ];

        for case in &test_cases {
            let calculated_apr = apr(case.ear, case.n);
            assert!(
                (calculated_apr - case.expected).abs() < dec!(1e-5),
                "Failed on case: {}. Expected {}, got {}",
                case.description,
                case.expected,
                calculated_apr
            );
        }
    }

    #[test]
    fn test_ear() {
        struct TestCase {
            n: Decimal,
            apr: Decimal,
            expected: Decimal,
            description: &'static str,
        }
        impl TestCase {
            fn new(n: f64, apr: f64, expected: f64, description: &'static str) -> TestCase {
                TestCase {
                    n: Decimal::from_f64(n).unwrap(),
                    apr: Decimal::from_f64(apr).unwrap(),
                    expected: Decimal::from_f64(expected).unwrap(),
                    description,
                }
            }
        }

        let test_cases = [
            TestCase::new(
                12.0,
                0.05,
                0.05116,
                "Standard case with APR of 0.05 and monthly compounding",
            ),
            TestCase::new(12.0, 0.0, 0.0, "Zero APR should result in zero EAR"),
            TestCase::new(12.0, 0.2, 0.21939, "High APR of 0.2 with monthly compounding"),
        ];

        for case in &test_cases {
            let calculated_ear = ear(case.apr, case.n);
            assert!(
                (calculated_ear - case.expected).abs() < dec!(1e-5),
                "Failed on case: {}. Expected {}, got {}",
                case.description,
                case.expected,
                calculated_ear
            );
        }
    }
}