From 0f00ed23fec3d925e1b46322fc1e01050e754e1f Mon Sep 17 00:00:00 2001 From: omi Date: Sun, 26 Oct 2025 22:11:20 +0530 Subject: [PATCH 1/6] added rhumb line distance algorithm to navigation module --- DIRECTORY.md | 1 + src/navigation/mod.rs | 5 ++- src/navigation/rhumbline.rs | 85 +++++++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 src/navigation/rhumbline.rs diff --git a/DIRECTORY.md b/DIRECTORY.md index 7a800e41379..4024c1c8eb0 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -262,6 +262,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) diff --git a/src/navigation/mod.rs b/src/navigation/mod.rs index e62be90acbc..515396899c8 100644 --- a/src/navigation/mod.rs +++ b/src/navigation/mod.rs @@ -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; diff --git a/src/navigation/rhumbline.rs b/src/navigation/rhumbline.rs new file mode 100644 index 00000000000..3127b3d7dc7 --- /dev/null +++ b/src/navigation/rhumbline.rs @@ -0,0 +1,85 @@ +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 = match del_psi.abs() > 1e-12 { + true => del_phi / del_psi, + false => 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.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 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 = match del_psi.abs() > 1e-12 { + true => del_phi / del_psi, + false => 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); + } +} From bc8cf5128758e367446ad7b685c3ba66ee90e685 Mon Sep 17 00:00:00 2001 From: omi Date: Sun, 26 Oct 2025 22:24:42 +0530 Subject: [PATCH 2/6] fixing clippy warnings --- src/navigation/rhumbline.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/navigation/rhumbline.rs b/src/navigation/rhumbline.rs index 3127b3d7dc7..7524b15294f 100644 --- a/src/navigation/rhumbline.rs +++ b/src/navigation/rhumbline.rs @@ -15,9 +15,10 @@ pub fn rhumb_dist(lat1: f64, long1: f64, lat2: f64, long2: f64) -> f64 { } let del_psi = ((phi2 / 2.00 + PI / 4.00).tan() / (phi1 / 2.00 + PI / 4.00).tan()).ln(); - let q = match del_psi.abs() > 1e-12 { - true => del_phi / del_psi, - false => phi1.cos(), + 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 @@ -29,9 +30,9 @@ pub fn rhumb_bearing(lat1: f64, long1: f64, lat2: f64, long2: f64) -> f64 { let mut del_lambda = (long2 - long1) * PI / 180.00; if del_lambda > PI { - del_lambda -= 2.00 * PI; + del_lambda -= 2.0 * PI; } else if del_lambda < -PI { - del_lambda += 2.00 * PI; + del_lambda += 2.0 * PI; } let del_psi = ((phi2 / 2.00 + PI / 4.00).tan() / (phi1 / 2.00 + PI / 4.00).tan()).ln(); @@ -48,9 +49,10 @@ pub fn rhumb_destination(lat: f64, long: f64, distance: f64, bearing: f64) -> (f 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 = match del_psi.abs() > 1e-12 { - true => del_phi / del_psi, - false => phi1.cos(), + let q = if del_psi.abs() > 1e-12 { + del_phi / del_psi + } else { + phi1.cos() }; let del_lambda = del * theta.sin() / q; From 357c4541b1c8e261cea68b8272643888113943de Mon Sep 17 00:00:00 2001 From: omi Date: Sun, 26 Oct 2025 22:34:45 +0530 Subject: [PATCH 3/6] added additional tests for more coverage of edge cases (antimeridian crossing, equator distance etc.) --- src/navigation/rhumbline.rs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/navigation/rhumbline.rs b/src/navigation/rhumbline.rs index 7524b15294f..7f5e14d7257 100644 --- a/src/navigation/rhumbline.rs +++ b/src/navigation/rhumbline.rs @@ -61,7 +61,7 @@ pub fn rhumb_destination(lat: f64, long: f64, distance: f64, bearing: f64) -> (f (phi2 * 180.00 / PI, lambda2 * 180.00 / PI) } -//TESTS +// TESTS #[cfg(test)] mod tests { use super::*; @@ -84,4 +84,26 @@ mod tests { 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); + } } From c433710a2d375accd0da1c6d24aeca55a7c3b353 Mon Sep 17 00:00:00 2001 From: omi Date: Sun, 26 Oct 2025 23:18:06 +0530 Subject: [PATCH 4/6] added Net Present Value Calculation, Compound Interest and Payback Period Calculation to financial module --- src/financial/compound_interest.rs | 24 +++++++++++++++++ src/financial/mod.rs | 7 +++++ src/financial/npv.rs | 42 ++++++++++++++++++++++++++++++ src/financial/payback.rs | 30 +++++++++++++++++++++ 4 files changed, 103 insertions(+) create mode 100644 src/financial/compound_interest.rs create mode 100644 src/financial/npv.rs create mode 100644 src/financial/payback.rs diff --git a/src/financial/compound_interest.rs b/src/financial/compound_interest.rs new file mode 100644 index 00000000000..d348078ddcc --- /dev/null +++ b/src/financial/compound_interest.rs @@ -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 + } +} diff --git a/src/financial/mod.rs b/src/financial/mod.rs index 89b36bfa5e0..0e0301b8555 100644 --- a/src/financial/mod.rs +++ b/src/financial/mod.rs @@ -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; diff --git a/src/financial/npv.rs b/src/financial/npv.rs new file mode 100644 index 00000000000..23e1c6d7981 --- /dev/null +++ b/src/financial/npv.rs @@ -0,0 +1,42 @@ +/// 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.00, 300.00, 400.00, -50.00]; + let rate = 0.10; + let result = npv(&cash_flows, rate); + assert!((result - 27.30).abs() < 0.05); //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); + } + // for empty entry + #[test] + fn test_npv_empty() { + let cash_flows: Vec = vec![]; + let rate = 0.05; + let result = npv(&cash_flows, rate); + assert_eq!(result, 0.0); + } +} diff --git a/src/financial/payback.rs b/src/financial/payback.rs new file mode 100644 index 00000000000..9a90b247f77 --- /dev/null +++ b/src/financial/payback.rs @@ -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 { + 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_period() { + let cash_flows = vec![-1000.0, 300.0, 400.0, 500.0]; + assert_eq!(payback_period(&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_period(&cash_flows), None); // never paid back + } +} From ff360775f31d546286e9894b3068c9d3ae200b87 Mon Sep 17 00:00:00 2001 From: omi Date: Sun, 26 Oct 2025 23:26:53 +0530 Subject: [PATCH 5/6] fixed test outputs --- src/financial/npv.rs | 8 +++++--- src/financial/payback.rs | 6 +++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/financial/npv.rs b/src/financial/npv.rs index 23e1c6d7981..d194fa302ff 100644 --- a/src/financial/npv.rs +++ b/src/financial/npv.rs @@ -18,10 +18,11 @@ mod tests { #[test] fn test_npv_basic() { - let cash_flows = vec![-1000.00, 300.00, 400.00, -50.00]; + let cash_flows = vec![-1000.0, 300.0, 400.0, -50.0]; let rate = 0.10; let result = npv(&cash_flows, rate); - assert!((result - 27.30).abs() < 0.05); //small margin of error + // Calculated value ≈ -434.25 + assert!((result - (-434.25)).abs() < 0.05); // Allow small margin of error } #[test] @@ -31,9 +32,10 @@ mod tests { let result = npv(&cash_flows, rate); assert!((result - 250.0).abs() < 0.05); } - // for empty entry + #[test] fn test_npv_empty() { + // For empty cash flows: NPV should be 0 let cash_flows: Vec = vec![]; let rate = 0.05; let result = npv(&cash_flows, rate); diff --git a/src/financial/payback.rs b/src/financial/payback.rs index 9a90b247f77..012e50c503a 100644 --- a/src/financial/payback.rs +++ b/src/financial/payback.rs @@ -17,14 +17,14 @@ mod tests { use super::*; #[test] - fn test_payback_period() { + fn test_payback() { let cash_flows = vec![-1000.0, 300.0, 400.0, 500.0]; - assert_eq!(payback_period(&cash_flows), Some(3)); // paid back in year 3 + 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_period(&cash_flows), None); // never paid back + assert_eq!(payback(&cash_flows), None); // never paid back } } From fa80fc45f09dab1ab03f55808accf665f9e669b6 Mon Sep 17 00:00:00 2001 From: omi Date: Sun, 26 Oct 2025 23:29:49 +0530 Subject: [PATCH 6/6] added all algorithms to DIRECTORY.md --- DIRECTORY.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/DIRECTORY.md b/DIRECTORY.md index 4024c1c8eb0..f3ebd28b4a3 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -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)