use crate::{ONE, ZERO};
use rust_decimal::prelude::*;
pub fn fv(rate: Decimal, nper: Decimal, pmt: Decimal, pv: Option<Decimal>, due: Option<bool>) -> Decimal {
let pv = pv.unwrap_or(ZERO);
let due = due.unwrap_or(false);
if rate == ZERO {
return pmt * nper + pv;
}
let nth_power = (ONE + rate).powd(nper);
let factor = (ONE - nth_power) / rate;
let pv_grown = pv * nth_power;
if due {
pmt * factor * (ONE + rate) + pv_grown
} else {
pmt * factor + pv_grown
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(not(feature = "std"))]
extern crate std;
use rust_decimal_macros::*;
#[cfg(not(feature = "std"))]
use std::assert;
#[cfg(not(feature = "std"))]
use std::prelude::v1::*;
#[test]
fn test_fv() {
struct TestCase {
rate: Decimal,
nper: Decimal,
pmt: Decimal,
pv: Option<Decimal>,
due: Option<bool>,
expected: Decimal,
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: Decimal::from_f64(rate).unwrap(),
nper: Decimal::from_f64(nper).unwrap(),
pmt: Decimal::from_f64(pmt).unwrap(),
pv: pv.map(Decimal::from_f64).unwrap_or(None),
due,
expected: Decimal::from_f64(expected).unwrap(),
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,
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() < dec!(1e-5),
"Failed on case: {}. Expected {}, got {}",
case.description,
case.expected,
calculated_fv
);
}
}
}