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