Skip to content

Commit 7a690c1

Browse files
feat: add Simple, Compound, and APR interest calculations (#1026)
1 parent a84b940 commit 7a690c1

File tree

4 files changed

+214
-26
lines changed

4 files changed

+214
-26
lines changed

DIRECTORY.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,10 +149,10 @@
149149
* [Trapped Rainwater](https://github.com/TheAlgorithms/Rust/blob/master/src/dynamic_programming/trapped_rainwater.rs)
150150
* [Word Break](https://github.com/TheAlgorithms/Rust/blob/master/src/dynamic_programming/word_break.rs)
151151
* Financial
152-
* [Compound Interest](https://github.com/TheAlgorithms/Rust/blob/master/src/financial/compound_interest.rs)
153152
* [Equated Monthly Installments](https://github.com/TheAlgorithms/Rust/blob/master/src/financial/equated_monthly_installments.rs)
154153
* [Exponential Moving Average](https://github.com/TheAlgorithms/Rust/blob/master/src/financial/exponential_moving_average.rs)
155154
* [Finance Ratios](https://github.com/TheAlgorithms/Rust/blob/master/src/financial/finance_ratios.rs)
155+
* [Interest](https://github.com/TheAlgorithms/Rust/blob/master/src/financial/interest.rs)
156156
* [Net Present Value](https://github.com/TheAlgorithms/Rust/blob/master/src/financial/npv.rs)
157157
* [NPV Sensitivity](https://github.com/TheAlgorithms/Rust/blob/master/src/financial/npv_sensitivity.rs)
158158
* [Payback Period](https://github.com/TheAlgorithms/Rust/blob/master/src/financial/payback.rs)

src/financial/compound_interest.rs

Lines changed: 0 additions & 23 deletions
This file was deleted.

src/financial/interest.rs

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
//! Calculates simple, compound, and APR interest on a principal amount.
2+
//!
3+
//! Formulas:
4+
//! Simple Interest: I = p * r * t
5+
//! Compound Interest: I = p * ((1 + r)^n - 1)
6+
//! APR Interest: Compound interest with r = annual_rate / 365
7+
//! and n = years * 365
8+
//! where:
9+
//! - `p` is the principal
10+
//! - `r` is the interest rate per period
11+
//! - `t` is the number of periods (days)
12+
//! - `n` is the total number of compounding periods
13+
//!
14+
//! Reference: https://www.investopedia.com/terms/i/interest.asp
15+
16+
/// Calculates simple interest earned over a number of days.
17+
///
18+
/// # Arguments
19+
/// * `principal` - The initial amount of money (must be > 0)
20+
/// * `daily_interest_rate` - The daily interest rate as a decimal (must be >= 0)
21+
/// * `days_between_payments` - The number of days between payments (must be > 0)
22+
///
23+
/// # Errors
24+
/// Returns an `Err(&'static str)` if any argument is out of range.
25+
pub fn simple_interest(
26+
principal: f64,
27+
daily_interest_rate: f64,
28+
days_between_payments: f64,
29+
) -> Result<f64, &'static str> {
30+
if principal <= 0.0 {
31+
return Err("principal must be > 0");
32+
}
33+
if daily_interest_rate < 0.0 {
34+
return Err("daily_interest_rate must be >= 0");
35+
}
36+
if days_between_payments <= 0.0 {
37+
return Err("days_between_payments must be > 0");
38+
}
39+
Ok(principal * daily_interest_rate * days_between_payments)
40+
}
41+
42+
/// Calculates compound interest earned over a number of compounding periods.
43+
///
44+
/// # Arguments
45+
/// * `principal` - The initial amount of money (must be > 0)
46+
/// * `nominal_annual_interest_rate` - The rate per compounding period as a decimal (must be >= 0)
47+
/// * `number_of_compounding_periods` - The total number of compounding periods (must be > 0)
48+
///
49+
/// # Errors
50+
/// Returns an `Err(&'static str)` if any argument is out of range.
51+
pub fn compound_interest(
52+
principal: f64,
53+
nominal_annual_interest_rate: f64,
54+
number_of_compounding_periods: f64,
55+
) -> Result<f64, &'static str> {
56+
if principal <= 0.0 {
57+
return Err("principal must be > 0");
58+
}
59+
if nominal_annual_interest_rate < 0.0 {
60+
return Err("nominal_annual_interest_rate must be >= 0");
61+
}
62+
if number_of_compounding_periods <= 0.0 {
63+
return Err("number_of_compounding_periods must be > 0");
64+
}
65+
Ok(
66+
principal
67+
* ((1.0 + nominal_annual_interest_rate).powf(number_of_compounding_periods) - 1.0),
68+
)
69+
}
70+
71+
/// Calculates interest using the Annual Percentage Rate (APR), compounded daily.
72+
///
73+
/// Converts the APR to a daily rate and compounds over the equivalent number
74+
/// of days, giving a more accurate real-world figure than simple annual compounding.
75+
///
76+
/// # Arguments
77+
/// * `principal` - The initial amount of money (must be > 0)
78+
/// * `nominal_annual_percentage_rate` - The APR as a decimal (must be >= 0)
79+
/// * `number_of_years` - The loan or investment duration in years (must be > 0)
80+
///
81+
/// # Errors
82+
/// Returns an `Err(&'static str)` if any argument is out of range.
83+
pub fn apr_interest(
84+
principal: f64,
85+
nominal_annual_percentage_rate: f64,
86+
number_of_years: f64,
87+
) -> Result<f64, &'static str> {
88+
if principal <= 0.0 {
89+
return Err("principal must be > 0");
90+
}
91+
if nominal_annual_percentage_rate < 0.0 {
92+
return Err("nominal_annual_percentage_rate must be >= 0");
93+
}
94+
if number_of_years <= 0.0 {
95+
return Err("number_of_years must be > 0");
96+
}
97+
// Divide annual rate by 365 to obtain the daily rate
98+
// Multiply years by 365 to obtain the total number of daily compounding periods
99+
compound_interest(
100+
principal,
101+
nominal_annual_percentage_rate / 365.0,
102+
number_of_years * 365.0,
103+
)
104+
}
105+
106+
#[cfg(test)]
107+
mod tests {
108+
use super::*;
109+
110+
#[test]
111+
fn test_simple_interest() {
112+
const EPSILON: f64 = 1e-9;
113+
114+
// Standard cases
115+
assert!((simple_interest(18000.0, 0.06, 3.0).unwrap() - 3240.0).abs() < EPSILON);
116+
assert!((simple_interest(0.5, 0.06, 3.0).unwrap() - 0.09).abs() < EPSILON);
117+
assert!((simple_interest(18000.0, 0.01, 10.0).unwrap() - 1800.0).abs() < EPSILON);
118+
assert!((simple_interest(5500.0, 0.01, 100.0).unwrap() - 5500.0).abs() < EPSILON);
119+
120+
// Zero interest rate yields zero interest
121+
assert!((simple_interest(18000.0, 0.0, 3.0).unwrap() - 0.0).abs() < EPSILON);
122+
123+
// Error cases
124+
assert_eq!(
125+
simple_interest(-10000.0, 0.06, 3.0),
126+
Err("principal must be > 0")
127+
);
128+
assert_eq!(
129+
simple_interest(0.0, 0.06, 3.0),
130+
Err("principal must be > 0")
131+
);
132+
assert_eq!(
133+
simple_interest(10000.0, -0.06, 3.0),
134+
Err("daily_interest_rate must be >= 0")
135+
);
136+
assert_eq!(
137+
simple_interest(5500.0, 0.01, -5.0),
138+
Err("days_between_payments must be > 0")
139+
);
140+
assert_eq!(
141+
simple_interest(5500.0, 0.01, 0.0),
142+
Err("days_between_payments must be > 0")
143+
);
144+
}
145+
146+
#[test]
147+
fn test_compound_interest() {
148+
const EPSILON: f64 = 1e-9;
149+
150+
// Standard cases
151+
assert!(
152+
(compound_interest(10000.0, 0.05, 3.0).unwrap() - 1_576.250_000_000_001_4).abs()
153+
< EPSILON
154+
);
155+
assert!(
156+
(compound_interest(10000.0, 0.05, 1.0).unwrap() - 500.000_000_000_000_45).abs()
157+
< EPSILON
158+
);
159+
assert!(
160+
(compound_interest(0.5, 0.05, 3.0).unwrap() - 0.078_812_500_000_000_06).abs() < EPSILON
161+
);
162+
163+
// Zero interest rate yields zero interest
164+
assert!((compound_interest(10000.0, 0.0, 5.0).unwrap() - 0.0).abs() < EPSILON);
165+
166+
// Error cases
167+
assert_eq!(
168+
compound_interest(-5500.0, 0.01, 5.0),
169+
Err("principal must be > 0")
170+
);
171+
assert_eq!(
172+
compound_interest(10000.0, -3.5, 3.0),
173+
Err("nominal_annual_interest_rate must be >= 0")
174+
);
175+
assert_eq!(
176+
compound_interest(10000.0, 0.06, -4.0),
177+
Err("number_of_compounding_periods must be > 0")
178+
);
179+
}
180+
181+
#[test]
182+
fn test_apr_interest() {
183+
const EPSILON: f64 = 1e-9;
184+
185+
// Standard cases
186+
assert!(
187+
(apr_interest(10000.0, 0.05, 3.0).unwrap() - 1_618.223_072_263_547).abs() < EPSILON
188+
);
189+
assert!(
190+
(apr_interest(10000.0, 0.05, 1.0).unwrap() - 512.674_964_674_473_2).abs() < EPSILON
191+
);
192+
assert!((apr_interest(0.5, 0.05, 3.0).unwrap() - 0.080_911_153_613_177_36).abs() < EPSILON);
193+
194+
// Zero interest rate yields zero interest
195+
assert!((apr_interest(10000.0, 0.0, 5.0).unwrap() - 0.0).abs() < EPSILON);
196+
197+
// Error cases
198+
assert_eq!(
199+
apr_interest(-5500.0, 0.01, 5.0),
200+
Err("principal must be > 0")
201+
);
202+
assert_eq!(
203+
apr_interest(10000.0, -3.5, 3.0),
204+
Err("nominal_annual_percentage_rate must be >= 0")
205+
);
206+
assert_eq!(
207+
apr_interest(10000.0, 0.06, -4.0),
208+
Err("number_of_years must be > 0")
209+
);
210+
}
211+
}

src/financial/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
1-
mod compound_interest;
21
mod equated_monthly_installments;
32
mod exponential_moving_average;
43
mod finance_ratios;
4+
mod interest;
55
mod npv;
66
mod npv_sensitivity;
77
mod payback;
88
mod present_value;
99
mod treynor_ratio;
1010

11-
pub use self::compound_interest::compound_interest;
1211
pub use self::equated_monthly_installments::equated_monthly_installments;
1312
pub use self::exponential_moving_average::exponential_moving_average;
1413
pub use self::finance_ratios::{
1514
debt_to_equity, earnings_per_sale, gross_profit_margin, return_on_investment,
1615
};
16+
pub use self::interest::{apr_interest, compound_interest, simple_interest};
1717
pub use self::npv::npv;
1818
pub use self::npv_sensitivity::npv_sensitivity;
1919
pub use self::payback::payback;

0 commit comments

Comments
 (0)