1use crate::cell::CellValue;
6use crate::error::Result;
7use crate::formula::ast::Expr;
8use crate::formula::eval::{coerce_to_number, Evaluator};
9use crate::formula::functions::check_arg_count;
10
11pub fn fn_fv(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
13 check_arg_count("FV", args, 3, 5)?;
14 let rate = coerce_to_number(&ctx.eval_expr(&args[0])?)?;
15 let nper = coerce_to_number(&ctx.eval_expr(&args[1])?)?;
16 let pmt = coerce_to_number(&ctx.eval_expr(&args[2])?)?;
17 let pv = if args.len() > 3 {
18 coerce_to_number(&ctx.eval_expr(&args[3])?)?
19 } else {
20 0.0
21 };
22 let pmt_type = if args.len() > 4 {
23 coerce_to_number(&ctx.eval_expr(&args[4])?)? as i32
24 } else {
25 0
26 };
27 let fv = calc_fv(rate, nper, pmt, pv, pmt_type);
28 Ok(CellValue::Number(fv))
29}
30
31pub fn fn_pv(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
33 check_arg_count("PV", args, 3, 5)?;
34 let rate = coerce_to_number(&ctx.eval_expr(&args[0])?)?;
35 let nper = coerce_to_number(&ctx.eval_expr(&args[1])?)?;
36 let pmt = coerce_to_number(&ctx.eval_expr(&args[2])?)?;
37 let fv = if args.len() > 3 {
38 coerce_to_number(&ctx.eval_expr(&args[3])?)?
39 } else {
40 0.0
41 };
42 let pmt_type = if args.len() > 4 {
43 coerce_to_number(&ctx.eval_expr(&args[4])?)? as i32
44 } else {
45 0
46 };
47 let pv = calc_pv(rate, nper, pmt, fv, pmt_type);
48 Ok(CellValue::Number(pv))
49}
50
51pub fn fn_npv(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
53 check_arg_count("NPV", args, 2, 255)?;
54 let rate = coerce_to_number(&ctx.eval_expr(&args[0])?)?;
55 let values = ctx.collect_numbers(&args[1..])?;
56 let mut npv = 0.0;
57 for (i, v) in values.iter().enumerate() {
58 npv += v / (1.0 + rate).powi(i as i32 + 1);
59 }
60 Ok(CellValue::Number(npv))
61}
62
63pub fn fn_irr(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
65 check_arg_count("IRR", args, 1, 2)?;
66 let values = ctx.collect_numbers(&args[0..1])?;
67 let guess = if args.len() > 1 {
68 coerce_to_number(&ctx.eval_expr(&args[1])?)?
69 } else {
70 0.1
71 };
72 match calc_irr(&values, guess) {
73 Some(irr) => Ok(CellValue::Number(irr)),
74 None => Ok(CellValue::Error("#NUM!".to_string())),
75 }
76}
77
78pub fn fn_pmt(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
80 check_arg_count("PMT", args, 3, 5)?;
81 let rate = coerce_to_number(&ctx.eval_expr(&args[0])?)?;
82 let nper = coerce_to_number(&ctx.eval_expr(&args[1])?)?;
83 let pv = coerce_to_number(&ctx.eval_expr(&args[2])?)?;
84 let fv = if args.len() > 3 {
85 coerce_to_number(&ctx.eval_expr(&args[3])?)?
86 } else {
87 0.0
88 };
89 let pmt_type = if args.len() > 4 {
90 coerce_to_number(&ctx.eval_expr(&args[4])?)? as i32
91 } else {
92 0
93 };
94 let pmt = calc_pmt(rate, nper, pv, fv, pmt_type);
95 Ok(CellValue::Number(pmt))
96}
97
98pub fn fn_ipmt(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
100 check_arg_count("IPMT", args, 4, 6)?;
101 let rate = coerce_to_number(&ctx.eval_expr(&args[0])?)?;
102 let per = coerce_to_number(&ctx.eval_expr(&args[1])?)?;
103 let nper = coerce_to_number(&ctx.eval_expr(&args[2])?)?;
104 let pv = coerce_to_number(&ctx.eval_expr(&args[3])?)?;
105 let fv = if args.len() > 4 {
106 coerce_to_number(&ctx.eval_expr(&args[4])?)?
107 } else {
108 0.0
109 };
110 let pmt_type = if args.len() > 5 {
111 coerce_to_number(&ctx.eval_expr(&args[5])?)? as i32
112 } else {
113 0
114 };
115 if per < 1.0 || per > nper {
116 return Ok(CellValue::Error("#NUM!".to_string()));
117 }
118 let ipmt = calc_ipmt(rate, per, nper, pv, fv, pmt_type);
119 Ok(CellValue::Number(ipmt))
120}
121
122pub fn fn_ppmt(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
124 check_arg_count("PPMT", args, 4, 6)?;
125 let rate = coerce_to_number(&ctx.eval_expr(&args[0])?)?;
126 let per = coerce_to_number(&ctx.eval_expr(&args[1])?)?;
127 let nper = coerce_to_number(&ctx.eval_expr(&args[2])?)?;
128 let pv = coerce_to_number(&ctx.eval_expr(&args[3])?)?;
129 let fv = if args.len() > 4 {
130 coerce_to_number(&ctx.eval_expr(&args[4])?)?
131 } else {
132 0.0
133 };
134 let pmt_type = if args.len() > 5 {
135 coerce_to_number(&ctx.eval_expr(&args[5])?)? as i32
136 } else {
137 0
138 };
139 if per < 1.0 || per > nper {
140 return Ok(CellValue::Error("#NUM!".to_string()));
141 }
142 let pmt = calc_pmt(rate, nper, pv, fv, pmt_type);
143 let ipmt = calc_ipmt(rate, per, nper, pv, fv, pmt_type);
144 Ok(CellValue::Number(pmt - ipmt))
145}
146
147pub fn fn_rate(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
149 check_arg_count("RATE", args, 3, 6)?;
150 let nper = coerce_to_number(&ctx.eval_expr(&args[0])?)?;
151 let pmt = coerce_to_number(&ctx.eval_expr(&args[1])?)?;
152 let pv = coerce_to_number(&ctx.eval_expr(&args[2])?)?;
153 let fv = if args.len() > 3 {
154 coerce_to_number(&ctx.eval_expr(&args[3])?)?
155 } else {
156 0.0
157 };
158 let pmt_type = if args.len() > 4 {
159 coerce_to_number(&ctx.eval_expr(&args[4])?)? as i32
160 } else {
161 0
162 };
163 let guess = if args.len() > 5 {
164 coerce_to_number(&ctx.eval_expr(&args[5])?)?
165 } else {
166 0.1
167 };
168 match calc_rate(nper, pmt, pv, fv, pmt_type, guess) {
169 Some(rate) => Ok(CellValue::Number(rate)),
170 None => Ok(CellValue::Error("#NUM!".to_string())),
171 }
172}
173
174pub fn fn_nper(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
176 check_arg_count("NPER", args, 3, 5)?;
177 let rate = coerce_to_number(&ctx.eval_expr(&args[0])?)?;
178 let pmt = coerce_to_number(&ctx.eval_expr(&args[1])?)?;
179 let pv = coerce_to_number(&ctx.eval_expr(&args[2])?)?;
180 let fv = if args.len() > 3 {
181 coerce_to_number(&ctx.eval_expr(&args[3])?)?
182 } else {
183 0.0
184 };
185 let pmt_type = if args.len() > 4 {
186 coerce_to_number(&ctx.eval_expr(&args[4])?)? as i32
187 } else {
188 0
189 };
190 if rate == 0.0 {
191 if pmt == 0.0 {
192 return Ok(CellValue::Error("#NUM!".to_string()));
193 }
194 return Ok(CellValue::Number(-(pv + fv) / pmt));
195 }
196 let z = pmt * (1.0 + rate * pmt_type as f64) / rate;
197 let numer = -fv + z;
198 let denom = pv + z;
199 if numer <= 0.0 && denom <= 0.0 {
200 return Ok(CellValue::Number((numer / denom).ln() / (1.0 + rate).ln()));
201 }
202 if numer / denom <= 0.0 {
203 return Ok(CellValue::Error("#NUM!".to_string()));
204 }
205 Ok(CellValue::Number((numer / denom).ln() / (1.0 + rate).ln()))
206}
207
208pub fn fn_db(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
210 check_arg_count("DB", args, 4, 5)?;
211 let cost = coerce_to_number(&ctx.eval_expr(&args[0])?)?;
212 let salvage = coerce_to_number(&ctx.eval_expr(&args[1])?)?;
213 let life = coerce_to_number(&ctx.eval_expr(&args[2])?)? as i32;
214 let period = coerce_to_number(&ctx.eval_expr(&args[3])?)? as i32;
215 let month = if args.len() > 4 {
216 coerce_to_number(&ctx.eval_expr(&args[4])?)? as i32
217 } else {
218 12
219 };
220 if life <= 0
221 || period <= 0
222 || period > life + 1
223 || cost < 0.0
224 || salvage < 0.0
225 || !(1..=12).contains(&month)
226 {
227 return Ok(CellValue::Error("#NUM!".to_string()));
228 }
229 if cost == 0.0 {
230 return Ok(CellValue::Number(0.0));
231 }
232 let rate = (1.0 - (salvage / cost).powf(1.0 / life as f64)) * 1000.0;
233 let rate = rate.round() / 1000.0;
234 let mut total_depreciation = 0.0;
235 let first_year_dep = cost * rate * month as f64 / 12.0;
236 if period == 1 {
237 return Ok(CellValue::Number(first_year_dep));
238 }
239 total_depreciation += first_year_dep;
240 for p in 2..period {
241 let dep = (cost - total_depreciation) * rate;
242 total_depreciation += dep;
243 if p > life {
244 break;
245 }
246 }
247 if period == life + 1 {
248 let dep = (cost - total_depreciation) * rate * (12 - month) as f64 / 12.0;
249 return Ok(CellValue::Number(dep));
250 }
251 let dep = (cost - total_depreciation) * rate;
252 Ok(CellValue::Number(dep))
253}
254
255pub fn fn_ddb(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
257 check_arg_count("DDB", args, 4, 5)?;
258 let cost = coerce_to_number(&ctx.eval_expr(&args[0])?)?;
259 let salvage = coerce_to_number(&ctx.eval_expr(&args[1])?)?;
260 let life = coerce_to_number(&ctx.eval_expr(&args[2])?)?;
261 let period = coerce_to_number(&ctx.eval_expr(&args[3])?)?;
262 let factor = if args.len() > 4 {
263 coerce_to_number(&ctx.eval_expr(&args[4])?)?
264 } else {
265 2.0
266 };
267 if life <= 0.0 || period <= 0.0 || period > life || cost < 0.0 || salvage < 0.0 || factor <= 0.0
268 {
269 return Ok(CellValue::Error("#NUM!".to_string()));
270 }
271 let rate = factor / life;
272 let rate = rate.min(1.0);
273 let mut total_dep = 0.0;
274 for p in 1..=(period as i32) {
275 let current_value = cost - total_dep;
276 let mut dep = current_value * rate;
277 if current_value - dep < salvage {
278 dep = (current_value - salvage).max(0.0);
279 }
280 if p == period as i32 {
281 return Ok(CellValue::Number(dep));
282 }
283 total_dep += dep;
284 }
285 Ok(CellValue::Number(0.0))
286}
287
288pub fn fn_sln(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
290 check_arg_count("SLN", args, 3, 3)?;
291 let cost = coerce_to_number(&ctx.eval_expr(&args[0])?)?;
292 let salvage = coerce_to_number(&ctx.eval_expr(&args[1])?)?;
293 let life = coerce_to_number(&ctx.eval_expr(&args[2])?)?;
294 if life == 0.0 {
295 return Ok(CellValue::Error("#DIV/0!".to_string()));
296 }
297 Ok(CellValue::Number((cost - salvage) / life))
298}
299
300pub fn fn_syd(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
302 check_arg_count("SYD", args, 4, 4)?;
303 let cost = coerce_to_number(&ctx.eval_expr(&args[0])?)?;
304 let salvage = coerce_to_number(&ctx.eval_expr(&args[1])?)?;
305 let life = coerce_to_number(&ctx.eval_expr(&args[2])?)?;
306 let per = coerce_to_number(&ctx.eval_expr(&args[3])?)?;
307 if life <= 0.0 || per <= 0.0 || per > life {
308 return Ok(CellValue::Error("#NUM!".to_string()));
309 }
310 let sum_of_years = life * (life + 1.0) / 2.0;
311 let dep = (cost - salvage) * (life - per + 1.0) / sum_of_years;
312 Ok(CellValue::Number(dep))
313}
314
315pub fn fn_effect(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
317 check_arg_count("EFFECT", args, 2, 2)?;
318 let nominal = coerce_to_number(&ctx.eval_expr(&args[0])?)?;
319 let npery = coerce_to_number(&ctx.eval_expr(&args[1])?)? as i32;
320 if nominal <= 0.0 || npery < 1 {
321 return Ok(CellValue::Error("#NUM!".to_string()));
322 }
323 let result = (1.0 + nominal / npery as f64).powi(npery) - 1.0;
324 Ok(CellValue::Number(result))
325}
326
327pub fn fn_nominal(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
329 check_arg_count("NOMINAL", args, 2, 2)?;
330 let effect = coerce_to_number(&ctx.eval_expr(&args[0])?)?;
331 let npery = coerce_to_number(&ctx.eval_expr(&args[1])?)? as i32;
332 if effect <= 0.0 || npery < 1 {
333 return Ok(CellValue::Error("#NUM!".to_string()));
334 }
335 let result = npery as f64 * ((1.0 + effect).powf(1.0 / npery as f64) - 1.0);
336 Ok(CellValue::Number(result))
337}
338
339pub fn fn_dollarde(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
341 check_arg_count("DOLLARDE", args, 2, 2)?;
342 let fractional = coerce_to_number(&ctx.eval_expr(&args[0])?)?;
343 let fraction = coerce_to_number(&ctx.eval_expr(&args[1])?)? as i32;
344 if fraction < 1 {
345 return Ok(CellValue::Error("#NUM!".to_string()));
346 }
347 let sign = if fractional < 0.0 { -1.0 } else { 1.0 };
348 let abs_val = fractional.abs();
349 let int_part = abs_val.floor();
350 let frac_part = abs_val - int_part;
351 let digits = (fraction as f64).log10().ceil().max(1.0) as u32;
352 let power = 10f64.powi(digits as i32);
353 let frac_decimal = frac_part * power / fraction as f64;
354 Ok(CellValue::Number(sign * (int_part + frac_decimal)))
355}
356
357pub fn fn_dollarfr(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
359 check_arg_count("DOLLARFR", args, 2, 2)?;
360 let decimal = coerce_to_number(&ctx.eval_expr(&args[0])?)?;
361 let fraction = coerce_to_number(&ctx.eval_expr(&args[1])?)? as i32;
362 if fraction < 1 {
363 return Ok(CellValue::Error("#NUM!".to_string()));
364 }
365 let sign = if decimal < 0.0 { -1.0 } else { 1.0 };
366 let abs_val = decimal.abs();
367 let int_part = abs_val.floor();
368 let frac_part = abs_val - int_part;
369 let digits = (fraction as f64).log10().ceil().max(1.0) as u32;
370 let power = 10f64.powi(digits as i32);
371 let frac_result = frac_part * fraction as f64 / power;
372 Ok(CellValue::Number(sign * (int_part + frac_result)))
373}
374
375pub fn fn_cumipmt(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
377 check_arg_count("CUMIPMT", args, 6, 6)?;
378 let rate = coerce_to_number(&ctx.eval_expr(&args[0])?)?;
379 let nper = coerce_to_number(&ctx.eval_expr(&args[1])?)?;
380 let pv = coerce_to_number(&ctx.eval_expr(&args[2])?)?;
381 let start = coerce_to_number(&ctx.eval_expr(&args[3])?)? as i32;
382 let end = coerce_to_number(&ctx.eval_expr(&args[4])?)? as i32;
383 let pmt_type = coerce_to_number(&ctx.eval_expr(&args[5])?)? as i32;
384 if rate <= 0.0
385 || nper <= 0.0
386 || pv <= 0.0
387 || start < 1
388 || end < 1
389 || start > end
390 || (pmt_type != 0 && pmt_type != 1)
391 {
392 return Ok(CellValue::Error("#NUM!".to_string()));
393 }
394 let mut total = 0.0;
395 for per in start..=end {
396 total += calc_ipmt(rate, per as f64, nper, pv, 0.0, pmt_type);
397 }
398 Ok(CellValue::Number(total))
399}
400
401pub fn fn_cumprinc(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
403 check_arg_count("CUMPRINC", args, 6, 6)?;
404 let rate = coerce_to_number(&ctx.eval_expr(&args[0])?)?;
405 let nper = coerce_to_number(&ctx.eval_expr(&args[1])?)?;
406 let pv = coerce_to_number(&ctx.eval_expr(&args[2])?)?;
407 let start = coerce_to_number(&ctx.eval_expr(&args[3])?)? as i32;
408 let end = coerce_to_number(&ctx.eval_expr(&args[4])?)? as i32;
409 let pmt_type = coerce_to_number(&ctx.eval_expr(&args[5])?)? as i32;
410 if rate <= 0.0
411 || nper <= 0.0
412 || pv <= 0.0
413 || start < 1
414 || end < 1
415 || start > end
416 || (pmt_type != 0 && pmt_type != 1)
417 {
418 return Ok(CellValue::Error("#NUM!".to_string()));
419 }
420 let pmt = calc_pmt(rate, nper, pv, 0.0, pmt_type);
421 let mut total = 0.0;
422 for per in start..=end {
423 let ipmt = calc_ipmt(rate, per as f64, nper, pv, 0.0, pmt_type);
424 total += pmt - ipmt;
425 }
426 Ok(CellValue::Number(total))
427}
428
429pub fn fn_xnpv(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
431 check_arg_count("XNPV", args, 3, 3)?;
432 let rate = coerce_to_number(&ctx.eval_expr(&args[0])?)?;
433 let values = ctx.collect_numbers(&args[1..2])?;
434 let dates = ctx.collect_numbers(&args[2..3])?;
435 if values.len() != dates.len() || values.is_empty() {
436 return Ok(CellValue::Error("#NUM!".to_string()));
437 }
438 if rate <= -1.0 {
439 return Ok(CellValue::Error("#NUM!".to_string()));
440 }
441 let d0 = dates[0];
442 let mut npv = 0.0;
443 for (i, v) in values.iter().enumerate() {
444 let days = dates[i] - d0;
445 npv += v / (1.0 + rate).powf(days / 365.0);
446 }
447 Ok(CellValue::Number(npv))
448}
449
450pub fn fn_xirr(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
452 check_arg_count("XIRR", args, 2, 3)?;
453 let values = ctx.collect_numbers(&args[0..1])?;
454 let dates = ctx.collect_numbers(&args[1..2])?;
455 let guess = if args.len() > 2 {
456 coerce_to_number(&ctx.eval_expr(&args[2])?)?
457 } else {
458 0.1
459 };
460 if values.len() != dates.len() || values.len() < 2 {
461 return Ok(CellValue::Error("#NUM!".to_string()));
462 }
463 let has_positive = values.iter().any(|v| *v > 0.0);
464 let has_negative = values.iter().any(|v| *v < 0.0);
465 if !has_positive || !has_negative {
466 return Ok(CellValue::Error("#NUM!".to_string()));
467 }
468 match calc_xirr(&values, &dates, guess) {
469 Some(irr) => Ok(CellValue::Number(irr)),
470 None => Ok(CellValue::Error("#NUM!".to_string())),
471 }
472}
473
474fn calc_fv(rate: f64, nper: f64, pmt: f64, pv: f64, pmt_type: i32) -> f64 {
475 if rate == 0.0 {
476 return -(pv + pmt * nper);
477 }
478 let pow = (1.0 + rate).powf(nper);
479 let factor = if pmt_type != 0 { 1.0 + rate } else { 1.0 };
480 -(pv * pow + pmt * factor * (pow - 1.0) / rate)
481}
482
483fn calc_pv(rate: f64, nper: f64, pmt: f64, fv: f64, pmt_type: i32) -> f64 {
484 if rate == 0.0 {
485 return -(fv + pmt * nper);
486 }
487 let pow = (1.0 + rate).powf(nper);
488 let factor = if pmt_type != 0 { 1.0 + rate } else { 1.0 };
489 -(fv + pmt * factor * (pow - 1.0) / rate) / pow
490}
491
492fn calc_pmt(rate: f64, nper: f64, pv: f64, fv: f64, pmt_type: i32) -> f64 {
493 if rate == 0.0 {
494 return -(pv + fv) / nper;
495 }
496 let pow = (1.0 + rate).powf(nper);
497 let factor = if pmt_type != 0 { 1.0 + rate } else { 1.0 };
498 -(pv * pow + fv) / (factor * (pow - 1.0) / rate)
499}
500
501fn calc_ipmt(rate: f64, per: f64, nper: f64, pv: f64, fv: f64, pmt_type: i32) -> f64 {
502 let pmt = calc_pmt(rate, nper, pv, fv, pmt_type);
503 if pmt_type != 0 {
504 let fv_prev = calc_fv(rate, per - 2.0, pmt, pv, pmt_type);
505 fv_prev * rate / (1.0 + rate)
506 } else {
507 let fv_prev = calc_fv(rate, per - 1.0, pmt, pv, pmt_type);
508 fv_prev * rate
509 }
510}
511
512fn calc_irr(values: &[f64], guess: f64) -> Option<f64> {
513 let has_positive = values.iter().any(|v| *v > 0.0);
514 let has_negative = values.iter().any(|v| *v < 0.0);
515 if !has_positive || !has_negative {
516 return None;
517 }
518 let mut rate = guess;
519 for _ in 0..100 {
520 let mut npv = 0.0;
521 let mut dnpv = 0.0;
522 for (i, v) in values.iter().enumerate() {
523 let denom = (1.0 + rate).powi(i as i32);
524 if denom == 0.0 {
525 return None;
526 }
527 npv += v / denom;
528 dnpv -= (i as f64) * v / (1.0 + rate).powi(i as i32 + 1);
529 }
530 if dnpv.abs() < 1e-15 {
531 return None;
532 }
533 let new_rate = rate - npv / dnpv;
534 if (new_rate - rate).abs() < 1e-10 {
535 return Some(new_rate);
536 }
537 rate = new_rate;
538 }
539 None
540}
541
542fn calc_rate(nper: f64, pmt: f64, pv: f64, fv: f64, pmt_type: i32, guess: f64) -> Option<f64> {
543 let mut rate = guess;
544 for _ in 0..100 {
545 if rate <= -1.0 {
546 return None;
547 }
548 let pow = (1.0 + rate).powf(nper);
549 let factor = if pmt_type != 0 { 1.0 + rate } else { 1.0 };
550 let y = pv * pow + pmt * factor * (pow - 1.0) / rate + fv;
551 let dy = pv * nper * (1.0 + rate).powf(nper - 1.0)
552 + pmt
553 * (factor * nper * (1.0 + rate).powf(nper - 1.0) / rate
554 - factor * (pow - 1.0) / (rate * rate)
555 + if pmt_type != 0 {
556 (pow - 1.0) / rate
557 } else {
558 0.0
559 });
560 if dy.abs() < 1e-15 {
561 return None;
562 }
563 let new_rate = rate - y / dy;
564 if (new_rate - rate).abs() < 1e-10 {
565 return Some(new_rate);
566 }
567 rate = new_rate;
568 }
569 None
570}
571
572fn calc_xirr(values: &[f64], dates: &[f64], guess: f64) -> Option<f64> {
573 let d0 = dates[0];
574 let mut rate = guess;
575 for _ in 0..100 {
576 let mut npv = 0.0;
577 let mut dnpv = 0.0;
578 for (i, v) in values.iter().enumerate() {
579 let days = (dates[i] - d0) / 365.0;
580 let denom = (1.0 + rate).powf(days);
581 if denom == 0.0 || !denom.is_finite() {
582 return None;
583 }
584 npv += v / denom;
585 dnpv -= days * v / ((1.0 + rate).powf(days + 1.0));
586 }
587 if dnpv.abs() < 1e-15 {
588 return None;
589 }
590 let new_rate = rate - npv / dnpv;
591 if (new_rate - rate).abs() < 1e-10 {
592 return Some(new_rate);
593 }
594 rate = new_rate;
595 }
596 None
597}
598
599#[cfg(test)]
600mod tests {
601 use crate::cell::CellValue;
602 use crate::formula::eval::{evaluate, CellSnapshot};
603 use crate::formula::parser::parse_formula;
604
605 fn eval_with_data(formula: &str, data: &[(&str, u32, u32, CellValue)]) -> CellValue {
606 let mut snap = CellSnapshot::new("Sheet1".to_string());
607 for (sheet, col, row, val) in data {
608 snap.set_cell(sheet, *col, *row, val.clone());
609 }
610 let expr = parse_formula(formula).unwrap();
611 evaluate(&expr, &snap).unwrap()
612 }
613
614 fn eval(formula: &str) -> CellValue {
615 eval_with_data(formula, &[])
616 }
617
618 fn assert_approx(result: CellValue, expected: f64, tol: f64) {
619 match result {
620 CellValue::Number(n) => {
621 assert!((n - expected).abs() < tol, "expected ~{expected}, got {n}");
622 }
623 other => panic!("expected number ~{expected}, got {other:?}"),
624 }
625 }
626
627 #[test]
628 fn fv_basic() {
629 assert_approx(eval("FV(0.06/12,10,-200,-500,1)"), 2581.4033740601, 0.01);
630 }
631
632 #[test]
633 fn fv_zero_rate() {
634 assert_approx(eval("FV(0,10,-200)"), 2000.0, 0.01);
635 }
636
637 #[test]
638 fn pv_basic() {
639 assert_approx(eval("PV(0.08/12,20*12,500,0,0)"), -59777.1458, 0.01);
640 }
641
642 #[test]
643 fn npv_basic() {
644 let data = vec![
645 ("Sheet1", 1, 1, CellValue::Number(-10000.0)),
646 ("Sheet1", 1, 2, CellValue::Number(3000.0)),
647 ("Sheet1", 1, 3, CellValue::Number(4200.0)),
648 ("Sheet1", 1, 4, CellValue::Number(6800.0)),
649 ];
650 assert_approx(eval_with_data("NPV(0.1,A1:A4)", &data), 1188.4434, 0.01);
651 }
652
653 #[test]
654 fn irr_basic() {
655 let data = vec![
656 ("Sheet1", 1, 1, CellValue::Number(-70000.0)),
657 ("Sheet1", 1, 2, CellValue::Number(12000.0)),
658 ("Sheet1", 1, 3, CellValue::Number(15000.0)),
659 ("Sheet1", 1, 4, CellValue::Number(18000.0)),
660 ("Sheet1", 1, 5, CellValue::Number(21000.0)),
661 ("Sheet1", 1, 6, CellValue::Number(26000.0)),
662 ];
663 assert_approx(eval_with_data("IRR(A1:A6)", &data), 0.08663, 0.001);
664 }
665
666 #[test]
667 fn irr_no_sign_change() {
668 let data = vec![
669 ("Sheet1", 1, 1, CellValue::Number(100.0)),
670 ("Sheet1", 1, 2, CellValue::Number(200.0)),
671 ];
672 assert_eq!(
673 eval_with_data("IRR(A1:A2)", &data),
674 CellValue::Error("#NUM!".to_string())
675 );
676 }
677
678 #[test]
679 fn pmt_basic() {
680 assert_approx(eval("PMT(0.08/12,10,10000)"), -1037.0321, 0.01);
681 }
682
683 #[test]
684 fn pmt_zero_rate() {
685 assert_approx(eval("PMT(0,10,10000)"), -1000.0, 0.01);
686 }
687
688 #[test]
689 fn ipmt_basic() {
690 assert_approx(eval("IPMT(0.1/12,1,36,8000)"), -66.6667, 0.01);
691 }
692
693 #[test]
694 fn ipmt_invalid_period() {
695 assert_eq!(
696 eval("IPMT(0.1,0,36,8000)"),
697 CellValue::Error("#NUM!".to_string())
698 );
699 }
700
701 #[test]
702 fn ppmt_basic() {
703 let pmt = eval("PMT(0.1/12,36,8000)");
704 let ipmt = eval("IPMT(0.1/12,1,36,8000)");
705 let ppmt = eval("PPMT(0.1/12,1,36,8000)");
706 if let (CellValue::Number(p), CellValue::Number(i), CellValue::Number(pp)) =
707 (pmt, ipmt, ppmt)
708 {
709 assert!((p - i - pp).abs() < 0.01);
710 } else {
711 panic!("expected numbers");
712 }
713 }
714
715 #[test]
716 fn rate_basic() {
717 assert_approx(eval("RATE(48,-200,8000)"), 0.007701, 0.0001);
718 }
719
720 #[test]
721 fn nper_basic() {
722 assert_approx(eval("NPER(0.01,-100,1000)"), 10.5886, 0.01);
723 }
724
725 #[test]
726 fn nper_zero_rate() {
727 assert_approx(eval("NPER(0,-100,1000)"), 10.0, 0.01);
728 }
729
730 #[test]
731 fn db_basic() {
732 assert_approx(eval("DB(1000000,100000,6,1,7)"), 186083.3333, 0.01);
733 }
734
735 #[test]
736 fn ddb_basic() {
737 assert_approx(eval("DDB(2400,300,10,1)"), 480.0, 0.01);
738 }
739
740 #[test]
741 fn ddb_later_period() {
742 assert_approx(eval("DDB(2400,300,10,2)"), 384.0, 0.01);
743 }
744
745 #[test]
746 fn sln_basic() {
747 assert_approx(eval("SLN(30000,7500,10)"), 2250.0, 0.01);
748 }
749
750 #[test]
751 fn sln_zero_life() {
752 assert_eq!(
753 eval("SLN(30000,7500,0)"),
754 CellValue::Error("#DIV/0!".to_string())
755 );
756 }
757
758 #[test]
759 fn syd_basic() {
760 assert_approx(eval("SYD(30000,7500,10,1)"), 4090.9091, 0.01);
761 }
762
763 #[test]
764 fn syd_last_period() {
765 assert_approx(eval("SYD(30000,7500,10,10)"), 409.0909, 0.01);
766 }
767
768 #[test]
769 fn effect_basic() {
770 assert_approx(eval("EFFECT(0.0525,4)"), 0.053543, 0.0001);
771 }
772
773 #[test]
774 fn effect_invalid() {
775 assert_eq!(
776 eval("EFFECT(-0.01,4)"),
777 CellValue::Error("#NUM!".to_string())
778 );
779 }
780
781 #[test]
782 fn nominal_basic() {
783 assert_approx(eval("NOMINAL(0.053543,4)"), 0.0525, 0.0001);
784 }
785
786 #[test]
787 fn dollarde_basic() {
788 assert_approx(eval("DOLLARDE(1.02,16)"), 1.125, 0.001);
789 }
790
791 #[test]
792 fn dollarfr_basic() {
793 assert_approx(eval("DOLLARFR(1.125,16)"), 1.02, 0.001);
794 }
795
796 #[test]
797 fn cumipmt_basic() {
798 assert_approx(eval("CUMIPMT(0.09/12,30*12,125000,1,1,0)"), -937.5, 0.01);
799 }
800
801 #[test]
802 fn cumprinc_basic() {
803 assert_approx(eval("CUMPRINC(0.09/12,30*12,125000,1,1,0)"), -68.2782, 0.01);
804 }
805
806 #[test]
807 fn xnpv_basic() {
808 let data = vec![
809 ("Sheet1", 1, 1, CellValue::Number(-10000.0)),
810 ("Sheet1", 1, 2, CellValue::Number(2750.0)),
811 ("Sheet1", 1, 3, CellValue::Number(4250.0)),
812 ("Sheet1", 1, 4, CellValue::Number(3250.0)),
813 ("Sheet1", 1, 5, CellValue::Number(2750.0)),
814 ("Sheet1", 2, 1, CellValue::Number(39448.0)),
815 ("Sheet1", 2, 2, CellValue::Number(39508.0)),
816 ("Sheet1", 2, 3, CellValue::Number(39600.0)),
817 ("Sheet1", 2, 4, CellValue::Number(39692.0)),
818 ("Sheet1", 2, 5, CellValue::Number(39783.0)),
819 ];
820 let result = eval_with_data("XNPV(0.09,A1:A5,B1:B5)", &data);
821 if let CellValue::Number(n) = result {
822 assert!(n > 2000.0, "XNPV should be positive, got {n}");
823 } else {
824 panic!("expected number, got {result:?}");
825 }
826 }
827
828 #[test]
829 fn xirr_basic() {
830 let data = vec![
831 ("Sheet1", 1, 1, CellValue::Number(-10000.0)),
832 ("Sheet1", 1, 2, CellValue::Number(2750.0)),
833 ("Sheet1", 1, 3, CellValue::Number(4250.0)),
834 ("Sheet1", 1, 4, CellValue::Number(3250.0)),
835 ("Sheet1", 1, 5, CellValue::Number(2750.0)),
836 ("Sheet1", 2, 1, CellValue::Number(39448.0)),
837 ("Sheet1", 2, 2, CellValue::Number(39508.0)),
838 ("Sheet1", 2, 3, CellValue::Number(39600.0)),
839 ("Sheet1", 2, 4, CellValue::Number(39692.0)),
840 ("Sheet1", 2, 5, CellValue::Number(39783.0)),
841 ];
842 let result = eval_with_data("XIRR(A1:A5,B1:B5)", &data);
843 if let CellValue::Number(n) = result {
844 assert!(n > 0.0, "XIRR should be positive, got {n}");
845 } else {
846 panic!("expected number, got {result:?}");
847 }
848 }
849}