use crate::amort_dep_tax::DepreciationPeriod;
use crate::FloatLike;
use crate::RoundingMode;
#[cfg(feature = "std")]
pub fn sln<T: FloatLike>(cost: T, salvage: T, life: u32) -> Vec<DepreciationPeriod<T>> {
let mut periods = vec![DepreciationPeriod::default(); life as usize];
sln_into(periods.as_mut_slice(), cost, salvage);
periods
}
pub fn sln_into<T: FloatLike>(slice: &mut [DepreciationPeriod<T>], cost: T, salvage: T) {
let life = slice.len();
let depreciation_expense = (cost - salvage) / T::from_usize(life);
let mut remaining_book_value = cost;
for (period, item) in slice.iter_mut().enumerate() {
remaining_book_value -= depreciation_expense;
item.period = period as u32 + 1;
item.depreciation_expense = depreciation_expense;
item.remaining_book_value = remaining_book_value;
}
}
#[cfg(feature = "std")]
pub fn db<T: FloatLike>(
cost: T,
salvage: T,
life: u32,
factor: Option<T>,
round: Option<(u32, RoundingMode, T)>,
) -> Vec<DepreciationPeriod<T>> {
let mut periods = vec![DepreciationPeriod::default(); life as usize];
db_into(periods.as_mut_slice(), cost, salvage, factor, round);
periods
}
pub fn db_into<T: FloatLike>(
slice: &mut [DepreciationPeriod<T>],
cost: T,
salvage: T,
factor: Option<T>,
round: Option<(u32, RoundingMode, T)>,
) {
let factor = factor.unwrap_or(T::two());
let life = slice.len();
let mut remain_bv = cost;
let mut accum_dep = T::zero();
for (period, item) in slice.iter_mut().enumerate() {
let mut dep_exp = factor * (cost - accum_dep) / T::from_usize(life);
if let Some((dp, rounding, epsilon)) = round {
dep_exp = dep_exp.round_with_mode(dp, rounding, epsilon);
}
if dep_exp > remain_bv - salvage {
dep_exp = remain_bv - salvage;
}
accum_dep += dep_exp;
remain_bv -= dep_exp;
item.period = period as u32 + 1;
item.depreciation_expense = dep_exp;
item.remaining_book_value = remain_bv;
}
if round.is_some() {
let last = slice.last_mut().unwrap();
last.depreciation_expense += last.remaining_book_value - salvage;
last.remaining_book_value = salvage;
}
}
#[cfg(feature = "std")]
pub fn syd<T: FloatLike>(
cost: T,
salvage: T,
life: u32,
round: Option<(u32, RoundingMode, T)>,
) -> Vec<DepreciationPeriod<T>> {
let mut periods = vec![DepreciationPeriod::default(); life as usize];
syd_into(periods.as_mut_slice(), cost, salvage, round);
periods
}
pub fn syd_into<T: FloatLike>(
slice: &mut [DepreciationPeriod<T>],
cost: T,
salvage: T,
round: Option<(u32, RoundingMode, T)>,
) {
let life = slice.len();
let mut remain_bv = cost;
let mut accum_dep = T::zero();
let sum_of_years = T::from_usize(life * (life + 1)) / T::two();
for (period, item) in slice.iter_mut().enumerate() {
let mut dep_exp = (cost - salvage) * T::from_usize(life - (period)) / sum_of_years;
if let Some((dp, rounding, epsilon)) = round {
dep_exp = dep_exp.round_with_mode(dp, rounding, epsilon)
};
accum_dep += dep_exp;
remain_bv -= dep_exp;
item.period = period as u32 + 1;
item.depreciation_expense = dep_exp;
item.remaining_book_value = remain_bv;
}
if round.is_some() {
let last = slice.last_mut().unwrap();
last.depreciation_expense += last.remaining_book_value - salvage;
last.remaining_book_value = salvage;
}
}
#[cfg(feature = "std")]
pub fn macrs<T: FloatLike>(cost: T, rates: &[T]) -> Vec<DepreciationPeriod<T>> {
let mut periods = vec![DepreciationPeriod::default(); rates.len()];
macrs_into(periods.as_mut_slice(), cost, rates);
periods
}
pub fn macrs_into<T: FloatLike>(slice: &mut [DepreciationPeriod<T>], cost: T, rates: &[T]) {
if slice.len() != rates.len() {
panic!("Length of slice must be equal to the number of rates");
}
let mut remain_bv = cost;
for (period, &rate) in rates.iter().enumerate() {
let dep_exp = cost * rate;
remain_bv -= dep_exp;
let item = &mut slice[period];
item.period = period as u32 + 1;
item.depreciation_expense = dep_exp;
item.remaining_book_value = remain_bv;
}
}
#[cfg(test)]
#[cfg(feature = "std")]
mod tests {
use super::*;
#[cfg(not(feature = "std"))]
extern crate std;
#[cfg(not(feature = "std"))]
use std::{assert_eq, println, vec};
#[test]
fn test_macrs() {
let cost = 10_000.0;
let rates = vec![0.20, 0.32, 0.1920, 0.1152, 0.1152, 0.0576];
const LIFE: usize = 6;
let mut schedule: [DepreciationPeriod<f64>; LIFE] = [DepreciationPeriod::default(); LIFE];
macrs_into(&mut schedule, cost, &rates);
schedule.iter().for_each(|period| println!("{:?}", period));
assert_eq!(schedule.len(), rates.len());
assert_eq!(schedule[0].depreciation_expense, 2000.0);
assert_eq!(schedule[0].remaining_book_value, 8000.0);
assert_eq!(schedule[5].depreciation_expense, 576.0);
assert_eq!(schedule[5].remaining_book_value, 0.0);
}
#[test]
fn test_syd() {
struct TestCase {
cost: f64,
salvage: f64,
life: u32,
round: Option<(u32, RoundingMode, f64)>,
expected: f64,
}
impl TestCase {
fn new(cost: f64, salvage: f64, life: u32, round: Option<(u32, RoundingMode)>, expected: f64) -> Self {
Self {
cost,
salvage,
life,
round: round.map(|(dp, mode)| (dp, mode, 1e-5)),
expected,
}
}
}
let cases = [
TestCase::new(10_000.00, 1_000.00, 5, None, 600.00),
TestCase::new(9_000.00, 1_000.00, 5, Some((2, RoundingMode::HalfToEven)), 533.33),
TestCase::new(9_000.00, 1_500.00, 10, Some((2, RoundingMode::HalfToEven)), 136.36),
];
for case in &cases {
let schedule = syd(case.cost, case.salvage, case.life, case.round);
schedule.iter().for_each(|period| println!("{:?}", period));
assert_eq!(schedule.len(), case.life as usize);
assert!((schedule.last().unwrap().depreciation_expense - case.expected).abs() < 1e-5);
}
}
#[test]
fn test_db() {
struct TestCase {
cost: f64,
salvage: f64,
life: u32,
factor: Option<f64>,
round: Option<(u32, RoundingMode, f64)>,
expected: f64,
}
impl TestCase {
fn new(
cost: f64,
salvage: f64,
life: u32,
factor: Option<f64>,
round: Option<(u32, RoundingMode)>,
expected: f64,
) -> Self {
Self {
cost,
salvage,
life,
factor,
round: round.map(|(dp, mode)| (dp, mode, 1e-5)),
expected,
}
}
}
let cases = [
TestCase::new(4_000.00, 1_000.00, 5, None, None, 0.00),
TestCase::new(10_000.00, 1_000.00, 5, None, None, 296.00),
TestCase::new(10_000.00, 1_000.00, 10, None, None, 268.435456),
TestCase::new(
10_000.00,
1_000.00,
10,
None,
Some((2, RoundingMode::HalfToEven)),
342.18,
),
];
for case in &cases {
let schedule = db(case.cost, case.salvage, case.life, case.factor, case.round);
schedule.iter().for_each(|period| println!("{:?}", period));
assert_eq!(schedule.len(), case.life as usize);
assert_eq!(schedule.last().unwrap().depreciation_expense, case.expected);
}
}
#[test]
fn test_sln() {
let cost = 10_000.0;
let salvage = 1_000.0;
let life = 5;
let schedule = sln(cost, salvage, life);
schedule.iter().for_each(|period| println!("{:?}", period));
assert_eq!(schedule.len(), 5);
assert_eq!(schedule[0].depreciation_expense, 1800.0);
assert_eq!(schedule[0].remaining_book_value, 8200.0);
}
}