rust_finprim/amort_dep_tax/
dep.rs

1use crate::amort_dep_tax::DepreciationPeriod;
2use crate::FloatLike;
3use crate::RoundingMode;
4
5#[cfg(feature = "std")]
6/// Straight Line Depreciation (SLN)
7///
8/// Calculates the depreciation schedule for an asset using the straight-line method.
9///
10/// # Feature
11/// This function requires the `std` feature to be enabled as it uses the `std::Vec`. `sln_into`
12/// can be used in a `no_std` environment as any allocation is done by the caller.
13///
14/// # Arguments
15/// * `cost` - The initial cost of the asset
16/// * `salvage` - The estimated salvage value of the asset at the end of its useful life
17/// * `life` - The number of periods over which the asset will be depreciated
18///
19///
20/// # Returns
21/// * A vector of `DepreciationPeriod` instances representing each period in the depreciation schedule.
22///
23/// # Examples
24/// * $10,000 asset, $1,000 salvage value, 5 year life
25/// ```
26/// use rust_finprim::amort_dep_tax::sln;
27///
28/// let cost = 10_000.0;
29/// let salvage = 1_000.0;
30/// let life = 5;
31/// let schedule = sln(cost, salvage, life);
32/// ```
33pub fn sln<T: FloatLike>(cost: T, salvage: T, life: u32) -> Vec<DepreciationPeriod<T>> {
34    let mut periods = vec![DepreciationPeriod::default(); life as usize];
35    sln_into(periods.as_mut_slice(), cost, salvage);
36    periods
37}
38
39/// Straight Line Depreciation (SLN) Into
40///
41/// Calculates the depreciation schedule for an asset using the straight-line method, mutating a
42/// "slice" of `DepreciationPeriod`.
43///
44/// # Arguments
45/// * `slice` - A mutable slice of `DepreciationPeriod` instances to be filled with the depreciation schedule.
46///
47/// **Warning**: The length of the slice should be as long as the life as the asset or there will
48/// be unexpected behavior.
49/// * `cost` - The initial cost of the asset
50/// * `salvage` - The estimated salvage value of the asset at the end of its useful life
51///
52/// # Examples
53/// * $10,000 asset, $1,000 salvage value, 5 year life
54/// ```
55/// use rust_finprim::amort_dep_tax::{DepreciationPeriod, sln_into};
56///
57/// let life = 5;
58/// let cost = 10_000.0;
59/// let salvage = 1_000.0;
60///
61/// let mut schedule = vec![DepreciationPeriod::default(); life as usize];
62/// sln_into(&mut schedule, cost, salvage);
63/// ```
64pub fn sln_into<T: FloatLike>(slice: &mut [DepreciationPeriod<T>], cost: T, salvage: T) {
65    let life = slice.len();
66    let depreciation_expense = (cost - salvage) / T::from_usize(life);
67
68    let mut remaining_book_value = cost;
69    for (period, item) in slice.iter_mut().enumerate() {
70        remaining_book_value -= depreciation_expense;
71        item.period = period as u32 + 1;
72        item.depreciation_expense = depreciation_expense;
73        item.remaining_book_value = remaining_book_value;
74    }
75}
76
77#[cfg(feature = "std")]
78/// Declining Balance Depreciation (DB)
79///
80/// Calculates the depreciation schedule for an asset using the declining balance method given a
81/// declining balance factor (e.g. double-declining balance).
82///
83/// # Feature
84/// This function requires the `std` feature to be enabled as it uses the `std::Vec`. `sln_into`
85/// can be used in a `no_std` environment as any allocation is done by the caller.
86///
87/// # Arguments
88/// * `cost` - The initial cost of the assert
89/// * `salvage` - The estimated salvage value of the asset at the end of its useful life
90/// * `life` - The number of periods over which the asset will be depreciated
91/// * `factor` (optional) - The factor by which the straight-line depreciation rate is multiplied (default is 2 for double-declining balance)
92/// * `round` (optional) - A tuple specifying the number of decimal places and a rounding strategy for the amounts `(dp, RoundingMode)`,
93/// default is no rounding of calculations. The final depreciation expense is adjusted to ensure the remaining book value is equal to the salvage value.
94///
95/// If rounding is enabled, the final period will be adjusted to "zero" out the remaining book
96/// value to the salvage value.
97///
98/// # Returns
99/// * A vector of `DepreciationPeriod` instances representing each period in the depreciation schedule.
100///
101/// # Examples
102/// * $10,000 asset, $1,000 salvage value, 5 year life
103/// ```
104/// use rust_finprim::amort_dep_tax::db;
105///
106/// let cost = 10_000.0;
107/// let salvage = 1_000.0;
108/// let life = 5;
109/// let schedule = db(cost, salvage, life, None, None);
110/// ```
111pub fn db<T: FloatLike>(
112    cost: T,
113    salvage: T,
114    life: u32,
115    factor: Option<T>,
116    round: Option<(u32, RoundingMode, T)>,
117) -> Vec<DepreciationPeriod<T>> {
118    let mut periods = vec![DepreciationPeriod::default(); life as usize];
119    db_into(periods.as_mut_slice(), cost, salvage, factor, round);
120    periods
121}
122
123/// Declining Balance Depreciation (DB) Into
124///
125/// Calculates the depreciation schedule for an asset using the declining balance method given a
126/// declining balance factor (e.g. double-declining balance), mutating a "slice" of DepreciationPeriod.
127///
128/// # Arguments
129/// * `slice` - A mutable slice of `DepreciationPeriod` instances to be filled with the depreciation schedule.
130///
131/// **Warning**: The length of the slice should be as long as the life as the asset or there will
132/// be unexpected behavior.
133/// * `cost` - The initial cost of the assert
134/// * `salvage` - The estimated salvage value of the asset at the end of its useful life
135/// * `factor` (optional) - The factor by which the straight-line depreciation rate is multiplied (default is 2 for double-declining balance)
136/// * `round` (optional) - A tuple specifying the number of decimal places and a rounding strategy for the amounts `(dp, RoundingMode)`,
137/// default is no rounding of calculations. The final depreciation expense is adjusted to ensure the remaining book value is equal to the salvage value.
138///
139/// If rounding is enabled, the final period will be adjusted to "zero" out the remaining book
140/// value to the salvage value.
141///
142/// # Examples
143/// * $10,000 asset, $1,000 salvage value, 5 year life
144/// ```
145/// use rust_finprim::amort_dep_tax::{DepreciationPeriod, db_into};
146///
147/// let life = 5;
148/// let cost = 10_000.0;
149/// let salvage = 1_000.0;
150///
151/// let mut schedule = vec![DepreciationPeriod::default(); life as usize];
152/// db_into(&mut schedule, cost, salvage, None, None);
153/// ```
154pub fn db_into<T: FloatLike>(
155    slice: &mut [DepreciationPeriod<T>],
156    cost: T,
157    salvage: T,
158    factor: Option<T>,
159    round: Option<(u32, RoundingMode, T)>,
160) {
161    let factor = factor.unwrap_or(T::two());
162    let life = slice.len();
163
164    let mut remain_bv = cost;
165    let mut accum_dep = T::zero();
166    for (period, item) in slice.iter_mut().enumerate() {
167        let mut dep_exp = factor * (cost - accum_dep) / T::from_usize(life);
168        if let Some((dp, rounding, epsilon)) = round {
169            dep_exp = dep_exp.round_with_mode(dp, rounding, epsilon);
170        }
171
172        if dep_exp > remain_bv - salvage {
173            dep_exp = remain_bv - salvage;
174        }
175        accum_dep += dep_exp;
176        remain_bv -= dep_exp;
177
178        item.period = period as u32 + 1;
179        item.depreciation_expense = dep_exp;
180        item.remaining_book_value = remain_bv;
181    }
182
183    if round.is_some() {
184        let last = slice.last_mut().unwrap();
185        last.depreciation_expense += last.remaining_book_value - salvage;
186        last.remaining_book_value = salvage;
187    }
188}
189
190#[cfg(feature = "std")]
191/// Sum of the Years Digits (SYD)
192///
193/// Calculates the depreciation schedule for an asset using the sum of the years' digits method.
194/// The sum of the years' digits method is an accelerated depreciation method that allocates
195/// more depreciation expense to the early years of an asset's life.
196///
197/// # Feature
198/// This function requires the `std` feature to be enabled as it uses the `std::Vec`. `syd_into`
199/// can be used in a `no_std` environment as any allocation is done by the caller.
200///
201/// # Arguments
202/// * `cost` - The initial cost of the asset
203/// * `salvage` - The estimated salvage value of the asset at the end of its useful life
204/// * `life` - The number of periods over which the asset will be depreciated
205/// * `round` (optional) - A tuple specifying the number of decimal places and a rounding strategy for the amounts `(dp, RoundingMode)`,
206/// default is no rounding of calculations. The final depreciation expense is adjusted to ensure the remaining book value is equal to the salvage value.
207///
208/// If rounding is enabled, the final period will be adjusted to "zero" out the remaining book value to the salvage value.
209///
210/// # Returns
211/// * A vector of `DepreciationPeriod` instances representing each period in the depreciation schedule.
212///
213/// # Examples
214/// * $10,000 asset, $1,000 salvage value, 5 year life
215/// ```
216/// use rust_finprim::amort_dep_tax::syd;
217///
218/// let cost = 10_000.0;
219/// let salvage = 1_000.0;
220/// let life = 5;
221/// let schedule = syd(cost, salvage, life, None);
222/// ```
223pub fn syd<T: FloatLike>(
224    cost: T,
225    salvage: T,
226    life: u32,
227    round: Option<(u32, RoundingMode, T)>,
228) -> Vec<DepreciationPeriod<T>> {
229    let mut periods = vec![DepreciationPeriod::default(); life as usize];
230    syd_into(periods.as_mut_slice(), cost, salvage, round);
231    periods
232}
233
234/// Sum of the Years Digits (SYD) Into
235///
236/// Calculates the depreciation schedule for an asset using the sum of the years' digits method.
237/// The sum of the years' digits method is an accelerated depreciation method that allocates
238/// more depreciation expense to the early years of an asset's life. Mutates a slice of
239/// `DepreciationPeriod`.
240///
241/// # Arguments
242/// * `slice` - A mutable slice of `DepreciationPeriod` instances to be filled with the depreciation schedule.
243///
244/// **Warning**: The length of the slice should be as long as the life as the asset or there will
245/// be unexpected behavior.
246/// * `salvage` - The estimated salvage value of the asset at the end of its useful life
247/// * `life` - The number of periods over which the asset will be depreciated
248/// * `round` (optional) - A tuple specifying the number of decimal places and a rounding strategy for the amounts `(dp, RoundingMode)`,
249/// default is no rounding of calculations. The final depreciation expense is adjusted to ensure the remaining book value is equal to the salvage value.
250///
251/// If rounding is enabled, the final period will be adjusted to "zero" out the remaining book value to the salvage value.
252///
253/// # Returns
254/// * A vector of `DepreciationPeriod` instances representing each period in the depreciation schedule.
255///
256/// # Examples
257/// * $10,000 asset, $1,000 salvage value, 5 year life
258/// ```
259/// use rust_finprim::amort_dep_tax::{DepreciationPeriod, syd_into};
260///
261/// let life = 5;
262/// let cost = 10_000.0;
263/// let salvage = 1_000.0;
264///
265/// let mut schedule = vec![DepreciationPeriod::default(); life as usize];
266/// syd_into(&mut schedule, cost, salvage, None);
267/// ```
268pub fn syd_into<T: FloatLike>(
269    slice: &mut [DepreciationPeriod<T>],
270    cost: T,
271    salvage: T,
272    round: Option<(u32, RoundingMode, T)>,
273) {
274    let life = slice.len();
275    let mut remain_bv = cost;
276    let mut accum_dep = T::zero();
277    let sum_of_years = T::from_usize(life * (life + 1)) / T::two();
278    for (period, item) in slice.iter_mut().enumerate() {
279        let mut dep_exp = (cost - salvage) * T::from_usize(life - (period)) / sum_of_years;
280        if let Some((dp, rounding, epsilon)) = round {
281            dep_exp = dep_exp.round_with_mode(dp, rounding, epsilon)
282        };
283
284        accum_dep += dep_exp;
285        remain_bv -= dep_exp;
286
287        item.period = period as u32 + 1;
288        item.depreciation_expense = dep_exp;
289        item.remaining_book_value = remain_bv;
290    }
291
292    if round.is_some() {
293        let last = slice.last_mut().unwrap();
294        last.depreciation_expense += last.remaining_book_value - salvage;
295        last.remaining_book_value = salvage;
296    }
297}
298
299#[cfg(feature = "std")]
300/// MACRS Deprectiation
301///
302/// Calculates the depreciation schedule for an asset using the Modified Accelerated Cost Recovery
303/// System (MACRS method). MACRS is a depreciation method allowed by the IRS for tax purposes.
304///
305/// # Feature
306/// This function requires the `std` feature to be enabled as it uses the `std::Vec`. `sln_into`
307/// can be used in a `no_std` environment as any allocation is done by the caller.
308///
309/// # Arguments
310/// * `cost` - The initial cost of the asset
311/// * `rates` - A slice representing the MACRS depreciation rates for all periods of the asset's
312/// life, starting with the first year (period 1) and ending with the last year (period 2). Rates
313/// for each period can be found in IRS Publication 946 or other tax resources. The rates should
314/// be in decimal form (e.g., 0.20 for 20%).
315///
316/// # Returns
317/// * A vector of `DepreciationPeriod` instances representing each period in the depreciation schedule.
318/// The length of the vector will be equal to the number of rates provided.
319///
320/// # Examples
321/// * $10,000 asset, MACRS rates for 5 year life
322/// ```
323/// use rust_finprim::amort_dep_tax::macrs;
324///
325/// let cost = 10_000.0;
326/// let rates = vec![
327///    0.20,
328///    0.32,
329///    0.1920,
330///    0.1152,
331///    0.1152,
332///    0.0576
333/// ];
334/// let schedule = macrs(cost, &rates);
335/// ```
336pub fn macrs<T: FloatLike>(cost: T, rates: &[T]) -> Vec<DepreciationPeriod<T>> {
337    let mut periods = vec![DepreciationPeriod::default(); rates.len()];
338    macrs_into(periods.as_mut_slice(), cost, rates);
339    periods
340}
341
342/// MACRS Deprectiation Into
343///
344/// Calculates the depreciation schedule for an asset using the Modified Accelerated Cost Recovery
345/// System (MACRS method). MACRS is a depreciation method allowed by the IRS for tax purposes.
346/// Mutates a slice of `DepreciationPeriod`.
347///
348/// # Arguments
349/// * `slice` - A mutable slice of `DepreciationPeriod` instances to be filled with the depreciation schedule.
350///
351/// **Warning**: The length of the slice should be as long as the life as the asset, in this case,
352/// that is as long as the number of rates provided. If the length of the slice is not equal to
353/// the number of rates, this will panic.
354/// * `cost` - The initial cost of the asset
355/// * `rates` - A slice representing the MACRS depreciation rates for all periods of the asset's
356/// life, starting with the first year (period 1) and ending with the last year (period 2). Rates
357/// for each period can be found in IRS Publication 946 or other tax resources. The rates should
358/// be in decimal form (e.g., 0.20 for 20%).
359///
360/// # Returns
361/// * A vector of `DepreciationPeriod` instances representing each period in the depreciation schedule.
362/// The length of the vector will be equal to the number of rates provided.
363///
364/// # Examples
365/// * $10,000 asset, MACRS rates for 5 year life
366/// ```
367/// use rust_finprim::amort_dep_tax::{DepreciationPeriod, macrs_into};
368///
369/// let cost = 10_000.0;
370/// let rates = vec![
371///    0.20,
372///    0.32,
373///    0.1920,
374///    0.1152,
375///    0.1152,
376///    0.0576
377/// ];
378/// let life = rates.len() as u32;
379/// let mut schedule = vec![DepreciationPeriod::default(); life as usize];
380/// macrs_into(&mut schedule, cost, &rates);
381/// ```
382pub fn macrs_into<T: FloatLike>(slice: &mut [DepreciationPeriod<T>], cost: T, rates: &[T]) {
383    if slice.len() != rates.len() {
384        panic!("Length of slice must be equal to the number of rates");
385    }
386    let mut remain_bv = cost;
387    for (period, &rate) in rates.iter().enumerate() {
388        let dep_exp = cost * rate;
389        remain_bv -= dep_exp;
390        let item = &mut slice[period];
391        item.period = period as u32 + 1;
392        item.depreciation_expense = dep_exp;
393        item.remaining_book_value = remain_bv;
394    }
395}
396
397// since the underlying logic is the same. Just the allocation is different.
398#[cfg(test)]
399#[cfg(feature = "std")]
400mod tests {
401    use super::*;
402
403    #[cfg(not(feature = "std"))]
404    extern crate std;
405    #[cfg(not(feature = "std"))]
406    use std::{assert_eq, println, vec};
407
408    #[test]
409    fn test_macrs() {
410        let cost = 10_000.0;
411        let rates = vec![0.20, 0.32, 0.1920, 0.1152, 0.1152, 0.0576];
412        const LIFE: usize = 6;
413        let mut schedule: [DepreciationPeriod<f64>; LIFE] = [DepreciationPeriod::default(); LIFE];
414        macrs_into(&mut schedule, cost, &rates);
415        schedule.iter().for_each(|period| println!("{:?}", period));
416        assert_eq!(schedule.len(), rates.len());
417        assert_eq!(schedule[0].depreciation_expense, 2000.0);
418        assert_eq!(schedule[0].remaining_book_value, 8000.0);
419        assert_eq!(schedule[5].depreciation_expense, 576.0);
420        assert_eq!(schedule[5].remaining_book_value, 0.0);
421    }
422
423    #[test]
424    fn test_syd() {
425        struct TestCase {
426            cost: f64,
427            salvage: f64,
428            life: u32,
429            round: Option<(u32, RoundingMode, f64)>,
430            expected: f64,
431        }
432
433        impl TestCase {
434            fn new(cost: f64, salvage: f64, life: u32, round: Option<(u32, RoundingMode)>, expected: f64) -> Self {
435                Self {
436                    cost,
437                    salvage,
438                    life,
439                    round: round.map(|(dp, mode)| (dp, mode, 1e-5)),
440                    expected,
441                }
442            }
443        }
444
445        let cases = [
446            TestCase::new(10_000.00, 1_000.00, 5, None, 600.00),
447            TestCase::new(9_000.00, 1_000.00, 5, Some((2, RoundingMode::HalfToEven)), 533.33),
448            TestCase::new(9_000.00, 1_500.00, 10, Some((2, RoundingMode::HalfToEven)), 136.36),
449        ];
450        for case in &cases {
451            let schedule = syd(case.cost, case.salvage, case.life, case.round);
452            schedule.iter().for_each(|period| println!("{:?}", period));
453            assert_eq!(schedule.len(), case.life as usize);
454            assert!((schedule.last().unwrap().depreciation_expense - case.expected).abs() < 1e-5);
455        }
456    }
457
458    #[test]
459    fn test_db() {
460        struct TestCase {
461            cost: f64,
462            salvage: f64,
463            life: u32,
464            factor: Option<f64>,
465            round: Option<(u32, RoundingMode, f64)>,
466            expected: f64,
467        }
468        impl TestCase {
469            fn new(
470                cost: f64,
471                salvage: f64,
472                life: u32,
473                factor: Option<f64>,
474                round: Option<(u32, RoundingMode)>,
475                expected: f64,
476            ) -> Self {
477                Self {
478                    cost,
479                    salvage,
480                    life,
481                    factor,
482                    round: round.map(|(dp, mode)| (dp, mode, 1e-5)),
483                    expected,
484                }
485            }
486        }
487
488        let cases = [
489            TestCase::new(4_000.00, 1_000.00, 5, None, None, 0.00),
490            TestCase::new(10_000.00, 1_000.00, 5, None, None, 296.00),
491            TestCase::new(10_000.00, 1_000.00, 10, None, None, 268.435456),
492            TestCase::new(
493                10_000.00,
494                1_000.00,
495                10,
496                None,
497                Some((2, RoundingMode::HalfToEven)),
498                342.18,
499            ),
500        ];
501        for case in &cases {
502            let schedule = db(case.cost, case.salvage, case.life, case.factor, case.round);
503            schedule.iter().for_each(|period| println!("{:?}", period));
504            assert_eq!(schedule.len(), case.life as usize);
505            assert_eq!(schedule.last().unwrap().depreciation_expense, case.expected);
506        }
507    }
508
509    #[test]
510    fn test_sln() {
511        let cost = 10_000.0;
512        let salvage = 1_000.0;
513        let life = 5;
514        let schedule = sln(cost, salvage, life);
515        schedule.iter().for_each(|period| println!("{:?}", period));
516        assert_eq!(schedule.len(), 5);
517        assert_eq!(schedule[0].depreciation_expense, 1800.0);
518        assert_eq!(schedule[0].remaining_book_value, 8200.0);
519    }
520}