use log::warn;
use super::tvm::*;
#[allow(unused_imports)]
use crate::{rate::*, periods::*, present_value::*};
pub fn future_value<T>(rate: f64, periods: u32, present_value: T, continuous_compounding: bool) -> f64
where T: Into<f64> + Copy
{
future_value_internal(rate, periods as f64, present_value.into(), continuous_compounding)
}
pub fn future_value_solution<T>(rate: f64, periods: u32, present_value: T, continuous_compounding: bool) -> TvmSolution
where T: Into<f64> + Copy
{
future_value_solution_internal(rate, periods as f64, present_value.into(), continuous_compounding)
}
pub fn future_value_schedule<T>(rates: &[f64], present_value: T) -> f64
where T: Into<f64> + Copy
{
let present_value= present_value.into();
let periods = rates.len();
for rate in rates {
check_future_value_parameters(*rate, periods as f64, present_value);
}
let mut future_value = -present_value;
for i in 0..periods {
future_value *= 1.0 + rates[i];
}
future_value
}
pub fn future_value_schedule_solution<T>(rates: &[f64], present_value: T) -> TvmScheduleSolution
where T: Into<f64> + Copy
{
let future_value = future_value_schedule(rates, present_value);
TvmScheduleSolution::new(TvmVariable::FutureValue, rates, present_value.into(), future_value)
}
pub(crate) fn future_value_internal(rate: f64, periods: f64, present_value: f64, continuous_compounding: bool) -> f64 {
check_future_value_parameters(rate, periods, present_value);
let future_value = if continuous_compounding {
-present_value * std::f64::consts::E.powf(rate * periods)
} else {
-present_value * (1.0 + rate).powf(periods)
};
assert!(future_value.is_finite());
future_value
}
pub(crate) fn future_value_solution_internal(rate: f64, periods: f64, present_value: f64, continuous_compounding: bool) -> TvmSolution {
let future_value = future_value_internal(rate, periods, present_value, continuous_compounding);
let (formula, symbolic_formula) = if continuous_compounding {
let formula = format!("{:.4} = {:.4} * {:.6}^({:.6} * {})", future_value, -present_value, std::f64::consts::E, rate, periods);
let symbolic_formula = "fv = -pv * e^(rt)";
(formula, symbolic_formula)
} else {
let rate_multiplier = 1.0 + rate;
assert!(rate_multiplier >= 0.0);
let formula = format!("{:.4} = {:.4} * ({:.6} ^ {})", future_value, -present_value, rate_multiplier, periods);
let symbolic_formula = "fv = -pv * (1 + r)^n";
(formula, symbolic_formula)
};
TvmSolution::new_fractional_periods(TvmVariable::FutureValue, continuous_compounding, rate, periods, present_value, future_value, &formula, symbolic_formula)
}
fn check_future_value_parameters(rate: f64, _periods: f64, present_value: f64) {
assert!(rate.is_finite(), "The rate must be finite (not NaN or infinity)");
assert!(rate >= -1.0, "The rate must be greater than or equal to -1.0 because a rate lower than -100% would mean the investment loses more than its full value in a period.");
if rate.abs() > 1. {
warn!("You provided a periodic rate ({}) greater than 1. Are you sure you expect a {}% return?", rate, rate * 100.0);
}
assert!(present_value.is_finite(), "The present value must be finite (not NaN or infinity)");
}
#[cfg(test)]
mod tests {
use super::*;
use crate::initialized_vector;
#[should_panic]
#[test]
fn test_future_value_error_rate_low() {
future_value(-101.0, 5, 250_000.00, false);
}
#[should_panic]
#[test]
fn test_future_value_solution_6() {
let rate_of_return = 1.0f64 / 0.0f64;
let periods = 6;
let present_value = 5_000.00;
let _should_panic = future_value_solution(rate_of_return, periods, present_value, false);
}
#[should_panic]
#[test]
fn test_future_value_solution_7() {
let rate_of_return = 0.03;
let periods = 6;
let present_value = 1.0f64 / 0.0f64;
let _should_panic = future_value_solution(rate_of_return, periods, present_value, false);
}
fn compare_to_excel (test_case: usize, r: f64, n: u32, pv: f64, fv_excel: f64, fv_manual_simple: f64, fv_manual_cont: f64) {
let display = false;
if display { println!("test_case = {}, r = {}, n = {}, pv = {}, fv_excel: {}, fv_manual_simple = {}, fv_manual_cont = {}", test_case, r, n, pv, fv_excel, fv_manual_simple, fv_manual_cont); }
assert_approx_equal!(fv_excel, fv_manual_simple);
let fv_calc_simple = future_value(r, n, pv, false);
if display { println!("fv_calc_simple = {}", fv_calc_simple) };
assert_approx_equal!(fv_excel, fv_calc_simple);
let fv_calc_cont = future_value(r, n, pv, true);
if display { println!("fv_calc_cont = {}", fv_calc_cont) };
assert_approx_equal!(fv_manual_cont, fv_calc_cont);
let ratio = fv_calc_cont / fv_calc_simple;
if display { println!("ratio = {}", ratio) };
assert!(ratio >= 1.0);
assert!(ratio < 2.0);
let solution = future_value_solution(r, n, pv, false);
if display { dbg!(&solution); }
solution.invariant();
assert!(solution.calculated_field().is_future_value());
assert_eq!(false, solution.continuous_compounding());
assert_approx_equal!(r, solution.rate());
assert_eq!(n, solution.periods());
assert_approx_equal!(n as f64, solution.fractional_periods());
assert_approx_equal!(pv, solution.present_value());
assert_approx_equal!(fv_excel, solution.future_value());
let solution = future_value_solution(r, n, pv, true);
if display { dbg!(&solution); }
solution.invariant();
assert!(solution.calculated_field().is_future_value());
assert!(solution.continuous_compounding());
assert_approx_equal!(r, solution.rate());
assert_eq!(n, solution.periods());
assert_approx_equal!(n as f64, solution.fractional_periods());
assert_approx_equal!(pv, solution.present_value());
assert_approx_equal!(fv_manual_cont, solution.future_value());
let rates = initialized_vector(n as usize, r);
let solution = future_value_schedule_solution(&rates, pv);
if display { dbg!(&solution); }
solution.invariant();
assert!(solution.calculated_field().is_future_value());
assert_eq!(n, solution.periods());
assert_approx_equal!(pv, solution.present_value());
assert_approx_equal!(fv_excel, solution.future_value());
}
#[test]
fn test_future_value_against_excel() {
compare_to_excel(1, 0.01f64, 90, 1f64, -2.44863267464848f64, -2.44863267464848f64, -2.45960311115695f64);
compare_to_excel(2, -0.01f64, 85, -1.5f64, 0.638385185082981f64, 0.638385185082981f64, 0.64112239792309f64);
compare_to_excel(3, 0f64, 80, 2.25f64, -2.25f64, -2.25f64, -2.25f64);
compare_to_excel(4, 0.05f64, 75, -3.375f64, 131.060314992675f64, 131.060314992675f64, 143.508651750212f64);
compare_to_excel(5, -0.05f64, 70, 5.0625f64, -0.139642432836174f64, -0.139642432836174f64, -0.152874253575487f64);
compare_to_excel(6, 0.01f64, 65, -7.59375f64, 14.499251769574f64, 14.499251769574f64, 14.5461381703243f64);
compare_to_excel(7, -0.01f64, 60, 11.390625f64, -6.23245612973226f64, -6.23245612973226f64, -6.25130754238352f64);
compare_to_excel(8, 0f64, 55, -17.0859375f64, 17.0859375f64, 17.0859375f64, 17.0859375f64);
compare_to_excel(9, 0.05f64, 50, 25.62890625f64, -293.896914040351f64, -293.896914040351f64, -312.223995610061f64);
compare_to_excel(10, -0.05f64, 45, -38.443359375f64, 3.82281753569715f64, 3.82281753569715f64, 4.05190026767808f64);
compare_to_excel(11, 0.01f64, 40, 57.6650390625f64, -85.8553853561044f64, -85.8553853561044f64, -86.0261294638861f64);
compare_to_excel(12, -0.01f64, 35, -86.49755859375f64, 60.8465082158636f64, 60.8465082158636f64, 60.9537993307622f64);
compare_to_excel(13, 0f64, 30, 129.746337890625f64, -129.746337890625f64, -129.746337890625f64, -129.746337890625f64);
compare_to_excel(14, 0.05f64, 25, -194.619506835937f64, 659.050728569279f64, 659.050728569279f64, 679.288825069511f64);
compare_to_excel(15, -0.05f64, 20, 291.929260253906f64, -104.652530140165f64, -104.652530140165f64, -107.3947731238f64);
compare_to_excel(16, 0.01f64, 15, -437.893890380859f64, 508.381212478371f64, 508.381212478371f64, 508.760116525988f64);
compare_to_excel(17, -0.01f64, 12, 656.840835571289f64, -582.213779775772f64, -582.213779775772f64, -582.56556073855f64);
compare_to_excel(18, 0f64, 10, -985.261253356933f64, 985.261253356933f64, 985.261253356933f64, 985.261253356933f64);
compare_to_excel(19, 0.05f64, 7, 1477.8918800354f64, -2079.54228903805f64, -2079.54228903805f64, -2097.22840728772f64);
compare_to_excel(20, -0.05f64, 5, -2216.8378200531f64, 1715.34684668614f64, 1715.34684668614f64, 1726.47503019966f64);
compare_to_excel(21, 0.01f64, 4, 3325.25673007965f64, -3460.27548760037f64, -3460.27548760037f64, -3460.96303162265f64);
compare_to_excel(22, -0.01f64, 3, -4987.88509511947f64, 4839.73991990933f64, 4839.73991990933f64, 4840.47081241187f64);
compare_to_excel(23, 0f64, 2, 7481.82764267921f64, -7481.82764267921f64, -7481.82764267921f64, -7481.82764267921f64);
compare_to_excel(24, 0.05f64, 1, -11222.7414640188f64, 11783.8785372198f64, 11783.8785372198f64, 11798.1437232237f64);
compare_to_excel(25, -0.05f64, 0, 16834.1121960282f64, -16834.1121960282f64, -16834.1121960282f64, -16834.1121960282f64);
}
}