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}