#![allow(unused_imports)]
use crate::cashflow::*;
use crate::future_value::future_value;
use crate::present_value::present_value;
pub fn present_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();
crate::util::error::require_rate_gt_minus_one(rate)?;
crate::util::error::require_money("annuity", pmt)?;
if periods == 0 {
return Err(crate::FinanceError::InvalidPeriod {
period: 0,
periods: 0,
message: "annuity requires at least one period",
});
}
let pv_ann = match (rate == 0.0, timing) {
(true, _) => -pmt * periods as f64,
(false, crate::PaymentTiming::EndOfPeriod) => {
-pmt * ((1.0 - (1.0 / (1.0 + rate)).powf(periods as f64)) / rate)
}
(false, crate::PaymentTiming::BeginningOfPeriod) => {
-pmt * (1.0 + rate) * ((1.0 - (1.0 / (1.0 + rate)).powf(periods as f64)) / rate)
}
};
if pv_ann.is_finite() {
Ok(pv_ann)
} else {
Err(crate::FinanceError::NonFinite {
field: "present_value_annuity",
value: pv_ann,
})
}
}
pub fn present_value_annuity_accumulator<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();
crate::util::error::require_rate_gt_minus_one(rate)?;
crate::util::error::require_money("annuity", pmt)?;
let mut pv_accumulator = match timing {
crate::PaymentTiming::BeginningOfPeriod => (1.0 + rate) * pmt,
crate::PaymentTiming::EndOfPeriod => 0.0,
};
for i in 1..=periods {
let present_value = present_value(rate, i as u32, pmt, false)?;
pv_accumulator += present_value;
}
if pv_accumulator.is_finite() {
Ok(pv_accumulator)
} else {
Err(crate::FinanceError::NonFinite {
field: "present_value_annuity",
value: pv_accumulator,
})
}
}
pub fn present_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 pv = present_value_annuity(rate, periods, annuity, timing)?;
let pvann_type = match timing {
crate::PaymentTiming::BeginningOfPeriod => CashflowVariable::PresentValueAnnuityDue,
crate::PaymentTiming::EndOfPeriod => CashflowVariable::PresentValueAnnuity,
};
let (formula, formula_symbolic) = match timing {
crate::PaymentTiming::EndOfPeriod => (
format!(
"-{} * ((1. - (1. / (1. + {})).powf({})) / {});",
annuity, rate, periods, rate
),
"-annuity * ((1. - (1. / (1. + rate)).powf(periods)) / rate);".to_string(),
),
crate::PaymentTiming::BeginningOfPeriod => (
format!(
"-{} * ((1. - (1. / (1. + {})).powf({})) / {}) * (1. + {});",
annuity, rate, periods, rate, rate
),
"-annuity * ((1. - (1. / (1. + rate)).powf(periods)) / rate) * (1. + rate);"
.to_string(),
),
};
let fv = future_value(rate, periods, pv, false)?;
Ok(CashflowSolution::new(
pvann_type,
rate,
periods,
pv,
fv,
due_at_beginning,
annuity,
&formula,
&formula_symbolic,
))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::*;
#[test]
fn test_present_value_annuity_1() {
let (rate, periods, annuity) = (0.034, 1, 500);
let pv = present_value_annuity(rate, periods, annuity, false).unwrap();
assert_eq!(-483.55899, (pv * 100000.).round() / 100000.);
}
#[test]
fn test_present_value_annuity_2() {
let (rate, periods, annuity) = (0.034, 400, 500);
let pv = present_value_annuity(rate, periods, annuity, false).unwrap();
assert_eq!(-14705.85948, (pv * 100000.).round() / 100000.);
}
#[test]
fn test_present_value_annuity_due_2() {
let (rate, periods, annuity) = (0.034, 400, 500);
let pv = present_value_annuity(rate, periods, annuity, true).unwrap();
assert_eq!(-15205.8587, (pv * 100000.).round() / 100000.);
}
#[test]
fn test_present_value_annuity_3() {
let (rate, periods, annuity) = (-0.034, 52, 500);
let pv = present_value_annuity(rate, periods, annuity, false).unwrap();
assert_eq!(-74_148.8399, (pv * 100000.).round() / 100000.);
}
#[test]
fn test_present_value_annuity_4() {
let (rate, periods, annuity) = (-0.999, 3, 500);
let pv = present_value_annuity(rate, periods, annuity, false).unwrap();
assert_eq!(-500_500_499_999.999, (pv * 1000.).round() / 1000.);
}
#[test]
fn test_present_value_annuity_due_4() {
let (rate, periods, annuity) = (-0.999, 3, 500);
let pv = present_value_annuity(rate, periods, annuity, true).unwrap();
assert_eq!(-500_500_499.999999, (pv * 1000000.).round() / 1000000.);
}
#[test]
fn test_present_value_annuity_5() {
let (rate, periods, annuity) = (0.00034, 2_800, 5_000_000);
let pv = present_value_annuity(rate, periods, annuity, false).unwrap();
assert_eq!(-9028959259.06, (pv * 100.).round() / 100.);
}
#[test]
fn test_present_value_annuity_payment_timing_parity() {
use crate::PaymentTiming;
let ordinary_bool = present_value_annuity(0.034, 10, 500, false).unwrap();
let ordinary_enum =
present_value_annuity(0.034, 10, 500, PaymentTiming::EndOfPeriod).unwrap();
assert_eq!(ordinary_bool, ordinary_enum);
let due_bool = present_value_annuity(0.034, 10, 500, true).unwrap();
let due_enum =
present_value_annuity(0.034, 10, 500, PaymentTiming::BeginningOfPeriod).unwrap();
assert_eq!(due_bool, due_enum);
assert!(due_enum.abs() > ordinary_enum.abs());
}
}