use crate::amort_dep_tax::DepreciationPeriod;
use crate::ZERO;
use rust_decimal::prelude::*;
#[cfg(feature = "std")]
pub fn sln(cost: Decimal, salvage: Decimal, life: u32) -> Vec<DepreciationPeriod> {
let mut periods = vec![DepreciationPeriod::default(); life as usize];
sln_into(periods.as_mut_slice(), cost, salvage);
periods
}
pub fn sln_into(slice: &mut [DepreciationPeriod], cost: Decimal, salvage: Decimal) {
let life = slice.len() as u32;
let depreciation_expense = (cost - salvage) / Decimal::from_u32(life).unwrap();
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(
cost: Decimal,
salvage: Decimal,
life: u32,
factor: Option<Decimal>,
round: Option<(u32, RoundingStrategy)>,
) -> Vec<DepreciationPeriod> {
let mut periods = vec![DepreciationPeriod::default(); life as usize];
db_into(periods.as_mut_slice(), cost, salvage, factor, round);
periods
}
pub fn db_into(
slice: &mut [DepreciationPeriod],
cost: Decimal,
salvage: Decimal,
factor: Option<Decimal>,
round: Option<(u32, RoundingStrategy)>,
) {
let factor = factor.unwrap_or(Decimal::TWO);
let life = slice.len() as u32;
let mut remain_bv = cost;
let mut accum_dep = ZERO;
for (period, item) in slice.iter_mut().enumerate() {
let mut dep_exp = factor * (cost - accum_dep) / Decimal::from_u32(life).unwrap();
if let Some((dp, rounding)) = round {
dep_exp = dep_exp.round_dp_with_strategy(dp, rounding);
}
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(
cost: Decimal,
salvage: Decimal,
life: u32,
round: Option<(u32, RoundingStrategy)>,
) -> Vec<DepreciationPeriod> {
let mut periods = vec![DepreciationPeriod::default(); life as usize];
syd_into(periods.as_mut_slice(), cost, salvage, round);
periods
}
pub fn syd_into(
slice: &mut [DepreciationPeriod],
cost: Decimal,
salvage: Decimal,
round: Option<(u32, RoundingStrategy)>,
) {
let life = slice.len() as u32;
let mut remain_bv = cost;
let mut accum_dep = ZERO;
let sum_of_years = Decimal::from_u32(life * (life + 1)).unwrap() / Decimal::TWO;
for (period, item) in slice.iter_mut().enumerate() {
let mut dep_exp = (cost - salvage) * Decimal::from_u32(life - (period as u32)).unwrap() / sum_of_years;
if let Some((dp, rounding)) = round {
dep_exp = dep_exp.round_dp_with_strategy(dp, rounding)
};
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(cost: Decimal, rates: &[Decimal]) -> Vec<DepreciationPeriod> {
let mut periods = vec![DepreciationPeriod::default(); rates.len()];
macrs_into(periods.as_mut_slice(), cost, rates);
periods
}
pub fn macrs_into(slice: &mut [DepreciationPeriod], cost: Decimal, rates: &[Decimal]) {
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::*;
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, vec};
#[test]
fn test_macrs() {
let cost = dec!(10_000);
let rates = vec![
dec!(0.20),
dec!(0.32),
dec!(0.1920),
dec!(0.1152),
dec!(0.1152),
dec!(0.0576),
];
const LIFE: u32 = 6;
let mut schedule: [DepreciationPeriod; LIFE as usize] = [DepreciationPeriod::default(); LIFE as usize];
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, dec!(2000));
assert_eq!(schedule[0].remaining_book_value, dec!(8000));
assert_eq!(schedule[5].depreciation_expense, dec!(576));
assert_eq!(schedule[5].remaining_book_value, dec!(0));
}
#[test]
fn test_syd() {
struct TestCase {
cost: Decimal,
salvage: Decimal,
life: u32,
round: Option<(u32, RoundingStrategy)>,
expected: Decimal,
}
impl TestCase {
fn new(cost: f64, salvage: f64, life: u32, round: Option<(u32, RoundingStrategy)>, expected: f64) -> Self {
Self {
cost: Decimal::from_f64(cost).unwrap(),
salvage: Decimal::from_f64(salvage).unwrap(),
life,
round,
expected: Decimal::from_f64(expected).unwrap(),
}
}
}
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, RoundingStrategy::MidpointNearestEven)),
533.33,
),
TestCase::new(
9_000.00,
1_500.00,
10,
Some((2, RoundingStrategy::MidpointNearestEven)),
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_eq!(schedule.last().unwrap().depreciation_expense, case.expected);
}
}
#[test]
fn test_db() {
struct TestCase {
cost: Decimal,
salvage: Decimal,
life: u32,
factor: Option<Decimal>,
round: Option<(u32, RoundingStrategy)>,
expected: Decimal,
}
impl TestCase {
fn new(
cost: f64,
salvage: f64,
life: u32,
factor: Option<f64>,
round: Option<(u32, RoundingStrategy)>,
expected: f64,
) -> Self {
Self {
cost: Decimal::from_f64(cost).unwrap(),
salvage: Decimal::from_f64(salvage).unwrap(),
life,
factor: factor.map(Decimal::from_f64).unwrap_or(None),
round,
expected: Decimal::from_f64(expected).unwrap(),
}
}
}
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, RoundingStrategy::MidpointNearestEven)),
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 = dec!(10_000);
let salvage = dec!(1_000);
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, dec!(1800));
assert_eq!(schedule[0].remaining_book_value, dec!(8200));
}
}