use crate::FloatLike;
pub fn fv<T: FloatLike>(rate: T, nper: T, pmt: T, pv: Option<T>, due: Option<bool>) -> T {
let pv = pv.unwrap_or(T::zero());
let due = due.unwrap_or(false);
if rate.is_zero() {
return pmt * nper + pv;
}
let nth_power = (T::one() + rate).powf(nper);
let factor = (nth_power - T::one()) / rate;
let pv_grown = pv * nth_power;
let fv = if due {
pmt * factor * (T::one() + rate) + pv_grown
} else {
pmt * factor + pv_grown
};
-fv
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(not(feature = "std"))]
extern crate std;
#[cfg(not(feature = "std"))]
use std::assert;
#[test]
fn test_fv() {
struct TestCase {
rate: f64,
nper: f64,
pmt: f64,
pv: Option<f64>,
due: Option<bool>,
expected: f64,
description: &'static str,
}
impl TestCase {
fn new(
rate: f64,
nper: f64,
pmt: f64,
pv: Option<f64>,
due: Option<bool>,
expected: f64,
description: &'static str,
) -> TestCase {
TestCase {
rate,
nper,
pmt,
pv,
due,
expected,
description,
}
}
}
let cases = [
TestCase::new(
0.05,
10.0,
-100.0,
None,
None,
1257.78925,
"Standard case with 5% rate, 10 periods, and $100 pmt",
),
TestCase::new(
0.05,
10.0,
-100.0,
None,
Some(true),
1320.67872,
"Payment at the beg of period should result in higher future value",
),
TestCase::new(0.0, 10.0, -100.0, None, None, -1000.0, "Zero interest rate no growth"),
TestCase::new(
0.05,
10.0,
-100.0,
Some(1000.0),
None,
-371.10537,
"Initial investment should result in higher future value",
),
TestCase::new(
0.05,
10.0,
-100.0,
Some(-1000.0),
None,
2886.68388,
"Initial investment should result in higher future value",
),
];
for case in &cases {
let calculated_fv = fv(case.rate, case.nper, case.pmt, case.pv, case.due);
assert!(
(calculated_fv - case.expected).abs() < 1e-5,
"Failed on case: {}. Expected {}, got {}",
case.description,
case.expected,
calculated_fv
);
}
}
}