1use super::super::builtins::utils::{ARG_RANGE_NUM_LENIENT_ONE, coerce_num};
19use crate::args::ArgSchema;
20use crate::function::Function;
21use crate::traits::{ArgumentHandle, FunctionContext};
22use formualizer_common::{ExcelError, LiteralValue};
23use formualizer_macros::func_caps;
25
26fn collect_numeric_stats(args: &[ArgumentHandle]) -> Result<Vec<f64>, ExcelError> {
32 let mut out = Vec::new();
33 for a in args {
34 if let Some(arr) = a.inline_array_literal()? {
38 for row in arr.into_iter() {
39 for cell in row.into_iter() {
40 match cell {
41 LiteralValue::Error(e) => return Err(e),
42 other => {
43 if let Ok(n) = coerce_num(&other) {
44 out.push(n);
45 }
46 }
47 }
48 }
49 }
50 continue;
51 }
52
53 if let Ok(view) = a.range_view() {
54 view.for_each_cell(&mut |v| {
55 match v {
56 LiteralValue::Error(e) => return Err(e.clone()),
57 LiteralValue::Number(n) => out.push(*n),
58 LiteralValue::Int(i) => out.push(*i as f64),
59 _ => {}
60 }
61 Ok(())
62 })?;
63 } else {
64 match a.value()?.as_ref() {
65 LiteralValue::Error(e) => return Err(e.clone()),
66 other => {
67 if let Ok(n) = coerce_num(other) {
68 out.push(n);
69 }
70 }
71 }
72 }
73 }
74 Ok(out)
75}
76
77fn percentile_inc(sorted: &[f64], p: f64) -> Result<f64, ExcelError> {
78 if sorted.is_empty() {
79 return Err(ExcelError::new_num());
80 }
81 if !(0.0..=1.0).contains(&p) {
82 return Err(ExcelError::new_num());
83 }
84 if sorted.len() == 1 {
85 return Ok(sorted[0]);
86 }
87 let n = sorted.len() as f64;
88 let rank = p * (n - 1.0); let lo = rank.floor() as usize;
90 let hi = rank.ceil() as usize;
91 if lo == hi {
92 return Ok(sorted[lo]);
93 }
94 let frac = rank - (lo as f64);
95 Ok(sorted[lo] + (sorted[hi] - sorted[lo]) * frac)
96}
97
98fn percentile_exc(sorted: &[f64], p: f64) -> Result<f64, ExcelError> {
99 if sorted.is_empty() {
101 return Err(ExcelError::new_num());
102 }
103 if !(0.0..=1.0).contains(&p) || p <= 0.0 || p >= 1.0 {
104 return Err(ExcelError::new_num());
105 }
106 let n = sorted.len() as f64;
107 let rank = p * (n + 1.0); if rank < 1.0 || rank > n {
109 return Err(ExcelError::new_num());
110 }
111 let lo = rank.floor();
112 let hi = rank.ceil();
113 if (lo - hi).abs() < f64::EPSILON {
114 return Ok(sorted[(lo as usize) - 1]);
115 }
116 let frac = rank - lo;
117 let lo_idx = (lo as usize) - 1;
118 let hi_idx = (hi as usize) - 1;
119 Ok(sorted[lo_idx] + (sorted[hi_idx] - sorted[lo_idx]) * frac)
120}
121
122#[derive(Debug)]
127pub struct RankEqFn;
128impl Function for RankEqFn {
129 func_caps!(PURE, NUMERIC_ONLY);
130 fn name(&self) -> &'static str {
131 "RANK.EQ"
132 }
133 fn aliases(&self) -> &'static [&'static str] {
134 &["RANK"]
135 }
136 fn min_args(&self) -> usize {
137 2
138 }
139 fn variadic(&self) -> bool {
140 true
141 } fn arg_schema(&self) -> &'static [ArgSchema] {
143 &ARG_RANGE_NUM_LENIENT_ONE[..]
144 }
145 fn eval_scalar<'a, 'b>(
146 &self,
147 args: &'a [ArgumentHandle<'a, 'b>],
148 _ctx: &dyn FunctionContext,
149 ) -> Result<LiteralValue, ExcelError> {
150 if args.len() < 2 {
151 return Ok(LiteralValue::Error(ExcelError::new_na()));
152 }
153 let target = match coerce_num(args[0].value()?.as_ref()) {
154 Ok(n) => n,
155 Err(_) => return Ok(LiteralValue::Error(ExcelError::new_na())),
156 };
157 let order = if args.len() >= 3 {
159 coerce_num(args[2].value()?.as_ref()).unwrap_or(0.0)
160 } else {
161 0.0
162 };
163 let nums = collect_numeric_stats(&args[1..2])?; if nums.is_empty() {
165 return Ok(LiteralValue::Error(ExcelError::new_na()));
166 }
167 let mut sorted = nums; if order.abs() < 1e-12 {
169 sorted.sort_by(|a, b| b.partial_cmp(a).unwrap());
171 } else {
172 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
174 }
175 for (i, &v) in sorted.iter().enumerate() {
176 if (v - target).abs() < 1e-12 {
177 return Ok(LiteralValue::Number((i + 1) as f64));
178 }
179 }
180 Ok(LiteralValue::Error(ExcelError::new_na()))
181 }
182}
183
184#[derive(Debug)]
186pub struct RankAvgFn;
187impl Function for RankAvgFn {
188 func_caps!(PURE, NUMERIC_ONLY);
189 fn name(&self) -> &'static str {
190 "RANK.AVG"
191 }
192 fn min_args(&self) -> usize {
193 2
194 }
195 fn variadic(&self) -> bool {
196 true
197 }
198 fn arg_schema(&self) -> &'static [ArgSchema] {
199 &ARG_RANGE_NUM_LENIENT_ONE[..]
200 }
201 fn eval_scalar<'a, 'b>(
202 &self,
203 args: &'a [ArgumentHandle<'a, 'b>],
204 _ctx: &dyn FunctionContext,
205 ) -> Result<LiteralValue, ExcelError> {
206 if args.len() < 2 {
207 return Ok(LiteralValue::Error(ExcelError::new_na()));
208 }
209 let target = match coerce_num(args[0].value()?.as_ref()) {
210 Ok(n) => n,
211 Err(_) => return Ok(LiteralValue::Error(ExcelError::new_na())),
212 };
213 let order = if args.len() >= 3 {
214 coerce_num(args[2].value()?.as_ref()).unwrap_or(0.0)
215 } else {
216 0.0
217 };
218 let nums = collect_numeric_stats(&args[1..2])?;
219 if nums.is_empty() {
220 return Ok(LiteralValue::Error(ExcelError::new_na()));
221 }
222 let mut sorted = nums;
223 if order.abs() < 1e-12 {
224 sorted.sort_by(|a, b| b.partial_cmp(a).unwrap());
225 } else {
226 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
227 }
228 let mut positions = Vec::new();
229 for (i, &v) in sorted.iter().enumerate() {
230 if (v - target).abs() < 1e-12 {
231 positions.push(i + 1);
232 }
233 }
234 if positions.is_empty() {
235 return Ok(LiteralValue::Error(ExcelError::new_na()));
236 }
237 let avg = positions.iter().copied().sum::<usize>() as f64 / positions.len() as f64;
238 Ok(LiteralValue::Number(avg))
239 }
240}
241
242#[derive(Debug)]
243pub struct LARGE;
244impl Function for LARGE {
245 func_caps!(PURE, NUMERIC_ONLY, REDUCTION);
246 fn name(&self) -> &'static str {
247 "LARGE"
248 }
249 fn min_args(&self) -> usize {
250 2
251 }
252 fn variadic(&self) -> bool {
253 true
254 }
255 fn arg_schema(&self) -> &'static [ArgSchema] {
256 &ARG_RANGE_NUM_LENIENT_ONE[..]
257 }
258 fn eval_scalar<'a, 'b>(
259 &self,
260 args: &'a [ArgumentHandle<'a, 'b>],
261 _ctx: &dyn FunctionContext,
262 ) -> Result<LiteralValue, ExcelError> {
263 if args.len() < 2 {
264 return Ok(LiteralValue::Error(ExcelError::new_num()));
265 }
266 let k = match coerce_num(args.last().unwrap().value()?.as_ref()) {
267 Ok(n) => n,
268 Err(_) => return Ok(LiteralValue::Error(ExcelError::new_num())),
269 };
270 let k = k as i64;
271 if k < 1 {
272 return Ok(LiteralValue::Error(ExcelError::new_num()));
273 }
274 let mut nums = collect_numeric_stats(&args[..args.len() - 1])?;
275 if nums.is_empty() || k as usize > nums.len() {
276 return Ok(LiteralValue::Error(ExcelError::new_num()));
277 }
278 nums.sort_by(|a, b| b.partial_cmp(a).unwrap());
279 Ok(LiteralValue::Number(nums[(k as usize) - 1]))
280 }
281 fn eval_fold(
282 &self,
283 f: &mut dyn crate::function::FnFoldCtx,
284 ) -> Option<Result<LiteralValue, ExcelError>> {
285 let args = f.args();
286 if args.len() < 2 {
287 return Some(Ok(LiteralValue::Error(ExcelError::new_num())));
288 }
289 let k = match coerce_num(args.last().unwrap().value().ok()?.as_ref()) {
290 Ok(n) => n as i64,
291 Err(_) => {
292 return Some(Ok(LiteralValue::Error(ExcelError::from_error_string(
293 "#NUM!",
294 ))));
295 }
296 };
297 if k < 1 {
298 return Some(Ok(LiteralValue::Error(ExcelError::new_num())));
299 }
300 let mut nums = match collect_numeric_stats(&args[..args.len() - 1]) {
301 Ok(v) => v,
302 Err(e) => return Some(Ok(LiteralValue::Error(e))),
303 };
304 if nums.is_empty() || k as usize > nums.len() {
305 return Some(Ok(LiteralValue::Error(ExcelError::new_num())));
306 }
307 let idx = (k as usize) - 1;
309 let target = nums.len() - 1 - idx;
311 let (left, nth, _right) =
312 nums.select_nth_unstable_by(target, |a, b| a.partial_cmp(b).unwrap());
313 let val = *nth;
314 f.write_result(LiteralValue::Number(val));
315 Some(Ok(LiteralValue::Number(val)))
316 }
317}
318
319#[derive(Debug)]
320pub struct SMALL;
321impl Function for SMALL {
322 func_caps!(PURE, NUMERIC_ONLY, REDUCTION);
323 fn name(&self) -> &'static str {
324 "SMALL"
325 }
326 fn min_args(&self) -> usize {
327 2
328 }
329 fn variadic(&self) -> bool {
330 true
331 }
332 fn arg_schema(&self) -> &'static [ArgSchema] {
333 &ARG_RANGE_NUM_LENIENT_ONE[..]
334 }
335 fn eval_scalar<'a, 'b>(
336 &self,
337 args: &'a [ArgumentHandle<'a, 'b>],
338 _ctx: &dyn FunctionContext,
339 ) -> Result<LiteralValue, ExcelError> {
340 if args.len() < 2 {
341 return Ok(LiteralValue::Error(ExcelError::new_num()));
342 }
343 let k = match coerce_num(args.last().unwrap().value()?.as_ref()) {
344 Ok(n) => n,
345 Err(_) => return Ok(LiteralValue::Error(ExcelError::new_num())),
346 };
347 let k = k as i64;
348 if k < 1 {
349 return Ok(LiteralValue::Error(ExcelError::new_num()));
350 }
351 let mut nums = collect_numeric_stats(&args[..args.len() - 1])?;
352 if nums.is_empty() || k as usize > nums.len() {
353 return Ok(LiteralValue::Error(ExcelError::new_num()));
354 }
355 nums.sort_by(|a, b| a.partial_cmp(b).unwrap());
356 Ok(LiteralValue::Number(nums[(k as usize) - 1]))
357 }
358 fn eval_fold(
359 &self,
360 f: &mut dyn crate::function::FnFoldCtx,
361 ) -> Option<Result<LiteralValue, ExcelError>> {
362 let args = f.args();
363 if args.len() < 2 {
364 return Some(Ok(LiteralValue::Error(ExcelError::new_num())));
365 }
366 let k = match coerce_num(args.last().unwrap().value().ok()?.as_ref()) {
367 Ok(n) => n as i64,
368 Err(_) => {
369 return Some(Ok(LiteralValue::Error(ExcelError::from_error_string(
370 "#NUM!",
371 ))));
372 }
373 };
374 if k < 1 {
375 return Some(Ok(LiteralValue::Error(ExcelError::new_num())));
376 }
377 let mut nums = match collect_numeric_stats(&args[..args.len() - 1]) {
378 Ok(v) => v,
379 Err(e) => return Some(Ok(LiteralValue::Error(e))),
380 };
381 if nums.is_empty() || k as usize > nums.len() {
382 return Some(Ok(LiteralValue::Error(ExcelError::new_num())));
383 }
384 let idx = (k as usize) - 1;
385 let (left, nth, _right) =
386 nums.select_nth_unstable_by(idx, |a, b| a.partial_cmp(b).unwrap());
387 let val = *nth;
388 f.write_result(LiteralValue::Number(val));
389 Some(Ok(LiteralValue::Number(val)))
390 }
391}
392
393#[derive(Debug)]
394pub struct MEDIAN;
395impl Function for MEDIAN {
396 func_caps!(PURE, NUMERIC_ONLY, REDUCTION);
397 fn name(&self) -> &'static str {
398 "MEDIAN"
399 }
400 fn min_args(&self) -> usize {
401 1
402 }
403 fn variadic(&self) -> bool {
404 true
405 }
406 fn arg_schema(&self) -> &'static [ArgSchema] {
407 &ARG_RANGE_NUM_LENIENT_ONE[..]
408 }
409 fn eval_scalar<'a, 'b>(
410 &self,
411 args: &'a [ArgumentHandle<'a, 'b>],
412 _ctx: &dyn FunctionContext,
413 ) -> Result<LiteralValue, ExcelError> {
414 let mut nums = collect_numeric_stats(args)?;
415 if nums.is_empty() {
416 return Ok(LiteralValue::Error(ExcelError::new_num()));
417 }
418 nums.sort_by(|a, b| a.partial_cmp(b).unwrap());
419 let n = nums.len();
420 let mid = n / 2;
421 let med = if n % 2 == 1 {
422 nums[mid]
423 } else {
424 (nums[mid - 1] + nums[mid]) / 2.0
425 };
426 Ok(LiteralValue::Number(med))
427 }
428 fn eval_fold(
429 &self,
430 f: &mut dyn crate::function::FnFoldCtx,
431 ) -> Option<Result<LiteralValue, ExcelError>> {
432 let args = f.args();
433 let mut nums = match collect_numeric_stats(args) {
434 Ok(v) => v,
435 Err(e) => return Some(Ok(LiteralValue::Error(e))),
436 };
437 if nums.is_empty() {
438 return Some(Ok(LiteralValue::Error(ExcelError::new_num())));
439 }
440 let n = nums.len();
441 let mid = n / 2;
442 if n % 2 == 1 {
443 let (_left, nth, _right) =
445 nums.select_nth_unstable_by(mid, |a, b| a.partial_cmp(b).unwrap());
446 let val = *nth;
447 let out = LiteralValue::Number(val);
448 f.write_result(out.clone());
449 Some(Ok(out))
450 } else {
451 let (_left, nth, _right) =
453 nums.select_nth_unstable_by(mid, |a, b| a.partial_cmp(b).unwrap());
454 let upper_mid = *nth;
455 let mut lower_max = f64::NEG_INFINITY;
457 for &v in &nums[..mid] {
458 if v > lower_max {
459 lower_max = v;
460 }
461 }
462 let med = (lower_max + upper_mid) / 2.0;
463 let out = LiteralValue::Number(med);
464 f.write_result(out.clone());
465 Some(Ok(out))
466 }
467 }
468}
469
470#[derive(Debug)]
471pub struct StdevSample; impl Function for StdevSample {
473 func_caps!(PURE, NUMERIC_ONLY, REDUCTION, STREAM_OK);
474 fn name(&self) -> &'static str {
475 "STDEV.S"
476 }
477 fn aliases(&self) -> &'static [&'static str] {
478 &["STDEV"]
479 }
480 fn min_args(&self) -> usize {
481 1
482 }
483 fn variadic(&self) -> bool {
484 true
485 }
486 fn arg_schema(&self) -> &'static [ArgSchema] {
487 &ARG_RANGE_NUM_LENIENT_ONE[..]
488 }
489 fn eval_scalar<'a, 'b>(
490 &self,
491 args: &'a [ArgumentHandle<'a, 'b>],
492 _ctx: &dyn FunctionContext,
493 ) -> Result<LiteralValue, ExcelError> {
494 let nums = collect_numeric_stats(args)?;
495 let n = nums.len();
496 if n < 2 {
497 return Ok(LiteralValue::Error(ExcelError::from_error_string(
498 "#DIV/0!",
499 )));
500 }
501 let mean = nums.iter().sum::<f64>() / (n as f64);
502 let mut ss = 0.0;
503 for &v in &nums {
504 let d = v - mean;
505 ss += d * d;
506 }
507 Ok(LiteralValue::Number((ss / ((n - 1) as f64)).sqrt()))
508 }
509 fn eval_fold(
510 &self,
511 f: &mut dyn crate::function::FnFoldCtx,
512 ) -> Option<Result<LiteralValue, ExcelError>> {
513 let mut n: f64 = 0.0;
515 let mut mean: f64 = 0.0;
516 let mut m2: f64 = 0.0;
517 let mut err: Option<ExcelError> = None;
518 let mut cb = |chunk: crate::stripes::NumericChunk| -> Result<(), ExcelError> {
519 for &x in chunk.data {
520 n += 1.0;
521 let delta = x - mean;
522 mean += delta / n;
523 let delta2 = x - mean;
524 m2 += delta * delta2;
525 }
526 Ok(())
527 };
528 if let Err(e) = f.for_each_numeric_chunk(4096, &mut cb) {
529 err = Some(e);
530 }
531 let out = if let Some(e) = err {
532 LiteralValue::Error(e)
533 } else if n < 2.0 {
534 LiteralValue::Error(ExcelError::new_div())
535 } else {
536 LiteralValue::Number((m2 / (n - 1.0)).sqrt())
537 };
538 f.write_result(out.clone());
539 Some(Ok(out))
540 }
541}
542
543#[derive(Debug)]
544pub struct StdevPop; impl Function for StdevPop {
546 func_caps!(PURE, NUMERIC_ONLY, REDUCTION, STREAM_OK);
547 fn name(&self) -> &'static str {
548 "STDEV.P"
549 }
550 fn aliases(&self) -> &'static [&'static str] {
551 &["STDEVP"]
552 }
553 fn min_args(&self) -> usize {
554 1
555 }
556 fn variadic(&self) -> bool {
557 true
558 }
559 fn arg_schema(&self) -> &'static [ArgSchema] {
560 &ARG_RANGE_NUM_LENIENT_ONE[..]
561 }
562 fn eval_scalar<'a, 'b>(
563 &self,
564 args: &'a [ArgumentHandle<'a, 'b>],
565 _ctx: &dyn FunctionContext,
566 ) -> Result<LiteralValue, ExcelError> {
567 let nums = collect_numeric_stats(args)?;
568 let n = nums.len();
569 if n == 0 {
570 return Ok(LiteralValue::Error(ExcelError::from_error_string(
571 "#DIV/0!",
572 )));
573 }
574 let mean = nums.iter().sum::<f64>() / (n as f64);
575 let mut ss = 0.0;
576 for &v in &nums {
577 let d = v - mean;
578 ss += d * d;
579 }
580 Ok(LiteralValue::Number((ss / (n as f64)).sqrt()))
581 }
582 fn eval_fold(
583 &self,
584 f: &mut dyn crate::function::FnFoldCtx,
585 ) -> Option<Result<LiteralValue, ExcelError>> {
586 let mut n: f64 = 0.0;
587 let mut mean = 0.0;
588 let mut m2 = 0.0;
589 let mut err: Option<ExcelError> = None;
590 let mut cb = |chunk: crate::stripes::NumericChunk| -> Result<(), ExcelError> {
591 for &x in chunk.data {
592 n += 1.0;
593 let delta = x - mean;
594 mean += delta / n;
595 let delta2 = x - mean;
596 m2 += delta * delta2;
597 }
598 Ok(())
599 };
600 if let Err(e) = f.for_each_numeric_chunk(4096, &mut cb) {
601 err = Some(e);
602 }
603 let out = if let Some(e) = err {
604 LiteralValue::Error(e)
605 } else if n == 0.0 {
606 LiteralValue::Error(ExcelError::new_div())
607 } else {
608 LiteralValue::Number((m2 / n).sqrt())
609 };
610 f.write_result(out.clone());
611 Some(Ok(out))
612 }
613}
614
615#[derive(Debug)]
616pub struct VarSample; impl Function for VarSample {
618 func_caps!(PURE, NUMERIC_ONLY, REDUCTION, STREAM_OK);
619 fn name(&self) -> &'static str {
620 "VAR.S"
621 }
622 fn aliases(&self) -> &'static [&'static str] {
623 &["VAR"]
624 }
625 fn min_args(&self) -> usize {
626 1
627 }
628 fn variadic(&self) -> bool {
629 true
630 }
631 fn arg_schema(&self) -> &'static [ArgSchema] {
632 &ARG_RANGE_NUM_LENIENT_ONE[..]
633 }
634 fn eval_scalar<'a, 'b>(
635 &self,
636 args: &'a [ArgumentHandle<'a, 'b>],
637 _ctx: &dyn FunctionContext,
638 ) -> Result<LiteralValue, ExcelError> {
639 let nums = collect_numeric_stats(args)?;
640 let n = nums.len();
641 if n < 2 {
642 return Ok(LiteralValue::Error(ExcelError::from_error_string(
643 "#DIV/0!",
644 )));
645 }
646 let mean = nums.iter().sum::<f64>() / (n as f64);
647 let mut ss = 0.0;
648 for &v in &nums {
649 let d = v - mean;
650 ss += d * d;
651 }
652 Ok(LiteralValue::Number(ss / ((n - 1) as f64)))
653 }
654 fn eval_fold(
655 &self,
656 f: &mut dyn crate::function::FnFoldCtx,
657 ) -> Option<Result<LiteralValue, ExcelError>> {
658 let mut n: f64 = 0.0;
659 let mut mean = 0.0;
660 let mut m2 = 0.0;
661 let mut err = None;
662 let mut cb = |chunk: crate::stripes::NumericChunk| -> Result<(), ExcelError> {
663 for &x in chunk.data {
664 n += 1.0;
665 let delta = x - mean;
666 mean += delta / n;
667 let delta2 = x - mean;
668 m2 += delta * delta2;
669 }
670 Ok(())
671 };
672 if let Err(e) = f.for_each_numeric_chunk(4096, &mut cb) {
673 err = Some(e);
674 }
675 let out = if let Some(e) = err {
676 LiteralValue::Error(e)
677 } else if n < 2.0 {
678 LiteralValue::Error(ExcelError::new_div())
679 } else {
680 LiteralValue::Number(m2 / (n - 1.0))
681 };
682 f.write_result(out.clone());
683 Some(Ok(out))
684 }
685}
686
687#[derive(Debug)]
688pub struct VarPop; impl Function for VarPop {
690 func_caps!(PURE, NUMERIC_ONLY, REDUCTION, STREAM_OK);
691 fn name(&self) -> &'static str {
692 "VAR.P"
693 }
694 fn aliases(&self) -> &'static [&'static str] {
695 &["VARP"]
696 }
697 fn min_args(&self) -> usize {
698 1
699 }
700 fn variadic(&self) -> bool {
701 true
702 }
703 fn arg_schema(&self) -> &'static [ArgSchema] {
704 &ARG_RANGE_NUM_LENIENT_ONE[..]
705 }
706 fn eval_scalar<'a, 'b>(
707 &self,
708 args: &'a [ArgumentHandle<'a, 'b>],
709 _ctx: &dyn FunctionContext,
710 ) -> Result<LiteralValue, ExcelError> {
711 let nums = collect_numeric_stats(args)?;
712 let n = nums.len();
713 if n == 0 {
714 return Ok(LiteralValue::Error(ExcelError::from_error_string(
715 "#DIV/0!",
716 )));
717 }
718 let mean = nums.iter().sum::<f64>() / (n as f64);
719 let mut ss = 0.0;
720 for &v in &nums {
721 let d = v - mean;
722 ss += d * d;
723 }
724 Ok(LiteralValue::Number(ss / (n as f64)))
725 }
726 fn eval_fold(
727 &self,
728 f: &mut dyn crate::function::FnFoldCtx,
729 ) -> Option<Result<LiteralValue, ExcelError>> {
730 let mut n: f64 = 0.0;
731 let mut mean = 0.0;
732 let mut m2 = 0.0;
733 let mut err = None;
734 let mut cb = |chunk: crate::stripes::NumericChunk| -> Result<(), ExcelError> {
735 for &x in chunk.data {
736 n += 1.0;
737 let delta = x - mean;
738 mean += delta / n;
739 let delta2 = x - mean;
740 m2 += delta * delta2;
741 }
742 Ok(())
743 };
744 if let Err(e) = f.for_each_numeric_chunk(4096, &mut cb) {
745 err = Some(e);
746 }
747 let out = if let Some(e) = err {
748 LiteralValue::Error(e)
749 } else if n == 0.0 {
750 LiteralValue::Error(ExcelError::new_div())
751 } else {
752 LiteralValue::Number(m2 / n)
753 };
754 f.write_result(out.clone());
755 Some(Ok(out))
756 }
757}
758
759#[derive(Debug)]
761pub struct ModeSingleFn;
762impl Function for ModeSingleFn {
763 func_caps!(PURE, NUMERIC_ONLY, REDUCTION);
764 fn name(&self) -> &'static str {
765 "MODE.SNGL"
766 }
767 fn aliases(&self) -> &'static [&'static str] {
768 &["MODE"]
769 }
770 fn min_args(&self) -> usize {
771 1
772 }
773 fn variadic(&self) -> bool {
774 true
775 }
776 fn arg_schema(&self) -> &'static [ArgSchema] {
777 &ARG_RANGE_NUM_LENIENT_ONE[..]
778 }
779 fn eval_scalar<'a, 'b>(
780 &self,
781 args: &'a [ArgumentHandle<'a, 'b>],
782 _ctx: &dyn FunctionContext,
783 ) -> Result<LiteralValue, ExcelError> {
784 let mut nums = collect_numeric_stats(args)?;
785 if nums.is_empty() {
786 return Ok(LiteralValue::Error(ExcelError::new_na()));
787 }
788 nums.sort_by(|a, b| a.partial_cmp(b).unwrap());
789 let mut best_val = nums[0];
790 let mut best_cnt = 1usize;
791 let mut cur_val = nums[0];
792 let mut cur_cnt = 1usize;
793 for &v in &nums[1..] {
794 if (v - cur_val).abs() < 1e-12 {
795 cur_cnt += 1;
796 } else {
797 if cur_cnt > best_cnt {
798 best_cnt = cur_cnt;
799 best_val = cur_val;
800 }
801 cur_val = v;
802 cur_cnt = 1;
803 }
804 }
805 if cur_cnt > best_cnt {
806 best_cnt = cur_cnt;
807 best_val = cur_val;
808 }
809 if best_cnt <= 1 {
810 Ok(LiteralValue::Error(ExcelError::new_na()))
811 } else {
812 Ok(LiteralValue::Number(best_val))
813 }
814 }
815 fn eval_fold(
816 &self,
817 f: &mut dyn crate::function::FnFoldCtx,
818 ) -> Option<Result<LiteralValue, ExcelError>> {
819 let args = f.args();
820 let mut nums = match collect_numeric_stats(args) {
821 Ok(v) => v,
822 Err(e) => return Some(Ok(LiteralValue::Error(e))),
823 };
824 if nums.is_empty() {
825 return Some(Ok(LiteralValue::Error(ExcelError::from_error_string(
826 "#N/A",
827 ))));
828 }
829 nums.sort_by(|a, b| a.partial_cmp(b).unwrap());
830 let mut best_val = nums[0];
831 let mut best_cnt = 1usize;
832 let mut cur_val = nums[0];
833 let mut cur_cnt = 1usize;
834 for &v in &nums[1..] {
835 if (v - cur_val).abs() < 1e-12 {
836 cur_cnt += 1;
837 } else {
838 if cur_cnt > best_cnt {
839 best_cnt = cur_cnt;
840 best_val = cur_val;
841 }
842 cur_val = v;
843 cur_cnt = 1;
844 }
845 }
846 if cur_cnt > best_cnt {
847 best_cnt = cur_cnt;
848 best_val = cur_val;
849 }
850 let out = if best_cnt <= 1 {
851 LiteralValue::Error(ExcelError::new_na())
852 } else {
853 LiteralValue::Number(best_val)
854 };
855 f.write_result(out.clone());
856 Some(Ok(out))
857 }
858}
859
860#[derive(Debug)]
861pub struct ModeMultiFn;
862impl Function for ModeMultiFn {
863 func_caps!(PURE, NUMERIC_ONLY, REDUCTION);
864 fn name(&self) -> &'static str {
865 "MODE.MULT"
866 }
867 fn min_args(&self) -> usize {
868 1
869 }
870 fn variadic(&self) -> bool {
871 true
872 }
873 fn arg_schema(&self) -> &'static [ArgSchema] {
874 &ARG_RANGE_NUM_LENIENT_ONE[..]
875 }
876 fn eval_scalar<'a, 'b>(
877 &self,
878 args: &'a [ArgumentHandle<'a, 'b>],
879 _ctx: &dyn FunctionContext,
880 ) -> Result<LiteralValue, ExcelError> {
881 let mut nums = collect_numeric_stats(args)?;
882 if nums.is_empty() {
883 return Ok(LiteralValue::Error(ExcelError::new_na()));
884 }
885 nums.sort_by(|a, b| a.partial_cmp(b).unwrap());
886 let mut runs: Vec<(f64, usize)> = Vec::new();
887 let mut cur_val = nums[0];
888 let mut cur_cnt = 1usize;
889 for &v in &nums[1..] {
890 if (v - cur_val).abs() < 1e-12 {
891 cur_cnt += 1;
892 } else {
893 runs.push((cur_val, cur_cnt));
894 cur_val = v;
895 cur_cnt = 1;
896 }
897 }
898 runs.push((cur_val, cur_cnt));
899 let max_freq = runs.iter().map(|r| r.1).max().unwrap_or(0);
900 if max_freq <= 1 {
901 return Ok(LiteralValue::Error(ExcelError::new_na()));
902 }
903 let rows: Vec<Vec<LiteralValue>> = runs
904 .into_iter()
905 .filter(|&(_, c)| c == max_freq)
906 .map(|(v, _)| vec![LiteralValue::Number(v)])
907 .collect();
908 Ok(LiteralValue::Array(rows))
909 }
910 fn eval_fold(
911 &self,
912 f: &mut dyn crate::function::FnFoldCtx,
913 ) -> Option<Result<LiteralValue, ExcelError>> {
914 let args = f.args();
915 let mut nums = match collect_numeric_stats(args) {
916 Ok(v) => v,
917 Err(e) => return Some(Ok(LiteralValue::Error(e))),
918 };
919 if nums.is_empty() {
920 return Some(Ok(LiteralValue::Error(ExcelError::from_error_string(
921 "#N/A",
922 ))));
923 }
924 nums.sort_by(|a, b| a.partial_cmp(b).unwrap());
925 let mut runs: Vec<(f64, usize)> = Vec::new();
926 let mut cur = nums[0];
927 let mut cnt = 1usize;
928 for &v in &nums[1..] {
929 if (v - cur).abs() < 1e-12 {
930 cnt += 1;
931 } else {
932 runs.push((cur, cnt));
933 cur = v;
934 cnt = 1;
935 }
936 }
937 runs.push((cur, cnt));
938 let max_freq = runs.iter().map(|r| r.1).max().unwrap_or(0);
939 if max_freq <= 1 {
940 let out = LiteralValue::Error(ExcelError::new_na());
941 f.write_result(out.clone());
942 return Some(Ok(out));
943 }
944 let rows: Vec<Vec<LiteralValue>> = runs
945 .into_iter()
946 .filter(|&(_, c)| c == max_freq)
947 .map(|(v, _)| vec![LiteralValue::Number(v)])
948 .collect();
949 let out = LiteralValue::Array(rows);
950 f.write_result(out.clone());
951 Some(Ok(out))
952 }
953}
954
955#[derive(Debug)]
956pub struct PercentileInc; impl Function for PercentileInc {
958 func_caps!(PURE, NUMERIC_ONLY);
959 fn name(&self) -> &'static str {
960 "PERCENTILE.INC"
961 }
962 fn aliases(&self) -> &'static [&'static str] {
963 &["PERCENTILE"]
964 }
965 fn min_args(&self) -> usize {
966 2
967 }
968 fn variadic(&self) -> bool {
969 true
970 }
971 fn arg_schema(&self) -> &'static [ArgSchema] {
972 &ARG_RANGE_NUM_LENIENT_ONE[..]
973 }
974 fn eval_scalar<'a, 'b>(
975 &self,
976 args: &'a [ArgumentHandle<'a, 'b>],
977 _ctx: &dyn FunctionContext,
978 ) -> Result<LiteralValue, ExcelError> {
979 if args.len() < 2 {
980 return Ok(LiteralValue::Error(ExcelError::new_num()));
981 }
982 let p = match coerce_num(args.last().unwrap().value()?.as_ref()) {
983 Ok(n) => n,
984 Err(_) => return Ok(LiteralValue::Error(ExcelError::new_num())),
985 };
986 let mut nums = collect_numeric_stats(&args[..args.len() - 1])?;
987 if nums.is_empty() {
988 return Ok(LiteralValue::Error(ExcelError::new_num()));
989 }
990 nums.sort_by(|a, b| a.partial_cmp(b).unwrap());
991 match percentile_inc(&nums, p) {
992 Ok(v) => Ok(LiteralValue::Number(v)),
993 Err(e) => Ok(LiteralValue::Error(e)),
994 }
995 }
996}
997
998#[derive(Debug)]
999pub struct PercentileExc; impl Function for PercentileExc {
1001 func_caps!(PURE, NUMERIC_ONLY);
1002 fn name(&self) -> &'static str {
1003 "PERCENTILE.EXC"
1004 }
1005 fn min_args(&self) -> usize {
1006 2
1007 }
1008 fn variadic(&self) -> bool {
1009 true
1010 }
1011 fn arg_schema(&self) -> &'static [ArgSchema] {
1012 &ARG_RANGE_NUM_LENIENT_ONE[..]
1013 }
1014 fn eval_scalar<'a, 'b>(
1015 &self,
1016 args: &'a [ArgumentHandle<'a, 'b>],
1017 _ctx: &dyn FunctionContext,
1018 ) -> Result<LiteralValue, ExcelError> {
1019 if args.len() < 2 {
1020 return Ok(LiteralValue::Error(ExcelError::new_num()));
1021 }
1022 let p = match coerce_num(args.last().unwrap().value()?.as_ref()) {
1023 Ok(n) => n,
1024 Err(_) => return Ok(LiteralValue::Error(ExcelError::new_num())),
1025 };
1026 let mut nums = collect_numeric_stats(&args[..args.len() - 1])?;
1027 if nums.is_empty() {
1028 return Ok(LiteralValue::Error(ExcelError::new_num()));
1029 }
1030 nums.sort_by(|a, b| a.partial_cmp(b).unwrap());
1031 match percentile_exc(&nums, p) {
1032 Ok(v) => Ok(LiteralValue::Number(v)),
1033 Err(e) => Ok(LiteralValue::Error(e)),
1034 }
1035 }
1036}
1037
1038#[derive(Debug)]
1039pub struct QuartileInc; impl Function for QuartileInc {
1041 func_caps!(PURE, NUMERIC_ONLY);
1042 fn name(&self) -> &'static str {
1043 "QUARTILE.INC"
1044 }
1045 fn aliases(&self) -> &'static [&'static str] {
1046 &["QUARTILE"]
1047 }
1048 fn min_args(&self) -> usize {
1049 2
1050 }
1051 fn variadic(&self) -> bool {
1052 true
1053 }
1054 fn arg_schema(&self) -> &'static [ArgSchema] {
1055 &ARG_RANGE_NUM_LENIENT_ONE[..]
1056 }
1057 fn eval_scalar<'a, 'b>(
1058 &self,
1059 args: &'a [ArgumentHandle<'a, 'b>],
1060 _ctx: &dyn FunctionContext,
1061 ) -> Result<LiteralValue, ExcelError> {
1062 if args.len() < 2 {
1063 return Ok(LiteralValue::Error(ExcelError::new_num()));
1064 }
1065 let q = match coerce_num(args.last().unwrap().value()?.as_ref()) {
1066 Ok(n) => n,
1067 Err(_) => return Ok(LiteralValue::Error(ExcelError::new_num())),
1068 };
1069 let q_i = q as i64;
1070 if !(0..=4).contains(&q_i) {
1071 return Ok(LiteralValue::Error(ExcelError::new_num()));
1072 }
1073 let mut nums = collect_numeric_stats(&args[..args.len() - 1])?;
1074 if nums.is_empty() {
1075 return Ok(LiteralValue::Error(ExcelError::new_num()));
1076 }
1077 nums.sort_by(|a, b| a.partial_cmp(b).unwrap());
1078 let p = match q_i {
1079 0 => return Ok(LiteralValue::Number(nums[0])),
1080 4 => return Ok(LiteralValue::Number(nums[nums.len() - 1])),
1081 1 => 0.25,
1082 2 => 0.5,
1083 3 => 0.75,
1084 _ => return Ok(LiteralValue::Error(ExcelError::new_num())),
1085 };
1086 match percentile_inc(&nums, p) {
1087 Ok(v) => Ok(LiteralValue::Number(v)),
1088 Err(e) => Ok(LiteralValue::Error(e)),
1089 }
1090 }
1091}
1092
1093#[derive(Debug)]
1094pub struct QuartileExc; impl Function for QuartileExc {
1096 func_caps!(PURE, NUMERIC_ONLY);
1097 fn name(&self) -> &'static str {
1098 "QUARTILE.EXC"
1099 }
1100 fn min_args(&self) -> usize {
1101 2
1102 }
1103 fn variadic(&self) -> bool {
1104 true
1105 }
1106 fn arg_schema(&self) -> &'static [ArgSchema] {
1107 &ARG_RANGE_NUM_LENIENT_ONE[..]
1108 }
1109 fn eval_scalar<'a, 'b>(
1110 &self,
1111 args: &'a [ArgumentHandle<'a, 'b>],
1112 _ctx: &dyn FunctionContext,
1113 ) -> Result<LiteralValue, ExcelError> {
1114 if args.len() < 2 {
1115 return Ok(LiteralValue::Error(ExcelError::new_num()));
1116 }
1117 let q = match coerce_num(args.last().unwrap().value()?.as_ref()) {
1118 Ok(n) => n,
1119 Err(_) => return Ok(LiteralValue::Error(ExcelError::new_num())),
1120 };
1121 let q_i = q as i64;
1122 if !(1..=3).contains(&q_i) {
1123 return Ok(LiteralValue::Error(ExcelError::new_num()));
1124 }
1125 let mut nums = collect_numeric_stats(&args[..args.len() - 1])?;
1126 if nums.len() < 2 {
1127 return Ok(LiteralValue::Error(ExcelError::new_num()));
1128 }
1129 nums.sort_by(|a, b| a.partial_cmp(b).unwrap());
1130 let p = match q_i {
1131 1 => 0.25,
1132 2 => 0.5,
1133 3 => 0.75,
1134 _ => return Ok(LiteralValue::Error(ExcelError::new_num())),
1135 };
1136 match percentile_exc(&nums, p) {
1137 Ok(v) => Ok(LiteralValue::Number(v)),
1138 Err(e) => Ok(LiteralValue::Error(e)),
1139 }
1140 }
1141}
1142
1143pub fn register_builtins() {
1144 use std::sync::Arc;
1145 crate::function_registry::register_function(Arc::new(LARGE));
1146 crate::function_registry::register_function(Arc::new(SMALL));
1147 crate::function_registry::register_function(Arc::new(MEDIAN));
1148 crate::function_registry::register_function(Arc::new(StdevSample));
1149 crate::function_registry::register_function(Arc::new(StdevPop));
1150 crate::function_registry::register_function(Arc::new(VarSample));
1151 crate::function_registry::register_function(Arc::new(VarPop));
1152 crate::function_registry::register_function(Arc::new(PercentileInc));
1153 crate::function_registry::register_function(Arc::new(PercentileExc));
1154 crate::function_registry::register_function(Arc::new(QuartileInc));
1155 crate::function_registry::register_function(Arc::new(QuartileExc));
1156 crate::function_registry::register_function(Arc::new(RankEqFn));
1157 crate::function_registry::register_function(Arc::new(RankAvgFn));
1158 crate::function_registry::register_function(Arc::new(ModeSingleFn));
1159 crate::function_registry::register_function(Arc::new(ModeMultiFn));
1160}
1161
1162#[cfg(test)]
1163mod tests_basic_stats {
1164 use super::*;
1165 use crate::test_workbook::TestWorkbook;
1166 use crate::traits::ArgumentHandle;
1167 use formualizer_common::LiteralValue;
1168 use formualizer_parse::parser::{ASTNode, ASTNodeType};
1169 fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
1170 wb.interpreter()
1171 }
1172 fn arr(vals: Vec<f64>) -> ASTNode {
1173 ASTNode::new(
1174 ASTNodeType::Literal(LiteralValue::Array(vec![
1175 vals.into_iter().map(LiteralValue::Number).collect(),
1176 ])),
1177 None,
1178 )
1179 }
1180 #[test]
1181 fn median_even() {
1182 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(MEDIAN));
1183 let ctx = interp(&wb);
1184 let node = arr(vec![1.0, 3.0, 5.0, 7.0]);
1185 let f = ctx.context.get_function("", "MEDIAN").unwrap();
1186 let out = f
1187 .dispatch(
1188 &[ArgumentHandle::new(&node, &ctx)],
1189 &ctx.function_context(None),
1190 )
1191 .unwrap();
1192 assert_eq!(out, LiteralValue::Number(4.0));
1193 }
1194 #[test]
1195 fn median_odd() {
1196 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(MEDIAN));
1197 let ctx = interp(&wb);
1198 let node = arr(vec![1.0, 9.0, 5.0]);
1199 let f = ctx.context.get_function("", "MEDIAN").unwrap();
1200 let out = f
1201 .dispatch(
1202 &[ArgumentHandle::new(&node, &ctx)],
1203 &ctx.function_context(None),
1204 )
1205 .unwrap();
1206 assert_eq!(out, LiteralValue::Number(5.0));
1207 }
1208 #[test]
1209 fn large_basic() {
1210 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(LARGE));
1211 let ctx = interp(&wb);
1212 let nums = arr(vec![10.0, 20.0, 30.0]);
1213 let k = ASTNode::new(ASTNodeType::Literal(LiteralValue::Number(2.0)), None);
1214 let f = ctx.context.get_function("", "LARGE").unwrap();
1215 let out = f
1216 .dispatch(
1217 &[
1218 ArgumentHandle::new(&nums, &ctx),
1219 ArgumentHandle::new(&k, &ctx),
1220 ],
1221 &ctx.function_context(None),
1222 )
1223 .unwrap();
1224 assert_eq!(out, LiteralValue::Number(20.0));
1225 }
1226 #[test]
1227 fn small_basic() {
1228 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(SMALL));
1229 let ctx = interp(&wb);
1230 let nums = arr(vec![10.0, 20.0, 30.0]);
1231 let k = ASTNode::new(ASTNodeType::Literal(LiteralValue::Number(2.0)), None);
1232 let f = ctx.context.get_function("", "SMALL").unwrap();
1233 let out = f
1234 .dispatch(
1235 &[
1236 ArgumentHandle::new(&nums, &ctx),
1237 ArgumentHandle::new(&k, &ctx),
1238 ],
1239 &ctx.function_context(None),
1240 )
1241 .unwrap();
1242 assert_eq!(out, LiteralValue::Number(20.0));
1243 }
1244 #[test]
1245 fn percentile_inc_quarter() {
1246 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(PercentileInc));
1247 let ctx = interp(&wb);
1248 let nums = arr(vec![1.0, 2.0, 3.0, 4.0]);
1249 let p = ASTNode::new(ASTNodeType::Literal(LiteralValue::Number(0.25)), None);
1250 let f = ctx.context.get_function("", "PERCENTILE.INC").unwrap();
1251 match f
1252 .dispatch(
1253 &[
1254 ArgumentHandle::new(&nums, &ctx),
1255 ArgumentHandle::new(&p, &ctx),
1256 ],
1257 &ctx.function_context(None),
1258 )
1259 .unwrap()
1260 {
1261 LiteralValue::Number(v) => assert!((v - 1.75).abs() < 1e-9),
1262 other => panic!("unexpected {other:?}"),
1263 }
1264 }
1265 #[test]
1266 fn rank_eq_descending() {
1267 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(RankEqFn));
1268 let ctx = interp(&wb);
1269 let target = ASTNode::new(ASTNodeType::Literal(LiteralValue::Number(5.0)), None);
1271 let arr_node = arr(vec![10.0, 5.0, 1.0]);
1272 let f = ctx.context.get_function("", "RANK.EQ").unwrap();
1273 let out = f
1274 .dispatch(
1275 &[
1276 ArgumentHandle::new(&target, &ctx),
1277 ArgumentHandle::new(&arr_node, &ctx),
1278 ],
1279 &ctx.function_context(None),
1280 )
1281 .unwrap();
1282 assert_eq!(out, LiteralValue::Number(2.0));
1283 }
1284 #[test]
1285 fn rank_eq_ascending_order_arg() {
1286 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(RankEqFn));
1287 let ctx = interp(&wb);
1288 let target = ASTNode::new(ASTNodeType::Literal(LiteralValue::Number(5.0)), None);
1290 let arr_node = arr(vec![1.0, 5.0, 10.0]);
1291 let order = ASTNode::new(ASTNodeType::Literal(LiteralValue::Number(1.0)), None);
1292 let f = ctx.context.get_function("", "RANK.EQ").unwrap();
1293 let out = f
1294 .dispatch(
1295 &[
1296 ArgumentHandle::new(&target, &ctx),
1297 ArgumentHandle::new(&arr_node, &ctx),
1298 ArgumentHandle::new(&order, &ctx),
1299 ],
1300 &ctx.function_context(None),
1301 )
1302 .unwrap();
1303 assert_eq!(out, LiteralValue::Number(2.0));
1304 }
1305 #[test]
1306 fn rank_avg_ties() {
1307 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(RankAvgFn));
1308 let ctx = interp(&wb);
1309 let target = ASTNode::new(ASTNodeType::Literal(LiteralValue::Number(5.0)), None);
1311 let arr_node = arr(vec![5.0, 5.0, 1.0]);
1312 let f = ctx.context.get_function("", "RANK.AVG").unwrap();
1313 let out = f
1314 .dispatch(
1315 &[
1316 ArgumentHandle::new(&target, &ctx),
1317 ArgumentHandle::new(&arr_node, &ctx),
1318 ],
1319 &ctx.function_context(None),
1320 )
1321 .unwrap();
1322 match out {
1323 LiteralValue::Number(v) => assert!((v - 1.5).abs() < 1e-12),
1324 other => panic!("expected number got {other:?}"),
1325 }
1326 }
1327 #[test]
1328 fn stdev_var_sample_population() {
1329 let wb = TestWorkbook::new()
1330 .with_function(std::sync::Arc::new(StdevSample))
1331 .with_function(std::sync::Arc::new(StdevPop))
1332 .with_function(std::sync::Arc::new(VarSample))
1333 .with_function(std::sync::Arc::new(VarPop));
1334 let ctx = interp(&wb);
1335 let arr_node = arr(vec![2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0]); let stdev_p = ctx.context.get_function("", "STDEV.P").unwrap();
1337 let stdev_s = ctx.context.get_function("", "STDEV.S").unwrap();
1338 let var_p = ctx.context.get_function("", "VAR.P").unwrap();
1339 let var_s = ctx.context.get_function("", "VAR.S").unwrap();
1340 let args = [ArgumentHandle::new(&arr_node, &ctx)];
1341 match var_p.dispatch(&args, &ctx.function_context(None)).unwrap() {
1342 LiteralValue::Number(v) => assert!((v - 4.0).abs() < 1e-12),
1343 other => panic!("unexpected {other:?}"),
1344 }
1345 match var_s.dispatch(&args, &ctx.function_context(None)).unwrap() {
1346 LiteralValue::Number(v) => assert!((v - 4.571428571428571).abs() < 1e-9),
1347 other => panic!("unexpected {other:?}"),
1348 }
1349 match stdev_p
1350 .dispatch(&args, &ctx.function_context(None))
1351 .unwrap()
1352 {
1353 LiteralValue::Number(v) => assert!((v - 2.0).abs() < 1e-12),
1354 other => panic!("unexpected {other:?}"),
1355 }
1356 match stdev_s
1357 .dispatch(&args, &ctx.function_context(None))
1358 .unwrap()
1359 {
1360 LiteralValue::Number(v) => assert!((v - 2.138089935).abs() < 1e-9),
1361 other => panic!("unexpected {other:?}"),
1362 }
1363 }
1364 #[test]
1365 fn quartile_inc_exc() {
1366 let wb = TestWorkbook::new()
1367 .with_function(std::sync::Arc::new(QuartileInc))
1368 .with_function(std::sync::Arc::new(QuartileExc));
1369 let ctx = interp(&wb);
1370 let arr_node = arr(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
1371 let q1 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Number(1.0)), None);
1372 let q2 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Number(2.0)), None);
1373 let f_inc = ctx.context.get_function("", "QUARTILE.INC").unwrap();
1374 let f_exc = ctx.context.get_function("", "QUARTILE.EXC").unwrap();
1375 let arg_inc_q1 = [
1376 ArgumentHandle::new(&arr_node, &ctx),
1377 ArgumentHandle::new(&q1, &ctx),
1378 ];
1379 let arg_inc_q2 = [
1380 ArgumentHandle::new(&arr_node, &ctx),
1381 ArgumentHandle::new(&q2, &ctx),
1382 ];
1383 match f_inc
1384 .dispatch(&arg_inc_q1, &ctx.function_context(None))
1385 .unwrap()
1386 {
1387 LiteralValue::Number(v) => assert!((v - 2.0).abs() < 1e-12),
1388 other => panic!("unexpected {other:?}"),
1389 }
1390 match f_inc
1391 .dispatch(&arg_inc_q2, &ctx.function_context(None))
1392 .unwrap()
1393 {
1394 LiteralValue::Number(v) => assert!((v - 3.0).abs() < 1e-12),
1395 other => panic!("unexpected {other:?}"),
1396 }
1397 match f_exc
1399 .dispatch(&arg_inc_q1, &ctx.function_context(None))
1400 .unwrap()
1401 {
1402 LiteralValue::Number(v) => assert!((v - 1.5).abs() < 1e-12),
1403 other => panic!("unexpected {other:?}"),
1404 }
1405 match f_exc
1406 .dispatch(&arg_inc_q2, &ctx.function_context(None))
1407 .unwrap()
1408 {
1409 LiteralValue::Number(v) => assert!((v - 3.0).abs() < 1e-12),
1410 other => panic!("unexpected {other:?}"),
1411 }
1412 }
1413
1414 #[test]
1416 fn fold_equivalence_var_stdev() {
1417 use crate::function::Function as _; let wb = TestWorkbook::new()
1419 .with_function(std::sync::Arc::new(VarSample))
1420 .with_function(std::sync::Arc::new(VarPop))
1421 .with_function(std::sync::Arc::new(StdevSample))
1422 .with_function(std::sync::Arc::new(StdevPop));
1423 let ctx = interp(&wb);
1424 let arr_node = arr(vec![1.0, 2.0, 5.0, 5.0, 9.0]);
1425 let args = [ArgumentHandle::new(&arr_node, &ctx)];
1426
1427 let var_s_fn = VarSample; let var_p_fn = VarPop;
1429 let stdev_s_fn = StdevSample;
1430 let stdev_p_fn = StdevPop;
1431
1432 let fctx = ctx.function_context(None);
1433 let disp_var_s = ctx
1435 .context
1436 .get_function("", "VAR.S")
1437 .unwrap()
1438 .dispatch(&args, &fctx)
1439 .unwrap();
1440 let disp_var_p = ctx
1441 .context
1442 .get_function("", "VAR.P")
1443 .unwrap()
1444 .dispatch(&args, &fctx)
1445 .unwrap();
1446 let disp_stdev_s = ctx
1447 .context
1448 .get_function("", "STDEV.S")
1449 .unwrap()
1450 .dispatch(&args, &fctx)
1451 .unwrap();
1452 let disp_stdev_p = ctx
1453 .context
1454 .get_function("", "STDEV.P")
1455 .unwrap()
1456 .dispatch(&args, &fctx)
1457 .unwrap();
1458
1459 let scalar_var_s = var_s_fn.eval_scalar(&args, &fctx).unwrap();
1461 let scalar_var_p = var_p_fn.eval_scalar(&args, &fctx).unwrap();
1462 let scalar_stdev_s = stdev_s_fn.eval_scalar(&args, &fctx).unwrap();
1463 let scalar_stdev_p = stdev_p_fn.eval_scalar(&args, &fctx).unwrap();
1464
1465 fn assert_close(a: &LiteralValue, b: &LiteralValue) {
1466 match (a, b) {
1467 (LiteralValue::Number(x), LiteralValue::Number(y)) => {
1468 assert!((x - y).abs() < 1e-12, "mismatch {x} vs {y}")
1469 }
1470 _ => assert_eq!(a, b),
1471 }
1472 }
1473 assert_close(&disp_var_s, &scalar_var_s);
1474 assert_close(&disp_var_p, &scalar_var_p);
1475 assert_close(&disp_stdev_s, &scalar_stdev_s);
1476 assert_close(&disp_stdev_p, &scalar_stdev_p);
1477 }
1478
1479 #[test]
1480 fn fold_equivalence_edge_cases() {
1481 use crate::function::Function as _;
1482 let wb = TestWorkbook::new()
1483 .with_function(std::sync::Arc::new(VarSample))
1484 .with_function(std::sync::Arc::new(VarPop))
1485 .with_function(std::sync::Arc::new(StdevSample))
1486 .with_function(std::sync::Arc::new(StdevPop));
1487 let ctx = interp(&wb);
1488 let single = arr(vec![42.0]);
1490 let args_single = [ArgumentHandle::new(&single, &ctx)];
1491 let fctx = ctx.function_context(None);
1492 let disp_var_s = ctx
1493 .context
1494 .get_function("", "VAR.S")
1495 .unwrap()
1496 .dispatch(&args_single, &fctx)
1497 .unwrap();
1498 let scalar_var_s = VarSample.eval_scalar(&args_single, &fctx).unwrap();
1499 assert_eq!(disp_var_s, scalar_var_s);
1500 let disp_var_p = ctx
1501 .context
1502 .get_function("", "VAR.P")
1503 .unwrap()
1504 .dispatch(&args_single, &fctx)
1505 .unwrap();
1506 let scalar_var_p = VarPop.eval_scalar(&args_single, &fctx).unwrap();
1507 assert_eq!(disp_var_p, scalar_var_p);
1508 let disp_stdev_p = ctx
1509 .context
1510 .get_function("", "STDEV.P")
1511 .unwrap()
1512 .dispatch(&args_single, &fctx)
1513 .unwrap();
1514 let scalar_stdev_p = StdevPop.eval_scalar(&args_single, &fctx).unwrap();
1515 assert_eq!(disp_stdev_p, scalar_stdev_p);
1516 let disp_stdev_s = ctx
1517 .context
1518 .get_function("", "STDEV.S")
1519 .unwrap()
1520 .dispatch(&args_single, &fctx)
1521 .unwrap();
1522 let scalar_stdev_s = StdevSample.eval_scalar(&args_single, &fctx).unwrap();
1523 assert_eq!(disp_stdev_s, scalar_stdev_s);
1524 }
1525
1526 #[test]
1527 fn legacy_aliases_match_modern() {
1528 let wb = TestWorkbook::new()
1529 .with_function(std::sync::Arc::new(PercentileInc))
1530 .with_function(std::sync::Arc::new(QuartileInc))
1531 .with_function(std::sync::Arc::new(RankEqFn));
1532 let ctx = interp(&wb);
1533 let arr_node = arr(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
1534 let p = ASTNode::new(ASTNodeType::Literal(LiteralValue::Number(0.4)), None);
1535 let q2 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Number(2.0)), None);
1536 let target = ASTNode::new(ASTNodeType::Literal(LiteralValue::Number(4.0)), None);
1537 let args_p = [
1538 ArgumentHandle::new(&arr_node, &ctx),
1539 ArgumentHandle::new(&p, &ctx),
1540 ];
1541 let args_q = [
1542 ArgumentHandle::new(&arr_node, &ctx),
1543 ArgumentHandle::new(&q2, &ctx),
1544 ];
1545 let args_rank = [
1546 ArgumentHandle::new(&target, &ctx),
1547 ArgumentHandle::new(&arr_node, &ctx),
1548 ];
1549 let modern_p = ctx
1550 .context
1551 .get_function("", "PERCENTILE.INC")
1552 .unwrap()
1553 .dispatch(&args_p, &ctx.function_context(None))
1554 .unwrap();
1555 let legacy_p = ctx
1556 .context
1557 .get_function("", "PERCENTILE")
1558 .unwrap()
1559 .dispatch(&args_p, &ctx.function_context(None))
1560 .unwrap();
1561 assert_eq!(modern_p, legacy_p);
1562 let modern_q = ctx
1563 .context
1564 .get_function("", "QUARTILE.INC")
1565 .unwrap()
1566 .dispatch(&args_q, &ctx.function_context(None))
1567 .unwrap();
1568 let legacy_q = ctx
1569 .context
1570 .get_function("", "QUARTILE")
1571 .unwrap()
1572 .dispatch(&args_q, &ctx.function_context(None))
1573 .unwrap();
1574 assert_eq!(modern_q, legacy_q);
1575 let modern_rank = ctx
1576 .context
1577 .get_function("", "RANK.EQ")
1578 .unwrap()
1579 .dispatch(&args_rank, &ctx.function_context(None))
1580 .unwrap();
1581 let legacy_rank = ctx
1582 .context
1583 .get_function("", "RANK")
1584 .unwrap()
1585 .dispatch(&args_rank, &ctx.function_context(None))
1586 .unwrap();
1587 assert_eq!(modern_rank, legacy_rank);
1588 }
1589
1590 #[test]
1591 fn mode_single_basic_and_alias() {
1592 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(ModeSingleFn));
1593 let ctx = interp(&wb);
1594 let arr_node = arr(vec![5.0, 2.0, 2.0, 3.0, 3.0, 3.0]);
1595 let args = [ArgumentHandle::new(&arr_node, &ctx)];
1596 let mode_sngl = ctx
1597 .context
1598 .get_function("", "MODE.SNGL")
1599 .unwrap()
1600 .dispatch(&args, &ctx.function_context(None))
1601 .unwrap();
1602 assert_eq!(mode_sngl, LiteralValue::Number(3.0));
1603 let mode_alias = ctx
1604 .context
1605 .get_function("", "MODE")
1606 .unwrap()
1607 .dispatch(&args, &ctx.function_context(None))
1608 .unwrap();
1609 assert_eq!(mode_alias, mode_sngl);
1610 }
1611
1612 #[test]
1613 fn mode_single_no_duplicates() {
1614 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(ModeSingleFn));
1615 let ctx = interp(&wb);
1616 let arr_node = arr(vec![1.0, 2.0, 3.0]);
1617 let args = [ArgumentHandle::new(&arr_node, &ctx)];
1618 let out = ctx
1619 .context
1620 .get_function("", "MODE.SNGL")
1621 .unwrap()
1622 .dispatch(&args, &ctx.function_context(None))
1623 .unwrap();
1624 match out {
1625 LiteralValue::Error(e) => assert!(e.to_string().contains("#N/A")),
1626 _ => panic!("expected #N/A"),
1627 }
1628 }
1629
1630 #[test]
1631 fn mode_multi_basic() {
1632 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(ModeMultiFn));
1633 let ctx = interp(&wb);
1634 let arr_node = arr(vec![2.0, 3.0, 2.0, 4.0, 3.0, 5.0, 2.0, 3.0]);
1635 let args = [ArgumentHandle::new(&arr_node, &ctx)];
1636 let out = ctx
1637 .context
1638 .get_function("", "MODE.MULT")
1639 .unwrap()
1640 .dispatch(&args, &ctx.function_context(None))
1641 .unwrap();
1642 let expected = LiteralValue::Array(vec![
1643 vec![LiteralValue::Number(2.0)],
1644 vec![LiteralValue::Number(3.0)],
1645 ]);
1646 assert_eq!(out, expected);
1647 }
1648
1649 #[test]
1650 fn large_small_fold_vs_scalar() {
1651 let wb = TestWorkbook::new()
1652 .with_function(std::sync::Arc::new(LARGE))
1653 .with_function(std::sync::Arc::new(SMALL));
1654 let ctx = interp(&wb);
1655 let arr_node = arr(vec![10.0, 5.0, 7.0, 12.0, 9.0]);
1656 let k_node = ASTNode::new(ASTNodeType::Literal(LiteralValue::Number(2.0)), None);
1657 let args = [
1658 ArgumentHandle::new(&arr_node, &ctx),
1659 ArgumentHandle::new(&k_node, &ctx),
1660 ];
1661 let f_large = ctx.context.get_function("", "LARGE").unwrap();
1662 let disp_large = f_large
1663 .dispatch(&args, &ctx.function_context(None))
1664 .unwrap();
1665 let scalar_large = LARGE
1666 .eval_scalar(&args, &ctx.function_context(None))
1667 .unwrap();
1668 assert_eq!(disp_large, scalar_large);
1669 let f_small = ctx.context.get_function("", "SMALL").unwrap();
1670 let disp_small = f_small
1671 .dispatch(&args, &ctx.function_context(None))
1672 .unwrap();
1673 let scalar_small = SMALL
1674 .eval_scalar(&args, &ctx.function_context(None))
1675 .unwrap();
1676 assert_eq!(disp_small, scalar_small);
1677 }
1678
1679 #[test]
1680 fn mode_fold_vs_scalar() {
1681 let wb = TestWorkbook::new()
1682 .with_function(std::sync::Arc::new(ModeSingleFn))
1683 .with_function(std::sync::Arc::new(ModeMultiFn));
1684 let ctx = interp(&wb);
1685 let arr_node = arr(vec![2.0, 3.0, 2.0, 4.0, 3.0, 3.0, 2.0]);
1686 let args = [ArgumentHandle::new(&arr_node, &ctx)];
1687 let f_single = ctx.context.get_function("", "MODE.SNGL").unwrap();
1688 let disp_single = f_single
1689 .dispatch(&args, &ctx.function_context(None))
1690 .unwrap();
1691 let scalar_single = ModeSingleFn
1692 .eval_scalar(&args, &ctx.function_context(None))
1693 .unwrap();
1694 assert_eq!(disp_single, scalar_single);
1695 let f_multi = ctx.context.get_function("", "MODE.MULT").unwrap();
1696 let disp_multi = f_multi
1697 .dispatch(&args, &ctx.function_context(None))
1698 .unwrap();
1699 let scalar_multi = ModeMultiFn
1700 .eval_scalar(&args, &ctx.function_context(None))
1701 .unwrap();
1702 assert_eq!(disp_multi, scalar_multi);
1703 }
1704
1705 #[test]
1706 fn median_fold_vs_scalar_even() {
1707 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(MEDIAN));
1708 let ctx = interp(&wb);
1709 let arr_node = arr(vec![7.0, 1.0, 9.0, 5.0]); let args = [ArgumentHandle::new(&arr_node, &ctx)];
1711 let f_med = ctx.context.get_function("", "MEDIAN").unwrap();
1712 let disp = f_med.dispatch(&args, &ctx.function_context(None)).unwrap();
1713 let scalar = MEDIAN
1714 .eval_scalar(&args, &ctx.function_context(None))
1715 .unwrap();
1716 assert_eq!(disp, scalar);
1717 assert_eq!(disp, LiteralValue::Number(6.0));
1718 }
1719
1720 #[test]
1721 fn median_fold_vs_scalar_odd() {
1722 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(MEDIAN));
1723 let ctx = interp(&wb);
1724 let arr_node = arr(vec![9.0, 2.0, 5.0]); let args = [ArgumentHandle::new(&arr_node, &ctx)];
1726 let f_med = ctx.context.get_function("", "MEDIAN").unwrap();
1727 let disp = f_med.dispatch(&args, &ctx.function_context(None)).unwrap();
1728 let scalar = MEDIAN
1729 .eval_scalar(&args, &ctx.function_context(None))
1730 .unwrap();
1731 assert_eq!(disp, scalar);
1732 assert_eq!(disp, LiteralValue::Number(5.0));
1733 }
1734
1735 #[test]
1737 fn percentile_inc_edges() {
1738 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(PercentileInc));
1739 let ctx = interp(&wb);
1740 let arr_node = arr(vec![10.0, 20.0, 30.0, 40.0]);
1741 let p0 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Number(0.0)), None);
1742 let p1 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Number(1.0)), None);
1743 let f = ctx.context.get_function("", "PERCENTILE.INC").unwrap();
1744 let args0 = [
1745 ArgumentHandle::new(&arr_node, &ctx),
1746 ArgumentHandle::new(&p0, &ctx),
1747 ];
1748 let args1 = [
1749 ArgumentHandle::new(&arr_node, &ctx),
1750 ArgumentHandle::new(&p1, &ctx),
1751 ];
1752 assert_eq!(
1753 f.dispatch(&args0, &ctx.function_context(None)).unwrap(),
1754 LiteralValue::Number(10.0)
1755 );
1756 assert_eq!(
1757 f.dispatch(&args1, &ctx.function_context(None)).unwrap(),
1758 LiteralValue::Number(40.0)
1759 );
1760 }
1761
1762 #[test]
1763 fn percentile_exc_invalid() {
1764 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(PercentileExc));
1765 let ctx = interp(&wb);
1766 let arr_node = arr(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
1767 let p_bad0 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Number(0.0)), None);
1768 let p_bad1 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Number(1.0)), None);
1769 let f = ctx.context.get_function("", "PERCENTILE.EXC").unwrap();
1770 for bad in [&p_bad0, &p_bad1] {
1771 let args = [
1772 ArgumentHandle::new(&arr_node, &ctx),
1773 ArgumentHandle::new(bad, &ctx),
1774 ];
1775 match f.dispatch(&args, &ctx.function_context(None)).unwrap() {
1776 LiteralValue::Error(e) => assert!(e.to_string().contains("#NUM!")),
1777 other => panic!("expected #NUM! got {other:?}"),
1778 }
1779 }
1780 }
1781
1782 #[test]
1783 fn quartile_invalids() {
1784 let wb = TestWorkbook::new()
1785 .with_function(std::sync::Arc::new(QuartileInc))
1786 .with_function(std::sync::Arc::new(QuartileExc));
1787 let ctx = interp(&wb);
1788 let arr_node = arr(vec![1.0, 2.0, 3.0]);
1789 let q5 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Number(5.0)), None);
1791 let args_bad_inc = [
1792 ArgumentHandle::new(&arr_node, &ctx),
1793 ArgumentHandle::new(&q5, &ctx),
1794 ];
1795 match ctx
1796 .context
1797 .get_function("", "QUARTILE.INC")
1798 .unwrap()
1799 .dispatch(&args_bad_inc, &ctx.function_context(None))
1800 .unwrap()
1801 {
1802 LiteralValue::Error(e) => assert!(e.to_string().contains("#NUM!")),
1803 other => panic!("expected #NUM! {other:?}"),
1804 }
1805 let q0 = ASTNode::new(ASTNodeType::Literal(LiteralValue::Number(0.0)), None);
1807 let args_bad_exc = [
1808 ArgumentHandle::new(&arr_node, &ctx),
1809 ArgumentHandle::new(&q0, &ctx),
1810 ];
1811 match ctx
1812 .context
1813 .get_function("", "QUARTILE.EXC")
1814 .unwrap()
1815 .dispatch(&args_bad_exc, &ctx.function_context(None))
1816 .unwrap()
1817 {
1818 LiteralValue::Error(e) => assert!(e.to_string().contains("#NUM!")),
1819 other => panic!("expected #NUM! {other:?}"),
1820 }
1821 }
1822
1823 #[test]
1824 fn rank_target_not_found() {
1825 let wb = TestWorkbook::new()
1826 .with_function(std::sync::Arc::new(RankEqFn))
1827 .with_function(std::sync::Arc::new(RankAvgFn));
1828 let ctx = interp(&wb);
1829 let arr_node = arr(vec![1.0, 2.0, 3.0]);
1830 let target = ASTNode::new(ASTNodeType::Literal(LiteralValue::Number(4.0)), None);
1831 let args = [
1832 ArgumentHandle::new(&target, &ctx),
1833 ArgumentHandle::new(&arr_node, &ctx),
1834 ];
1835 for name in ["RANK.EQ", "RANK.AVG"] {
1836 match ctx
1837 .context
1838 .get_function("", name)
1839 .unwrap()
1840 .dispatch(&args, &ctx.function_context(None))
1841 .unwrap()
1842 {
1843 LiteralValue::Error(e) => assert!(e.to_string().contains("#N/A")),
1844 other => panic!("expected #N/A {other:?}"),
1845 }
1846 }
1847 }
1848
1849 #[test]
1850 fn mode_mult_ordering() {
1851 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(ModeMultiFn));
1852 let ctx = interp(&wb);
1853 let arr_node = arr(vec![5.0, 2.0, 2.0, 5.0, 3.0, 7.0, 5.0, 2.0]); let args = [ArgumentHandle::new(&arr_node, &ctx)];
1856 let out = ctx
1857 .context
1858 .get_function("", "MODE.MULT")
1859 .unwrap()
1860 .dispatch(&args, &ctx.function_context(None))
1861 .unwrap();
1862 match out {
1863 LiteralValue::Array(rows) => {
1864 let vals: Vec<f64> = rows
1865 .into_iter()
1866 .map(|r| {
1867 if let LiteralValue::Number(n) = r[0] {
1868 n
1869 } else {
1870 panic!("expected number")
1871 }
1872 })
1873 .collect();
1874 assert_eq!(vals, vec![2.0, 5.0]);
1875 }
1876 other => panic!("expected array {other:?}"),
1877 }
1878 }
1879
1880 #[test]
1881 fn boolean_and_text_in_range_are_ignored() {
1882 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(StdevPop));
1883 let ctx = interp(&wb);
1884 let mixed = ASTNode::new(
1885 ASTNodeType::Literal(LiteralValue::Array(vec![vec![
1886 LiteralValue::Number(1.0),
1887 LiteralValue::Text("ABC".into()),
1888 LiteralValue::Boolean(true),
1889 LiteralValue::Number(4.0),
1890 ]])),
1891 None,
1892 );
1893 let f = ctx.context.get_function("", "STDEV.P").unwrap();
1894 let out = f
1895 .dispatch(
1896 &[ArgumentHandle::new(&mixed, &ctx)],
1897 &ctx.function_context(None),
1898 )
1899 .unwrap();
1900 match out {
1903 LiteralValue::Number(v) => {
1904 assert!((v - 2f64.sqrt()).abs() < 1e-12, "expected sqrt(2) got {v}")
1905 }
1906 other => panic!("unexpected {other:?}"),
1907 }
1908 }
1909
1910 #[test]
1911 fn boolean_direct_arg_coerces() {
1912 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(StdevPop));
1913 let ctx = interp(&wb);
1914 let one = ASTNode::new(ASTNodeType::Literal(LiteralValue::Number(1.0)), None);
1915 let t = ASTNode::new(ASTNodeType::Literal(LiteralValue::Boolean(true)), None);
1916 let f = ctx.context.get_function("", "STDEV.P").unwrap();
1917 let args = [
1918 ArgumentHandle::new(&one, &ctx),
1919 ArgumentHandle::new(&t, &ctx),
1920 ];
1921 let out = f.dispatch(&args, &ctx.function_context(None)).unwrap();
1922 assert_eq!(out, LiteralValue::Number(0.0));
1923 }
1924}