formualizer_eval/builtins/financial/
depreciation.rs1use crate::args::ArgSchema;
4use crate::function::Function;
5use crate::traits::{ArgumentHandle, CalcValue, FunctionContext};
6use formualizer_common::{ExcelError, LiteralValue};
7use formualizer_macros::func_caps;
8
9fn coerce_num(arg: &ArgumentHandle) -> Result<f64, ExcelError> {
10 let v = arg.value()?.into_literal();
11 match v {
12 LiteralValue::Number(f) => Ok(f),
13 LiteralValue::Int(i) => Ok(i as f64),
14 LiteralValue::Boolean(b) => Ok(if b { 1.0 } else { 0.0 }),
15 LiteralValue::Empty => Ok(0.0),
16 LiteralValue::Error(e) => Err(e),
17 _ => Err(ExcelError::new_value()),
18 }
19}
20
21#[derive(Debug)]
24pub struct SlnFn;
25impl Function for SlnFn {
26 func_caps!(PURE);
27 fn name(&self) -> &'static str {
28 "SLN"
29 }
30 fn min_args(&self) -> usize {
31 3
32 }
33 fn arg_schema(&self) -> &'static [ArgSchema] {
34 use std::sync::LazyLock;
35 static SCHEMA: LazyLock<Vec<ArgSchema>> = LazyLock::new(|| {
36 vec![
37 ArgSchema::number_lenient_scalar(),
38 ArgSchema::number_lenient_scalar(),
39 ArgSchema::number_lenient_scalar(),
40 ]
41 });
42 &SCHEMA[..]
43 }
44 fn eval<'a, 'b, 'c>(
45 &self,
46 args: &'c [ArgumentHandle<'a, 'b>],
47 _ctx: &dyn FunctionContext<'b>,
48 ) -> Result<CalcValue<'b>, ExcelError> {
49 let cost = coerce_num(&args[0])?;
50 let salvage = coerce_num(&args[1])?;
51 let life = coerce_num(&args[2])?;
52
53 if life == 0.0 {
54 return Ok(CalcValue::Scalar(
55 LiteralValue::Error(ExcelError::new_div()),
56 ));
57 }
58
59 let depreciation = (cost - salvage) / life;
60 Ok(CalcValue::Scalar(LiteralValue::Number(depreciation)))
61 }
62}
63
64#[derive(Debug)]
67pub struct SydFn;
68impl Function for SydFn {
69 func_caps!(PURE);
70 fn name(&self) -> &'static str {
71 "SYD"
72 }
73 fn min_args(&self) -> usize {
74 4
75 }
76 fn arg_schema(&self) -> &'static [ArgSchema] {
77 use std::sync::LazyLock;
78 static SCHEMA: LazyLock<Vec<ArgSchema>> = LazyLock::new(|| {
79 vec![
80 ArgSchema::number_lenient_scalar(),
81 ArgSchema::number_lenient_scalar(),
82 ArgSchema::number_lenient_scalar(),
83 ArgSchema::number_lenient_scalar(),
84 ]
85 });
86 &SCHEMA[..]
87 }
88 fn eval<'a, 'b, 'c>(
89 &self,
90 args: &'c [ArgumentHandle<'a, 'b>],
91 _ctx: &dyn FunctionContext<'b>,
92 ) -> Result<CalcValue<'b>, ExcelError> {
93 let cost = coerce_num(&args[0])?;
94 let salvage = coerce_num(&args[1])?;
95 let life = coerce_num(&args[2])?;
96 let per = coerce_num(&args[3])?;
97
98 if life <= 0.0 || per <= 0.0 || per > life {
99 return Ok(CalcValue::Scalar(
100 LiteralValue::Error(ExcelError::new_num()),
101 ));
102 }
103
104 let sum_of_years = life * (life + 1.0) / 2.0;
106
107 let depreciation = (cost - salvage) * (life - per + 1.0) / sum_of_years;
109
110 Ok(CalcValue::Scalar(LiteralValue::Number(depreciation)))
111 }
112}
113
114#[derive(Debug)]
117pub struct DbFn;
118impl Function for DbFn {
119 func_caps!(PURE);
120 fn name(&self) -> &'static str {
121 "DB"
122 }
123 fn min_args(&self) -> usize {
124 4
125 }
126 fn variadic(&self) -> bool {
127 true
128 }
129 fn arg_schema(&self) -> &'static [ArgSchema] {
130 use std::sync::LazyLock;
131 static SCHEMA: LazyLock<Vec<ArgSchema>> = LazyLock::new(|| {
132 vec![
133 ArgSchema::number_lenient_scalar(),
134 ArgSchema::number_lenient_scalar(),
135 ArgSchema::number_lenient_scalar(),
136 ArgSchema::number_lenient_scalar(),
137 ArgSchema::number_lenient_scalar(),
138 ]
139 });
140 &SCHEMA[..]
141 }
142 fn eval<'a, 'b, 'c>(
143 &self,
144 args: &'c [ArgumentHandle<'a, 'b>],
145 _ctx: &dyn FunctionContext<'b>,
146 ) -> Result<CalcValue<'b>, ExcelError> {
147 let cost = coerce_num(&args[0])?;
148 let salvage = coerce_num(&args[1])?;
149 let life = coerce_num(&args[2])?;
150 let period = coerce_num(&args[3])?;
151 let month = if args.len() > 4 {
152 coerce_num(&args[4])?
153 } else {
154 12.0
155 };
156
157 if life <= 0.0 || period <= 0.0 || !(1.0..=12.0).contains(&month) {
158 return Ok(CalcValue::Scalar(
159 LiteralValue::Error(ExcelError::new_num()),
160 ));
161 }
162
163 let life_int = life.trunc() as i32;
164 let period_int = period.trunc() as i32;
165
166 if period_int < 1 || period_int > life_int + 1 {
167 return Ok(CalcValue::Scalar(
168 LiteralValue::Error(ExcelError::new_num()),
169 ));
170 }
171
172 let rate = if cost <= 0.0 || salvage <= 0.0 {
174 1.0
175 } else {
176 let r = 1.0 - (salvage / cost).powf(1.0 / life);
177 (r * 1000.0).round() / 1000.0
178 };
179
180 let mut total_depreciation = 0.0;
181 let value = cost;
182
183 for p in 1..=period_int {
184 let depreciation = if p == 1 {
185 value * rate * month / 12.0
187 } else if p == life_int + 1 {
188 (value - total_depreciation - salvage)
190 .max(0.0)
191 .min(value - total_depreciation)
192 } else {
193 (value - total_depreciation) * rate
194 };
195
196 if p == period_int {
197 return Ok(CalcValue::Scalar(LiteralValue::Number(depreciation)));
198 }
199
200 total_depreciation += depreciation;
201 }
202
203 Ok(CalcValue::Scalar(LiteralValue::Number(0.0)))
204 }
205}
206
207#[derive(Debug)]
220pub struct DdbFn;
221impl Function for DdbFn {
222 func_caps!(PURE);
223 fn name(&self) -> &'static str {
224 "DDB"
225 }
226 fn min_args(&self) -> usize {
227 4
228 }
229 fn variadic(&self) -> bool {
230 true
231 }
232 fn arg_schema(&self) -> &'static [ArgSchema] {
233 use std::sync::LazyLock;
234 static SCHEMA: LazyLock<Vec<ArgSchema>> = LazyLock::new(|| {
235 vec![
236 ArgSchema::number_lenient_scalar(),
237 ArgSchema::number_lenient_scalar(),
238 ArgSchema::number_lenient_scalar(),
239 ArgSchema::number_lenient_scalar(),
240 ArgSchema::number_lenient_scalar(),
241 ]
242 });
243 &SCHEMA[..]
244 }
245 fn eval<'a, 'b, 'c>(
246 &self,
247 args: &'c [ArgumentHandle<'a, 'b>],
248 _ctx: &dyn FunctionContext<'b>,
249 ) -> Result<CalcValue<'b>, ExcelError> {
250 let cost = coerce_num(&args[0])?;
251 let salvage = coerce_num(&args[1])?;
252 let life = coerce_num(&args[2])?;
253 let period = coerce_num(&args[3])?;
254 let factor = if args.len() > 4 {
255 coerce_num(&args[4])?
256 } else {
257 2.0
258 };
259
260 if cost < 0.0 || salvage < 0.0 || life <= 0.0 || period <= 0.0 || factor <= 0.0 {
261 return Ok(CalcValue::Scalar(
262 LiteralValue::Error(ExcelError::new_num()),
263 ));
264 }
265
266 if period > life {
267 return Ok(CalcValue::Scalar(
268 LiteralValue::Error(ExcelError::new_num()),
269 ));
270 }
271
272 let rate = factor / life;
273 let mut value = cost;
274 let mut depreciation = 0.0;
275
276 for p in 1..=(period.trunc() as i32) {
277 depreciation = value * rate;
278 if value - depreciation < salvage {
280 depreciation = (value - salvage).max(0.0);
281 }
282 value -= depreciation;
283 }
284
285 let frac = period.fract();
289 if frac > 0.0 {
290 let next_depreciation = value * rate;
291 let next_depreciation = if value - next_depreciation < salvage {
292 (value - salvage).max(0.0)
293 } else {
294 next_depreciation
295 };
296 depreciation = depreciation * (1.0 - frac) + next_depreciation * frac;
297 }
298
299 Ok(CalcValue::Scalar(LiteralValue::Number(depreciation)))
300 }
301}
302
303pub fn register_builtins() {
304 use std::sync::Arc;
305 crate::function_registry::register_function(Arc::new(SlnFn));
306 crate::function_registry::register_function(Arc::new(SydFn));
307 crate::function_registry::register_function(Arc::new(DbFn));
308 crate::function_registry::register_function(Arc::new(DdbFn));
309}