Skip to content

Commit d14a4e2

Browse files
feat: add equated monthly installments to financial (#1020)
1 parent 39c6978 commit d14a4e2

File tree

3 files changed

+102
-13
lines changed

3 files changed

+102
-13
lines changed

DIRECTORY.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,12 +149,13 @@
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-
* [Present Value](https://github.com/TheAlgorithms/Rust/blob/master/src/financial/present_value.rs)
152+
* [Compound Interest](https://github.com/TheAlgorithms/Rust/blob/master/src/financial/compound_interest.rs)
153+
* [Equated Monthly Installments](https://github.com/TheAlgorithms/Rust/blob/master/src/financial/equated_monthly_installments.rs)
154+
* [Finance Ratios](https://github.com/TheAlgorithms/Rust/blob/master/src/financial/finance_ratios.rs)
153155
* [Net Present Value](https://github.com/TheAlgorithms/Rust/blob/master/src/financial/npv.rs)
154156
* [NPV Sensitivity](https://github.com/TheAlgorithms/Rust/blob/master/src/financial/npv_sensitivity.rs)
155-
* [Compound Interest](https://github.com/TheAlgorithms/Rust/blob/master/src/financial/compound_interest.rs)
156157
* [Payback Period](https://github.com/TheAlgorithms/Rust/blob/master/src/financial/payback.rs)
157-
* [Finance Ratios](https://github.com/TheAlgorithms/Rust/blob/master/src/financial/finance_ratios.rs)
158+
* [Present Value](https://github.com/TheAlgorithms/Rust/blob/master/src/financial/present_value.rs)
158159
* [Treynor Ratio](https://github.com/TheAlgorithms/Rust/blob/master/src/financial/treynor_ratio.rs)
159160
* General
160161
* [Convex Hull](https://github.com/TheAlgorithms/Rust/blob/master/src/general/convex_hull.rs)
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
//! Calculates the Equated Monthly Installment (EMI) for a loan.
2+
//!
3+
//! Formula: A = p * r * (1 + r)^n / ((1 + r)^n - 1)
4+
//! where:
5+
//! - `p` is the principal
6+
//! - `r` is the monthly interest rate (annual rate / 12)
7+
//! - `n` is the total number of monthly payments (years * 12)
8+
//!
9+
//! Wikipedia Reference: https://en.wikipedia.org/wiki/Equated_monthly_installment
10+
11+
/// Computes the monthly EMI for a loan.
12+
///
13+
/// # Arguments
14+
/// * `principal` - The total amount borrowed (must be > 0)
15+
/// * `rate_per_annum` - Annual interest rate as a decimal, e.g. 0.12 for 12% (must be >= 0)
16+
/// * `years_to_repay` - Loan term in whole years (must be > 0)
17+
///
18+
/// # Errors
19+
/// Returns an `Err(&'static str)` if any argument is out of range.
20+
pub fn equated_monthly_installments(
21+
principal: f64,
22+
rate_per_annum: f64,
23+
years_to_repay: u32,
24+
) -> Result<f64, &'static str> {
25+
if principal <= 0.0 {
26+
return Err("Principal borrowed must be > 0");
27+
}
28+
if rate_per_annum < 0.0 {
29+
return Err("Rate of interest must be >= 0");
30+
}
31+
if years_to_repay == 0 {
32+
return Err("Years to repay must be an integer > 0");
33+
}
34+
35+
// Divide annual rate by 12 to obtain the monthly rate
36+
let rate_per_month = rate_per_annum / 12.0;
37+
38+
// Multiply years by 12 to obtain the total number of monthly payments
39+
let number_of_payments = f64::from(years_to_repay * 12);
40+
41+
// Handle the edge case where the interest rate is 0 (simple division)
42+
if rate_per_month == 0.0 {
43+
return Ok(principal / number_of_payments);
44+
}
45+
46+
let factor = (1.0 + rate_per_month).powf(number_of_payments);
47+
Ok(principal * rate_per_month * factor / (factor - 1.0))
48+
}
49+
50+
#[cfg(test)]
51+
mod tests {
52+
use super::*;
53+
54+
#[test]
55+
fn test_equated_monthly_installments() {
56+
const EPSILON: f64 = 1e-8;
57+
58+
// Standard cases
59+
let result = equated_monthly_installments(25000.0, 0.12, 3).unwrap();
60+
assert!((result - 830.357_745_321_279_3).abs() < EPSILON);
61+
62+
let result = equated_monthly_installments(25000.0, 0.12, 10).unwrap();
63+
assert!((result - 358.677_371_006_468_26).abs() < EPSILON);
64+
65+
// With 0% interest the EMI is simply principal / number_of_payments
66+
let result = equated_monthly_installments(12000.0, 0.0, 1).unwrap();
67+
assert!((result - 1000.0).abs() < EPSILON);
68+
69+
// Error cases
70+
assert_eq!(
71+
equated_monthly_installments(0.0, 0.12, 3),
72+
Err("Principal borrowed must be > 0")
73+
);
74+
assert_eq!(
75+
equated_monthly_installments(-5000.0, 0.12, 3),
76+
Err("Principal borrowed must be > 0")
77+
);
78+
assert_eq!(
79+
equated_monthly_installments(25000.0, -1.0, 3),
80+
Err("Rate of interest must be >= 0")
81+
);
82+
assert_eq!(
83+
equated_monthly_installments(25000.0, 0.12, 0),
84+
Err("Years to repay must be an integer > 0")
85+
);
86+
}
87+
}

src/financial/mod.rs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
mod compound_interest;
2+
mod equated_monthly_installments;
23
mod finance_ratios;
34
mod npv;
45
mod npv_sensitivity;
56
mod payback;
67
mod present_value;
78
mod treynor_ratio;
8-
pub use compound_interest::compound_interest;
9-
pub use npv::npv;
10-
pub use npv_sensitivity::npv_sensitivity;
11-
pub use payback::payback;
12-
pub use present_value::present_value;
13-
pub use treynor_ratio::treynor_ratio;
149

15-
pub use finance_ratios::debt_to_equity;
16-
pub use finance_ratios::earnings_per_sale;
17-
pub use finance_ratios::gross_profit_margin;
18-
pub use finance_ratios::return_on_investment;
10+
pub use self::compound_interest::compound_interest;
11+
pub use self::equated_monthly_installments::equated_monthly_installments;
12+
pub use self::finance_ratios::{
13+
debt_to_equity, earnings_per_sale, gross_profit_margin, return_on_investment,
14+
};
15+
pub use self::npv::npv;
16+
pub use self::npv_sensitivity::npv_sensitivity;
17+
pub use self::payback::payback;
18+
pub use self::present_value::present_value;
19+
pub use self::treynor_ratio::treynor_ratio;

0 commit comments

Comments
 (0)