#![allow(unused_imports)]
use crate::assert_approx_equal;
use crate::cashflow::*;
use crate::future_value::future_value;
use crate::present_value::present_value;
fn check_future_value_annuity_parameters(
rate: f64,
periods: u32,
cashflow: f64,
) -> crate::FinanceResult<()> {
crate::util::error::require_rate_gt_minus_one(rate)?;
crate::util::error::require_money("annuity", cashflow)?;
if periods == 0 {
return Err(crate::FinanceError::InvalidPeriod {
period: 0,
periods: 0,
message: "annuity requires at least one period",
});
}
Ok(())
}
pub fn future_value_annuity<T, D>(
rate: f64,
periods: u32,
annuity: T,
timing: D,
) -> crate::FinanceResult<f64>
where
T: Into<f64> + Copy,
D: Into<crate::PaymentTiming>,
{
let pmt = annuity.into();
let timing = timing.into();
check_future_value_annuity_parameters(rate, periods, pmt)?;
let fv_ann = match (rate == 0.0, timing) {
(true, _) => -pmt * periods as f64,
(false, crate::PaymentTiming::EndOfPeriod) => {
-pmt * ((1.0 + rate).powf(periods as f64) - 1.0) / rate
}
(false, crate::PaymentTiming::BeginningOfPeriod) => {
-pmt * (1.0 + rate) * ((1.0 + rate).powf(periods as f64) - 1.0) / rate
}
};
if fv_ann.is_finite() {
Ok(fv_ann)
} else {
Err(crate::FinanceError::NonFinite {
field: "future_value_annuity",
value: fv_ann,
})
}
}
pub fn future_value_annuity_solution<T, D>(
rate: f64,
periods: u32,
cashflow: T,
timing: D,
) -> crate::FinanceResult<CashflowSolution>
where
T: Into<f64> + Copy,
D: Into<crate::PaymentTiming>,
{
let annuity = cashflow.into();
let timing = timing.into();
let due_at_beginning = timing.is_beginning();
let fv = future_value_annuity(rate, periods, annuity, timing)?;
let fvann_type = match timing {
crate::PaymentTiming::BeginningOfPeriod => CashflowVariable::FutureValueAnnuityDue,
crate::PaymentTiming::EndOfPeriod => CashflowVariable::FutureValueAnnuity,
};
let (formula, formula_symbolic) = match timing {
crate::PaymentTiming::EndOfPeriod => (
format!(
"-{} * (((1. + {}).powf({}) - 1.) / {});",
annuity, rate, periods, rate
),
"-annuity * (((1. + rate).powf(periods) - 1.) / rate);".to_string(),
),
crate::PaymentTiming::BeginningOfPeriod => (
format!(
"-{} * (1. + {}) * (((1. + {}).powf({}) - 1.) / {});",
annuity, rate, rate, periods, rate
),
"-annuity * (1. + rate) * (((1. + rate).powf(periods) - 1.) / rate);".to_string(),
),
};
let pv = present_value(rate, periods, fv, false)?;
Ok(CashflowSolution::new(
fvann_type,
rate,
periods,
pv,
fv,
due_at_beginning,
annuity,
&formula,
&formula_symbolic,
))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::*;
#[test]
fn test_future_value_annuity() {
let rate = 0.034;
let periods = 10;
let annuity = 500;
let fv = future_value_annuity(rate, periods, annuity, false).unwrap();
assert_eq!(-5838.66016, (fv * 100000.).round() / 100000.);
}
#[test]
fn test_future_value_annuity_1() {
let rate = 0.034;
let periods = 1;
let annuity = 500;
let fv = future_value_annuity(rate, periods, annuity, false).unwrap();
assert_eq!(-500.0000, (fv * 100000.).round() / 100000.);
}
#[test]
fn test_future_value_annuity_2() {
let rate = 0.034;
let periods = 400;
let annuity = 500;
let fv = future_value_annuity(rate, periods, annuity, false).unwrap();
assert_eq!(-9455966284.4844600, (fv * 100000.).round() / 100000.);
}
#[test]
fn test_future_value_annuity_3() {
let rate = 0.989;
let periods = 8;
let annuity = 120_000;
let fv = future_value_annuity(rate, periods, annuity, false).unwrap();
assert_eq!(-29_599_651.75013, (fv * 100000.).round() / 100000.);
}
#[test]
fn test_future_value_annuity_4() {
let rate = 0.00009;
let periods = 780;
let annuity = 120_000;
let fv = future_value_annuity(rate, periods, annuity, false).unwrap();
assert_eq!(-96_959_087.75951, (fv * 100000.).round() / 100000.);
}
#[test]
fn test_future_value_annuity_5() {
let rate = -0.0314;
let periods = 10;
let annuity = 13_000;
let fv = future_value_annuity(rate, periods, annuity, false).unwrap();
assert_eq!(-113_087.68194, (fv * 100000.).round() / 100000.);
}
#[test]
fn test_future_value_annuity_6() {
let rate = -0.999;
let periods = 10;
let annuity = 13_000;
let fv = future_value_annuity(rate, periods, annuity, false).unwrap();
assert_eq!(-13_013.01301, (fv * 100000.).round() / 100000.);
}
#[test]
fn test_future_value_annuity_7() {
let rate = -0.999;
let periods = 780;
let annuity = 13_000;
let fv = future_value_annuity(rate, periods, annuity, false).unwrap();
assert_eq!(-13_013.01301, (fv * 100000.).round() / 100000.);
}
#[test]
fn test_future_value_annuity_payment_timing_parity() {
use crate::PaymentTiming;
let ordinary_bool = future_value_annuity(0.034, 10, 500, false).unwrap();
let ordinary_enum =
future_value_annuity(0.034, 10, 500, PaymentTiming::EndOfPeriod).unwrap();
assert_eq!(ordinary_bool, ordinary_enum);
let due_bool = future_value_annuity(0.034, 10, 500, true).unwrap();
let due_enum =
future_value_annuity(0.034, 10, 500, PaymentTiming::BeginningOfPeriod).unwrap();
assert_eq!(due_bool, due_enum);
assert!(due_enum.abs() > ordinary_enum.abs());
}
}