Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions DIRECTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@
* [Word Break](https://github.com/TheAlgorithms/Rust/blob/master/src/dynamic_programming/word_break.rs)
* Financial
* [Present Value](https://github.com/TheAlgorithms/Rust/blob/master/src/financial/present_value.rs)
* [Compound Interest](https://github.com/TheAlgorithms/Rust/blob/master/src/financial/compound_interest.rs)
* [Net Present Value](https://github.com/TheAlgorithms/Rust/blob/master/src/financial/npv.rs)
* [Payback Period](https://github.com/TheAlgorithms/Rust/blob/master/src/financial/payback.rs)

* General
* [Convex Hull](https://github.com/TheAlgorithms/Rust/blob/master/src/general/convex_hull.rs)
* [Fisher Yates Shuffle](https://github.com/TheAlgorithms/Rust/blob/master/src/general/fisher_yates_shuffle.rs)
Expand Down Expand Up @@ -262,6 +266,7 @@
* Navigation
* [Bearing](https://github.com/TheAlgorithms/Rust/blob/master/src/navigation/bearing.rs)
* [Haversine](https://github.com/TheAlgorithms/Rust/blob/master/src/navigation/haversine.rs)
* [Rhumbline](https://github.com/TheAlgorithms/Rust/blob/master/src/navigation/rhumbline.rs)
* Number Theory
* [Compute Totient](https://github.com/TheAlgorithms/Rust/blob/master/src/number_theory/compute_totient.rs)
* [Euler Totient](https://github.com/TheAlgorithms/Rust/blob/master/src/number_theory/euler_totient.rs)
Expand Down
24 changes: 24 additions & 0 deletions src/financial/compound_interest.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// compound interest is given by A = P(1+r/n)^nt
// where: A = Final Amount, P = Principal Amount, r = rate of interest,
// n = number of times interest is compounded per year and t = time (in years)

pub fn compound_interest(princpal: f64, rate: f64, comp_per_year: u32, years: f64) -> f64 {
let amount = princpal * (1.00 + rate / comp_per_year as f64).powf(comp_per_year as f64 * years);
return amount;
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_compound_interest() {
let principal = 1000.0;
let rate = 0.05; // 5% annual interest
let times_per_year = 4; // interest compounded quarterly
let years = 2.0; // 2 years tenure
let result = compound_interest(principal, rate, times_per_year, years);
assert!((result - 1104.486).abs() < 0.001); // expected value rounded up to 3 decimal
// places
}
}
7 changes: 7 additions & 0 deletions src/financial/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,9 @@
mod compound_interest;
mod npv;
mod payback;
mod present_value;

pub use compound_interest::compound_interest;
pub use npv::npv;
pub use payback::payback;
pub use present_value::present_value;
44 changes: 44 additions & 0 deletions src/financial/npv.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/// Calculates Net Present Value given a vector of cash flows and a discount rate.
/// cash_flows: Vector of f64 representing cash flows for each period.
/// rate: Discount rate as an f64 (e.g., 0.05 for 5%)

pub fn npv(cash_flows: &[f64], rate: f64) -> f64 {
cash_flows
.iter()
.enumerate()
.map(|(t, &cf)| cf / (1.00 + rate).powi(t as i32))
.sum()
}

// tests

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_npv_basic() {
let cash_flows = vec![-1000.0, 300.0, 400.0, -50.0];
let rate = 0.10;
let result = npv(&cash_flows, rate);
// Calculated value ≈ -434.25
assert!((result - (-434.25)).abs() < 0.05); // Allow small margin of error
}

#[test]
fn test_npv_zero_rate() {
let cash_flows = vec![100.0, 200.0, -50.0];
let rate = 0.0;
let result = npv(&cash_flows, rate);
assert!((result - 250.0).abs() < 0.05);
}

#[test]
fn test_npv_empty() {
// For empty cash flows: NPV should be 0
let cash_flows: Vec<f64> = vec![];
let rate = 0.05;
let result = npv(&cash_flows, rate);
assert_eq!(result, 0.0);
}
}
30 changes: 30 additions & 0 deletions src/financial/payback.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/// Returns the payback period in years
/// If investment is not paid back, returns None.

pub fn payback(cash_flow: &[f64]) -> Option<usize> {
let mut total = 0.00;
for (year, &cf) in cash_flow.iter().enumerate() {
total += cf;
if total >= 0.00 {
return Some(year);
}
}
None
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_payback() {
let cash_flows = vec![-1000.0, 300.0, 400.0, 500.0];
assert_eq!(payback(&cash_flows), Some(3)); // paid back in year 3
}

#[test]
fn test_no_payback() {
let cash_flows = vec![-1000.0, 100.0, 100.0, 100.0];
assert_eq!(payback(&cash_flows), None); // never paid back
}
}
5 changes: 4 additions & 1 deletion src/navigation/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
mod bearing;
mod haversine;

mod rhumbline;
pub use self::bearing::bearing;
pub use self::haversine::haversine;
pub use self::rhumbline::rhumb_bearing;
pub use self::rhumbline::rhumb_destination;
pub use self::rhumbline::rhumb_dist;
109 changes: 109 additions & 0 deletions src/navigation/rhumbline.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
use std::f64::consts::PI;

const EARTH_RADIUS: f64 = 6371000.0;

pub fn rhumb_dist(lat1: f64, long1: f64, lat2: f64, long2: f64) -> f64 {
let phi1 = lat1 * PI / 180.00;
let phi2 = lat2 * PI / 180.00;
let del_phi = phi2 - phi1;
let mut del_lambda = (long2 - long1) * PI / 180.00;

if del_lambda > PI {
del_lambda -= 2.00 * PI;
} else if del_lambda < -PI {
del_lambda += 2.00 * PI;
}

let del_psi = ((phi2 / 2.00 + PI / 4.00).tan() / (phi1 / 2.00 + PI / 4.00).tan()).ln();
let q = if del_psi.abs() > 1e-12 {
del_phi / del_psi
} else {
phi1.cos()
};

(del_phi.powf(2.00) + (q * del_lambda).powf(2.00)).sqrt() * EARTH_RADIUS
}

pub fn rhumb_bearing(lat1: f64, long1: f64, lat2: f64, long2: f64) -> f64 {
let phi1 = lat1 * PI / 180.00;
let phi2 = lat2 * PI / 180.00;
let mut del_lambda = (long2 - long1) * PI / 180.00;

if del_lambda > PI {
del_lambda -= 2.0 * PI;
} else if del_lambda < -PI {
del_lambda += 2.0 * PI;
}

let del_psi = ((phi2 / 2.00 + PI / 4.00).tan() / (phi1 / 2.00 + PI / 4.00).tan()).ln();
let bearing = del_lambda.atan2(del_psi) * 180.0 / PI;
(bearing + 360.00) % 360.00
}
pub fn rhumb_destination(lat: f64, long: f64, distance: f64, bearing: f64) -> (f64, f64) {
let del = distance / EARTH_RADIUS;
let phi1 = lat * PI / 180.00;
let lambda1 = long * PI / 180.00;
let theta = bearing * PI / 180.00;

let del_phi = del * theta.cos();
let phi2 = (phi1 + del_phi).clamp(-PI / 2.0, PI / 2.0);

let del_psi = ((phi2 / 2.00 + PI / 4.00).tan() / (phi1 / 2.0 + PI / 4.0).tan()).ln();
let q = if del_psi.abs() > 1e-12 {
del_phi / del_psi
} else {
phi1.cos()
};

let del_lambda = del * theta.sin() / q;
let lambda2 = lambda1 + del_lambda;

(phi2 * 180.00 / PI, lambda2 * 180.00 / PI)
}

// TESTS
#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_rhumb_distance() {
let distance = rhumb_dist(28.5416, 77.2006, 28.5457, 77.1928);
assert!(distance > 700.00 && distance < 1000.0);
}

#[test]
fn test_rhumb_bearing() {
let bearing = rhumb_bearing(28.5416, 77.2006, 28.5457, 77.1928);
assert!((bearing - 300.0).abs() < 5.0);
}

#[test]
fn test_rhumb_destination_point() {
let (lat, lng) = rhumb_destination(28.5457, 77.1928, 1000.00, 305.0);
assert!((lat - 28.550).abs() < 0.010);
assert!((lng - 77.1851).abs() < 0.010);
}
// edge cases

#[test]
fn test_rhumb_distance_cross_antimeridian() {
// Test when del_lambda > PI (line 12)
let distance = rhumb_dist(0.0, 170.0, 0.0, -170.0);
assert!(distance > 0.0);
}

#[test]
fn test_rhumb_distance_cross_antimeridian_negative() {
// Test when del_lambda < -PI (line 14)
let distance = rhumb_dist(0.0, -170.0, 0.0, 170.0);
assert!(distance > 0.0);
}

#[test]
fn test_rhumb_distance_to_equator() {
// Test when del_psi is near zero (line 21 - the else branch)
let distance = rhumb_dist(0.0, 0.0, 0.0, 1.0);
assert!(distance > 0.0);
}
}