use crate::amort_dep_tax::AmortizationPeriod;
use crate::ZERO;
use rust_decimal::prelude::*;
#[cfg(feature = "std")]
pub fn amort_schedule(
rate: Decimal,
nper: u32,
principal: Decimal,
pmt: Decimal,
round: Option<(u32, RoundingStrategy)>,
) -> Vec<AmortizationPeriod> {
let mut periods = vec![AmortizationPeriod::default(); nper as usize];
amort_schedule_into(periods.as_mut_slice(), rate, principal, pmt, round);
periods
}
pub fn amort_schedule_into(
slice: &mut [AmortizationPeriod],
rate: Decimal,
principal: Decimal,
pmt: Decimal,
round: Option<(u32, RoundingStrategy)>,
) {
let pmt = if let Some((dp, rounding)) = round {
-pmt.round_dp_with_strategy(dp, rounding)
} else {
-pmt
};
let mut remaining_balance = principal;
for (period, item) in slice.iter_mut().enumerate() {
let mut interest_payment = remaining_balance * rate;
let mut principal_payment = pmt - interest_payment;
if let Some((dp, rounding)) = round {
principal_payment = principal_payment.round_dp_with_strategy(dp, rounding);
interest_payment = interest_payment.round_dp_with_strategy(dp, rounding);
}
remaining_balance -= principal_payment;
*item = AmortizationPeriod::new(
period as u32 + 1,
principal_payment,
interest_payment,
remaining_balance,
);
}
if round.is_some() {
let final_payment = slice.last_mut().unwrap();
final_payment.principal_payment += final_payment.remaining_balance;
final_payment.remaining_balance = ZERO;
}
}
#[cfg(test)]
mod tests {
use super::*;
use rust_decimal_macros::dec;
#[cfg(not(feature = "std"))]
extern crate std;
#[cfg(not(feature = "std"))]
use std::prelude::v1::*;
#[cfg(not(feature = "std"))]
use std::{assert_eq, println};
#[test]
fn test_amort_schedule_into() {
let rate = dec!(0.05) / dec!(12);
const NPER: u32 = 30 * 12;
let principal = dec!(250_000);
let pmt = crate::tvm::pmt(rate, Decimal::from_u32(NPER).unwrap(), principal, None, None);
println!("PMT: {}", pmt);
let mut schedule = [AmortizationPeriod::default(); NPER as usize];
amort_schedule_into(&mut schedule, rate, principal, pmt, None);
schedule.iter().for_each(|period| {
println!("{:?}", period);
});
assert_eq!(schedule.last().unwrap().remaining_balance.abs() < dec!(1e-20), true);
let mut schedule_round = [AmortizationPeriod::default(); NPER as usize];
amort_schedule_into(
&mut schedule_round,
rate,
principal,
pmt,
Some((2, RoundingStrategy::MidpointNearestEven)),
);
schedule_round.iter().for_each(|period| {
println!("{:?}", period);
});
let sec_last_elem = schedule_round.get(358).unwrap();
let last_elem = schedule_round.last().unwrap();
assert_eq!(sec_last_elem.remaining_balance - last_elem.principal_payment, ZERO);
assert_eq!(last_elem.remaining_balance, ZERO);
}
}