use super::{annuity_factor, compound_factor, validate_financial_params};
use crate::array::Array;
use crate::error::{NumRs2Error, Result};
#[allow(unused_imports)] use num_traits::{Float, One, Zero};
use std::fmt::Debug;
pub fn pv<T>(rate: T, nper: T, pmt: T, fv: T, when: i32) -> Result<T>
where
T: Float + Debug + Clone,
{
validate_financial_params(rate, nper, pmt, T::zero(), fv)?;
let when_factor = if when == 1 { T::one() + rate } else { T::one() };
if rate.is_zero() {
return Ok(-(fv + pmt * nper));
}
let compound = compound_factor(rate, nper);
let annuity = annuity_factor(rate, nper);
let pv_annuity = pmt * annuity * when_factor / compound;
let pv_lump_sum = fv / compound;
Ok(-(pv_annuity + pv_lump_sum))
}
pub fn pv_array<T>(
rate: &Array<T>,
nper: &Array<T>,
pmt: &Array<T>,
fv: &Array<T>,
when: i32,
) -> Result<Array<T>>
where
T: Float + Debug + Clone,
{
if rate.shape() != nper.shape() || rate.shape() != pmt.shape() || rate.shape() != fv.shape() {
return Err(NumRs2Error::DimensionMismatch(
"All input arrays must have the same shape".to_string(),
));
}
let rate_vec = rate.to_vec();
let nper_vec = nper.to_vec();
let pmt_vec = pmt.to_vec();
let fv_vec = fv.to_vec();
let mut result_vec = Vec::with_capacity(rate_vec.len());
for i in 0..rate_vec.len() {
let pv_result = pv(rate_vec[i], nper_vec[i], pmt_vec[i], fv_vec[i], when)?;
result_vec.push(pv_result);
}
Ok(Array::from_vec(result_vec).reshape(&rate.shape()))
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_pv_basic() {
let result = pv(0.05, 10.0, 100.0, 0.0, 0).expect("pv calculation should succeed");
assert_relative_eq!(result, -772.1734, epsilon = 1e-4);
}
#[test]
fn test_pv_with_future_value() {
let result = pv(0.05, 10.0, 100.0, 1000.0, 0).expect("pv calculation should succeed");
assert_relative_eq!(result, -1386.087, epsilon = 1e-3);
}
#[test]
fn test_pv_beginning_of_period() {
let result = pv(0.05, 10.0, 100.0, 0.0, 1).expect("pv calculation should succeed");
assert_relative_eq!(result, -810.782, epsilon = 1e-3);
}
#[test]
fn test_pv_zero_rate() {
let result = pv(0.0, 10.0, 100.0, 0.0, 0).expect("pv calculation should succeed");
assert_relative_eq!(result, -1000.0, epsilon = 1e-9);
}
#[test]
fn test_pv_array() {
let rates = Array::from_vec(vec![0.05, 0.06]);
let npers = Array::from_vec(vec![10.0, 15.0]);
let pmts = Array::from_vec(vec![100.0, 200.0]);
let fvs = Array::from_vec(vec![0.0, 0.0]);
let result =
pv_array(&rates, &npers, &pmts, &fvs, 0).expect("pv_array calculation should succeed");
assert_eq!(result.shape(), vec![2]);
let values = result.to_vec();
assert_relative_eq!(values[0], -772.1734, epsilon = 1e-4);
assert_relative_eq!(values[1], -1942.449798, epsilon = 1e-4);
}
}