1use std::collections::HashSet;
4
5use serde_json::{Number, Value};
6
7use crate::functions::{Function, number_value};
8use crate::interpreter::SearchResult;
9use crate::registry::register_if_enabled;
10use crate::{Context, Runtime, arg, defn};
11
12const MAX_FLOAT_PRECISION: usize = 100;
16const MAX_HISTOGRAM_BINS: usize = 1_000_000;
18
19pub fn register_filtered(runtime: &mut Runtime, enabled: &HashSet<&str>) {
21 register_if_enabled(runtime, "round", enabled, Box::new(RoundFn::new()));
22 register_if_enabled(runtime, "floor_fn", enabled, Box::new(FloorFnExt::new()));
23 register_if_enabled(runtime, "ceil_fn", enabled, Box::new(CeilFnExt::new()));
24 register_if_enabled(runtime, "abs_fn", enabled, Box::new(AbsFnExt::new()));
25 register_if_enabled(runtime, "mod_fn", enabled, Box::new(ModFn::new()));
26 register_if_enabled(runtime, "pow", enabled, Box::new(PowFn::new()));
27 register_if_enabled(runtime, "sqrt", enabled, Box::new(SqrtFn::new()));
28 register_if_enabled(runtime, "log", enabled, Box::new(LogFn::new()));
29 register_if_enabled(runtime, "clamp", enabled, Box::new(ClampFn::new()));
30 register_if_enabled(runtime, "median", enabled, Box::new(MedianFn::new()));
31 register_if_enabled(
32 runtime,
33 "percentile",
34 enabled,
35 Box::new(PercentileFn::new()),
36 );
37 register_if_enabled(runtime, "variance", enabled, Box::new(VarianceFn::new()));
38 register_if_enabled(runtime, "stddev", enabled, Box::new(StddevFn::new()));
39 register_if_enabled(runtime, "sin", enabled, Box::new(SinFn::new()));
40 register_if_enabled(runtime, "cos", enabled, Box::new(CosFn::new()));
41 register_if_enabled(runtime, "tan", enabled, Box::new(TanFn::new()));
42 register_if_enabled(runtime, "asin", enabled, Box::new(AsinFn::new()));
43 register_if_enabled(runtime, "acos", enabled, Box::new(AcosFn::new()));
44 register_if_enabled(runtime, "atan", enabled, Box::new(AtanFn::new()));
45 register_if_enabled(runtime, "atan2", enabled, Box::new(Atan2Fn::new()));
46 register_if_enabled(runtime, "deg_to_rad", enabled, Box::new(DegToRadFn::new()));
47 register_if_enabled(runtime, "rad_to_deg", enabled, Box::new(RadToDegFn::new()));
48 register_if_enabled(runtime, "sign", enabled, Box::new(SignFn::new()));
49 register_if_enabled(runtime, "add", enabled, Box::new(AddFn::new()));
50 register_if_enabled(runtime, "subtract", enabled, Box::new(SubtractFn::new()));
51 register_if_enabled(runtime, "multiply", enabled, Box::new(MultiplyFn::new()));
52 register_if_enabled(runtime, "divide", enabled, Box::new(DivideFn::new()));
53 register_if_enabled(runtime, "mode", enabled, Box::new(ModeFn::new()));
54 register_if_enabled(runtime, "to_fixed", enabled, Box::new(ToFixedFn::new()));
55 register_if_enabled(
56 runtime,
57 "format_number",
58 enabled,
59 Box::new(FormatNumberFn::new()),
60 );
61 register_if_enabled(runtime, "histogram", enabled, Box::new(HistogramFn::new()));
62 register_if_enabled(runtime, "normalize", enabled, Box::new(NormalizeFn::new()));
63 register_if_enabled(runtime, "z_score", enabled, Box::new(ZScoreFn::new()));
64 register_if_enabled(
65 runtime,
66 "correlation",
67 enabled,
68 Box::new(CorrelationFn::new()),
69 );
70 register_if_enabled(runtime, "quantile", enabled, Box::new(QuantileFn::new()));
71 register_if_enabled(runtime, "moving_avg", enabled, Box::new(MovingAvgFn::new()));
72 register_if_enabled(runtime, "ewma", enabled, Box::new(EwmaFn::new()));
73 register_if_enabled(
74 runtime,
75 "covariance",
76 enabled,
77 Box::new(CovarianceFn::new()),
78 );
79 register_if_enabled(
80 runtime,
81 "standardize",
82 enabled,
83 Box::new(StandardizeFn::new()),
84 );
85 register_if_enabled(runtime, "quartiles", enabled, Box::new(QuartilesFn::new()));
86 register_if_enabled(
87 runtime,
88 "outliers_iqr",
89 enabled,
90 Box::new(OutliersIqrFn::new()),
91 );
92 register_if_enabled(
93 runtime,
94 "outliers_zscore",
95 enabled,
96 Box::new(OutliersZscoreFn::new()),
97 );
98 register_if_enabled(runtime, "skew", enabled, Box::new(SkewFn::new()));
99 register_if_enabled(runtime, "kurtosis", enabled, Box::new(KurtosisFn::new()));
100 register_if_enabled(runtime, "mad", enabled, Box::new(MadFn::new()));
101 register_if_enabled(runtime, "trend", enabled, Box::new(TrendFn::new()));
103 register_if_enabled(
104 runtime,
105 "trend_slope",
106 enabled,
107 Box::new(TrendSlopeFn::new()),
108 );
109 register_if_enabled(
110 runtime,
111 "rate_of_change",
112 enabled,
113 Box::new(RateOfChangeFn::new()),
114 );
115 register_if_enabled(
116 runtime,
117 "cumulative_sum",
118 enabled,
119 Box::new(CumulativeSumFn::new()),
120 );
121}
122
123defn!(RoundFn, vec![arg!(number)], Some(arg!(number)));
128
129impl Function for RoundFn {
130 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
131 self.signature.validate(args, ctx)?;
132
133 let n = args[0].as_f64().unwrap();
134
135 let precision = if args.len() > 1 {
136 args[1].as_f64().map(|p| p as i32).unwrap_or(0)
137 } else {
138 0
139 };
140
141 let result = if precision == 0 {
142 n.round()
143 } else {
144 let multiplier = 10_f64.powi(precision);
145 (n * multiplier).round() / multiplier
146 };
147
148 Ok(number_value(result))
149 }
150}
151
152defn!(FloorFnExt, vec![arg!(number)], None);
157
158impl Function for FloorFnExt {
159 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
160 self.signature.validate(args, ctx)?;
161 let n = args[0].as_f64().unwrap();
162 Ok(Value::Number(Number::from(n.floor() as i64)))
163 }
164}
165
166defn!(CeilFnExt, vec![arg!(number)], None);
171
172impl Function for CeilFnExt {
173 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
174 self.signature.validate(args, ctx)?;
175 let n = args[0].as_f64().unwrap();
176 Ok(Value::Number(Number::from(n.ceil() as i64)))
177 }
178}
179
180defn!(AbsFnExt, vec![arg!(number)], None);
185
186impl Function for AbsFnExt {
187 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
188 self.signature.validate(args, ctx)?;
189 let n = args[0].as_f64().unwrap();
190 Ok(number_value(n.abs()))
191 }
192}
193
194defn!(ModFn, vec![arg!(number), arg!(number)], None);
199
200impl Function for ModFn {
201 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
202 self.signature.validate(args, ctx)?;
203
204 let n = args[0].as_f64().unwrap();
205 let divisor = args[1].as_f64().unwrap();
206
207 if divisor == 0.0 {
208 return Err(crate::functions::custom_error(ctx, "Division by zero"));
209 }
210
211 Ok(number_value(n % divisor))
212 }
213}
214
215defn!(PowFn, vec![arg!(number), arg!(number)], None);
220
221impl Function for PowFn {
222 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
223 self.signature.validate(args, ctx)?;
224 let base = args[0].as_f64().unwrap();
225 let exp = args[1].as_f64().unwrap();
226 Ok(number_value(base.powf(exp)))
227 }
228}
229
230defn!(SqrtFn, vec![arg!(number)], None);
235
236impl Function for SqrtFn {
237 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
238 self.signature.validate(args, ctx)?;
239 let n = args[0].as_f64().unwrap();
240
241 if n < 0.0 {
242 return Err(crate::functions::custom_error(
243 ctx,
244 "Cannot take square root of negative number",
245 ));
246 }
247
248 Ok(number_value(n.sqrt()))
249 }
250}
251
252defn!(LogFn, vec![arg!(number)], Some(arg!(number)));
257
258impl Function for LogFn {
259 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
260 self.signature.validate(args, ctx)?;
261
262 let n = args[0].as_f64().unwrap();
263
264 if n <= 0.0 {
265 return Err(crate::functions::custom_error(
266 ctx,
267 "Logarithm requires positive number",
268 ));
269 }
270
271 let result = if args.len() > 1 {
272 let base = args[1].as_f64().unwrap();
273 n.log(base)
274 } else {
275 n.ln()
276 };
277
278 Ok(number_value(result))
279 }
280}
281
282defn!(
287 ClampFn,
288 vec![arg!(number), arg!(number), arg!(number)],
289 None
290);
291
292impl Function for ClampFn {
293 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
294 self.signature.validate(args, ctx)?;
295
296 let n = args[0].as_f64().unwrap();
297 let min = args[1].as_f64().unwrap();
298 let max = args[2].as_f64().unwrap();
299
300 let result = n.max(min).min(max);
301
302 Ok(number_value(result))
303 }
304}
305
306defn!(MedianFn, vec![arg!(array)], None);
311
312impl Function for MedianFn {
313 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
314 self.signature.validate(args, ctx)?;
315
316 let arr = args[0].as_array().unwrap();
317
318 let mut numbers: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
319
320 if numbers.is_empty() {
321 return Ok(Value::Null);
322 }
323
324 numbers.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
325
326 let len = numbers.len();
327 let median = if len.is_multiple_of(2) {
328 (numbers[len / 2 - 1] + numbers[len / 2]) / 2.0
329 } else {
330 numbers[len / 2]
331 };
332
333 Ok(number_value(median))
334 }
335}
336
337defn!(PercentileFn, vec![arg!(array), arg!(number)], None);
342
343impl Function for PercentileFn {
344 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
345 self.signature.validate(args, ctx)?;
346
347 let arr = args[0].as_array().unwrap();
348 let p = args[1].as_f64().unwrap();
349
350 if !(0.0..=100.0).contains(&p) {
351 return Err(crate::functions::custom_error(
352 ctx,
353 "Percentile must be between 0 and 100",
354 ));
355 }
356
357 let mut numbers: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
358
359 if numbers.is_empty() {
360 return Ok(Value::Null);
361 }
362
363 numbers.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
364
365 let len = numbers.len();
366 if len == 1 {
367 return Ok(number_value(numbers[0]));
368 }
369
370 let rank = (p / 100.0) * (len - 1) as f64;
371 let lower_idx = rank.floor() as usize;
372 let upper_idx = rank.ceil() as usize;
373 let fraction = rank - lower_idx as f64;
374
375 let result = if lower_idx == upper_idx {
376 numbers[lower_idx]
377 } else {
378 numbers[lower_idx] * (1.0 - fraction) + numbers[upper_idx] * fraction
379 };
380
381 Ok(number_value(result))
382 }
383}
384
385defn!(VarianceFn, vec![arg!(array)], None);
390
391impl Function for VarianceFn {
392 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
393 self.signature.validate(args, ctx)?;
394
395 let arr = args[0].as_array().unwrap();
396
397 let numbers: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
398
399 if numbers.is_empty() {
400 return Ok(Value::Null);
401 }
402
403 let mean = numbers.iter().sum::<f64>() / numbers.len() as f64;
404 let variance =
405 numbers.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / numbers.len() as f64;
406
407 Ok(number_value(variance))
408 }
409}
410
411defn!(StddevFn, vec![arg!(array)], None);
416
417impl Function for StddevFn {
418 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
419 self.signature.validate(args, ctx)?;
420
421 let arr = args[0].as_array().unwrap();
422
423 let numbers: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
424
425 if numbers.is_empty() {
426 return Ok(Value::Null);
427 }
428
429 let mean = numbers.iter().sum::<f64>() / numbers.len() as f64;
430 let variance =
431 numbers.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / numbers.len() as f64;
432 let stddev = variance.sqrt();
433
434 Ok(number_value(stddev))
435 }
436}
437
438defn!(SinFn, vec![arg!(number)], None);
443
444impl Function for SinFn {
445 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
446 self.signature.validate(args, ctx)?;
447 let n = args[0].as_f64().unwrap();
448 Ok(number_value(n.sin()))
449 }
450}
451
452defn!(CosFn, vec![arg!(number)], None);
453
454impl Function for CosFn {
455 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
456 self.signature.validate(args, ctx)?;
457 let n = args[0].as_f64().unwrap();
458 Ok(number_value(n.cos()))
459 }
460}
461
462defn!(TanFn, vec![arg!(number)], None);
463
464impl Function for TanFn {
465 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
466 self.signature.validate(args, ctx)?;
467 let n = args[0].as_f64().unwrap();
468 Ok(number_value(n.tan()))
469 }
470}
471
472defn!(AsinFn, vec![arg!(number)], None);
473
474impl Function for AsinFn {
475 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
476 self.signature.validate(args, ctx)?;
477 let n = args[0].as_f64().unwrap();
478 let result = n.asin();
479 if result.is_nan() {
481 Ok(Value::Null)
482 } else {
483 Ok(number_value(result))
484 }
485 }
486}
487
488defn!(AcosFn, vec![arg!(number)], None);
489
490impl Function for AcosFn {
491 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
492 self.signature.validate(args, ctx)?;
493 let n = args[0].as_f64().unwrap();
494 let result = n.acos();
495 if result.is_nan() {
497 Ok(Value::Null)
498 } else {
499 Ok(number_value(result))
500 }
501 }
502}
503
504defn!(AtanFn, vec![arg!(number)], None);
505
506impl Function for AtanFn {
507 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
508 self.signature.validate(args, ctx)?;
509 let n = args[0].as_f64().unwrap();
510 Ok(number_value(n.atan()))
511 }
512}
513
514defn!(Atan2Fn, vec![arg!(number), arg!(number)], None);
515
516impl Function for Atan2Fn {
517 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
518 self.signature.validate(args, ctx)?;
519 let y = args[0].as_f64().unwrap();
520 let x = args[1].as_f64().unwrap();
521 Ok(number_value(y.atan2(x)))
522 }
523}
524
525defn!(DegToRadFn, vec![arg!(number)], None);
526
527impl Function for DegToRadFn {
528 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
529 self.signature.validate(args, ctx)?;
530 let n = args[0].as_f64().unwrap();
531 Ok(number_value(n.to_radians()))
532 }
533}
534
535defn!(RadToDegFn, vec![arg!(number)], None);
536
537impl Function for RadToDegFn {
538 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
539 self.signature.validate(args, ctx)?;
540 let n = args[0].as_f64().unwrap();
541 Ok(number_value(n.to_degrees()))
542 }
543}
544
545defn!(SignFn, vec![arg!(number)], None);
546
547impl Function for SignFn {
548 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
549 self.signature.validate(args, ctx)?;
550 let n = args[0].as_f64().unwrap();
551 let sign = if n > 0.0 {
552 1
553 } else if n < 0.0 {
554 -1
555 } else {
556 0
557 };
558 Ok(Value::Number(Number::from(sign)))
559 }
560}
561
562defn!(AddFn, vec![arg!(number), arg!(number)], None);
567
568impl Function for AddFn {
569 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
570 self.signature.validate(args, ctx)?;
571 let a = args[0].as_f64().unwrap();
572 let b = args[1].as_f64().unwrap();
573 Ok(number_value(a + b))
574 }
575}
576
577defn!(SubtractFn, vec![arg!(number), arg!(number)], None);
582
583impl Function for SubtractFn {
584 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
585 self.signature.validate(args, ctx)?;
586 let a = args[0].as_f64().unwrap();
587 let b = args[1].as_f64().unwrap();
588 Ok(number_value(a - b))
589 }
590}
591
592defn!(MultiplyFn, vec![arg!(number), arg!(number)], None);
597
598impl Function for MultiplyFn {
599 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
600 self.signature.validate(args, ctx)?;
601 let a = args[0].as_f64().unwrap();
602 let b = args[1].as_f64().unwrap();
603 Ok(number_value(a * b))
604 }
605}
606
607defn!(DivideFn, vec![arg!(number), arg!(number)], None);
612
613impl Function for DivideFn {
614 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
615 self.signature.validate(args, ctx)?;
616 let a = args[0].as_f64().unwrap();
617 let b = args[1].as_f64().unwrap();
618 if b == 0.0 {
619 return Err(crate::functions::custom_error(ctx, "Division by zero"));
620 }
621 Ok(number_value(a / b))
622 }
623}
624
625defn!(ModeFn, vec![arg!(array)], None);
630
631impl Function for ModeFn {
632 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
633 self.signature.validate(args, ctx)?;
634
635 let arr = args[0].as_array().unwrap();
636
637 if arr.is_empty() {
638 return Ok(Value::Null);
639 }
640
641 let mut counts: std::collections::HashMap<String, (usize, Value)> =
643 std::collections::HashMap::new();
644
645 for item in arr.iter() {
646 let key = serde_json::to_string(item).unwrap_or_default();
647 counts
648 .entry(key)
649 .and_modify(|(count, _)| *count += 1)
650 .or_insert((1, item.clone()));
651 }
652
653 let (_, (_, mode_value)) = counts
655 .into_iter()
656 .max_by_key(|(_, (count, _))| *count)
657 .unwrap();
658
659 Ok(mode_value)
660 }
661}
662
663defn!(ToFixedFn, vec![arg!(number), arg!(number)], None);
668
669impl Function for ToFixedFn {
670 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
671 self.signature.validate(args, ctx)?;
672
673 let num = args[0].as_f64().unwrap();
674 let precision = (args[1].as_f64().unwrap() as usize).min(MAX_FLOAT_PRECISION);
677
678 let result = format!("{:.prec$}", num, prec = precision);
679 Ok(Value::String(result))
680 }
681}
682
683defn!(FormatNumberFn, vec![arg!(number)], Some(arg!(any)));
688
689impl Function for FormatNumberFn {
690 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
691 self.signature.validate(args, ctx)?;
692
693 let num = args[0].as_f64().unwrap();
694
695 let precision = args
696 .get(1)
697 .and_then(|v| v.as_f64())
698 .map(|n| n as usize)
699 .unwrap_or(0);
700
701 let suffix = args.get(2).and_then(|v| v.as_str()).map(|s| s.to_string());
702
703 let (scaled_num, auto_suffix) = if let Some(ref s) = suffix {
705 match s.as_str() {
706 "k" | "K" => (num / 1_000.0, "k"),
707 "M" => (num / 1_000_000.0, "M"),
708 "B" => (num / 1_000_000_000.0, "B"),
709 "T" => (num / 1_000_000_000_000.0, "T"),
710 "auto" => {
711 let abs_num = num.abs();
712 if abs_num >= 1_000_000_000_000.0 {
713 (num / 1_000_000_000_000.0, "T")
714 } else if abs_num >= 1_000_000_000.0 {
715 (num / 1_000_000_000.0, "B")
716 } else if abs_num >= 1_000_000.0 {
717 (num / 1_000_000.0, "M")
718 } else if abs_num >= 1_000.0 {
719 (num / 1_000.0, "k")
720 } else {
721 (num, "")
722 }
723 }
724 _ => (num, s.as_str()),
725 }
726 } else {
727 (num, "")
728 };
729
730 let formatted = format!("{:.prec$}", scaled_num, prec = precision);
732
733 let result = if suffix.is_none() || suffix.as_deref() == Some("") {
735 add_thousand_separators(&formatted)
736 } else {
737 format!("{}{}", formatted, auto_suffix)
738 };
739
740 Ok(Value::String(result))
741 }
742}
743
744fn add_thousand_separators(s: &str) -> String {
746 let parts: Vec<&str> = s.split('.').collect();
747 let int_part = parts[0];
748 let dec_part = parts.get(1);
749
750 let (sign, digits) = if let Some(stripped) = int_part.strip_prefix('-') {
752 ("-", stripped)
753 } else {
754 ("", int_part)
755 };
756
757 let digit_chars: Vec<char> = digits.chars().collect();
759 let len = digit_chars.len();
760 let with_commas: String = digit_chars
761 .iter()
762 .enumerate()
763 .map(|(i, c)| {
764 let pos_from_right = len - 1 - i;
765 if pos_from_right > 0 && pos_from_right.is_multiple_of(3) {
766 format!("{},", c)
767 } else {
768 c.to_string()
769 }
770 })
771 .collect();
772
773 match dec_part {
774 Some(dec) => format!("{}{}.{}", sign, with_commas, dec),
775 None => format!("{}{}", sign, with_commas),
776 }
777}
778
779defn!(HistogramFn, vec![arg!(array), arg!(number)], None);
784
785impl Function for HistogramFn {
786 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
787 self.signature.validate(args, ctx)?;
788
789 let arr = args[0].as_array().unwrap();
790
791 let num_bins = args[1].as_f64().unwrap() as usize;
792
793 if num_bins > MAX_HISTOGRAM_BINS {
794 return Err(crate::functions::custom_error(
795 ctx,
796 "Number of bins exceeds the maximum allowed",
797 ));
798 }
799
800 if num_bins == 0 {
801 return Err(crate::functions::custom_error(
802 ctx,
803 "Number of bins must be greater than 0",
804 ));
805 }
806
807 let values: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
809
810 if values.is_empty() {
811 return Ok(Value::Array(vec![]));
812 }
813
814 let min_val = values.iter().cloned().fold(f64::INFINITY, f64::min);
815 let max_val = values.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
816
817 let bin_width = if (max_val - min_val).abs() < f64::EPSILON {
819 1.0
820 } else {
821 (max_val - min_val) / num_bins as f64
822 };
823
824 let mut bins: Vec<(f64, f64, usize)> = (0..num_bins)
826 .map(|i| {
827 let bin_min = min_val + (i as f64 * bin_width);
828 let bin_max = if i == num_bins - 1 {
829 max_val
830 } else {
831 min_val + ((i + 1) as f64 * bin_width)
832 };
833 (bin_min, bin_max, 0)
834 })
835 .collect();
836
837 for val in &values {
839 let bin_idx = if (max_val - min_val).abs() < f64::EPSILON {
840 0
841 } else {
842 let idx = ((val - min_val) / bin_width) as usize;
843 idx.min(num_bins - 1)
844 };
845 bins[bin_idx].2 += 1;
846 }
847
848 let result: Vec<Value> = bins
850 .into_iter()
851 .map(|(bin_min, bin_max, count)| {
852 let mut map = serde_json::Map::new();
853 map.insert("min".to_string(), number_value(bin_min));
854 map.insert("max".to_string(), number_value(bin_max));
855 map.insert("count".to_string(), Value::Number(Number::from(count)));
856 Value::Object(map)
857 })
858 .collect();
859
860 Ok(Value::Array(result))
861 }
862}
863
864defn!(NormalizeFn, vec![arg!(array)], None);
869
870impl Function for NormalizeFn {
871 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
872 self.signature.validate(args, ctx)?;
873
874 let arr = args[0].as_array().unwrap();
875
876 let values: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
877
878 if values.is_empty() {
879 return Ok(Value::Array(vec![]));
880 }
881
882 let min_val = values.iter().cloned().fold(f64::INFINITY, f64::min);
883 let max_val = values.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
884 let range = max_val - min_val;
885
886 let result: Vec<Value> = values
887 .iter()
888 .map(|v| {
889 let normalized = if range.abs() < f64::EPSILON {
890 0.0
891 } else {
892 (v - min_val) / range
893 };
894 number_value(normalized)
895 })
896 .collect();
897
898 Ok(Value::Array(result))
899 }
900}
901
902defn!(ZScoreFn, vec![arg!(array)], None);
907
908impl Function for ZScoreFn {
909 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
910 self.signature.validate(args, ctx)?;
911
912 let arr = args[0].as_array().unwrap();
913
914 let values: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
915
916 if values.is_empty() {
917 return Ok(Value::Array(vec![]));
918 }
919
920 let n = values.len() as f64;
921 let mean = values.iter().sum::<f64>() / n;
922 let variance = values.iter().map(|v| (v - mean).powi(2)).sum::<f64>() / n;
923 let stddev = variance.sqrt();
924
925 let result: Vec<Value> = values
926 .iter()
927 .map(|v| {
928 let z = if stddev.abs() < f64::EPSILON {
929 0.0
930 } else {
931 (v - mean) / stddev
932 };
933 number_value(z)
934 })
935 .collect();
936
937 Ok(Value::Array(result))
938 }
939}
940
941defn!(CorrelationFn, vec![arg!(array), arg!(array)], None);
946
947impl Function for CorrelationFn {
948 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
949 self.signature.validate(args, ctx)?;
950
951 let arr1 = args[0].as_array().unwrap();
952 let arr2 = args[1].as_array().unwrap();
953
954 let values1: Vec<f64> = arr1.iter().filter_map(|v| v.as_f64()).collect();
955 let values2: Vec<f64> = arr2.iter().filter_map(|v| v.as_f64()).collect();
956
957 if values1.is_empty() || values2.is_empty() {
958 return Ok(Value::Null);
959 }
960
961 let n = values1.len().min(values2.len());
963 if n == 0 {
964 return Ok(Value::Null);
965 }
966
967 let values1 = &values1[..n];
968 let values2 = &values2[..n];
969
970 let mean1 = values1.iter().sum::<f64>() / n as f64;
971 let mean2 = values2.iter().sum::<f64>() / n as f64;
972
973 let mut cov = 0.0;
974 let mut var1 = 0.0;
975 let mut var2 = 0.0;
976
977 for i in 0..n {
978 let d1 = values1[i] - mean1;
979 let d2 = values2[i] - mean2;
980 cov += d1 * d2;
981 var1 += d1 * d1;
982 var2 += d2 * d2;
983 }
984
985 let denom = (var1 * var2).sqrt();
986 let correlation = if denom.abs() < f64::EPSILON {
987 0.0
988 } else {
989 cov / denom
990 };
991
992 Ok(number_value(correlation))
993 }
994}
995
996defn!(QuantileFn, vec![arg!(array), arg!(number)], None);
1001
1002impl Function for QuantileFn {
1003 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1004 self.signature.validate(args, ctx)?;
1005
1006 let arr = args[0].as_array().unwrap();
1007 let q = args[1].as_f64().unwrap();
1008
1009 if !(0.0..=1.0).contains(&q) {
1010 return Ok(Value::Null);
1011 }
1012
1013 let mut values: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
1014
1015 if values.is_empty() {
1016 return Ok(Value::Null);
1017 }
1018
1019 values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1020
1021 let n = values.len();
1022 let pos = q * (n - 1) as f64;
1023 let lower = pos.floor() as usize;
1024 let upper = pos.ceil() as usize;
1025 let frac = pos - lower as f64;
1026
1027 let result = if lower == upper {
1028 values[lower]
1029 } else {
1030 values[lower] * (1.0 - frac) + values[upper] * frac
1031 };
1032
1033 Ok(number_value(result))
1034 }
1035}
1036
1037defn!(MovingAvgFn, vec![arg!(array), arg!(number)], None);
1042
1043impl Function for MovingAvgFn {
1044 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1045 self.signature.validate(args, ctx)?;
1046
1047 let arr = args[0].as_array().unwrap();
1048
1049 let window = args[1].as_f64().unwrap() as usize;
1050
1051 if window == 0 {
1052 return Ok(Value::Null);
1053 }
1054
1055 let values: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
1056
1057 if values.is_empty() || window > values.len() {
1058 return Ok(Value::Array(vec![]));
1059 }
1060
1061 let mut result: Vec<Value> = Vec::new();
1062
1063 for i in 0..values.len() {
1064 if i + 1 < window {
1065 result.push(Value::Null);
1066 } else {
1067 let start = i + 1 - window;
1068 let sum: f64 = values[start..=i].iter().sum();
1069 let avg = sum / window as f64;
1070 result.push(number_value(avg));
1071 }
1072 }
1073
1074 Ok(Value::Array(result))
1075 }
1076}
1077
1078defn!(EwmaFn, vec![arg!(array), arg!(number)], None);
1083
1084impl Function for EwmaFn {
1085 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1086 self.signature.validate(args, ctx)?;
1087
1088 let arr = args[0].as_array().unwrap();
1089 let alpha = args[1].as_f64().unwrap();
1090
1091 if !(0.0..=1.0).contains(&alpha) {
1092 return Ok(Value::Null);
1093 }
1094
1095 let values: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
1096
1097 if values.is_empty() {
1098 return Ok(Value::Array(vec![]));
1099 }
1100
1101 let mut result: Vec<Value> = Vec::new();
1102 let mut ewma = values[0];
1103
1104 for value in &values {
1105 ewma = alpha * value + (1.0 - alpha) * ewma;
1106 result.push(number_value(ewma));
1107 }
1108
1109 Ok(Value::Array(result))
1110 }
1111}
1112
1113defn!(CovarianceFn, vec![arg!(array), arg!(array)], None);
1118
1119impl Function for CovarianceFn {
1120 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1121 self.signature.validate(args, ctx)?;
1122
1123 let arr1 = args[0].as_array().unwrap();
1124 let arr2 = args[1].as_array().unwrap();
1125
1126 let values1: Vec<f64> = arr1.iter().filter_map(|v| v.as_f64()).collect();
1127 let values2: Vec<f64> = arr2.iter().filter_map(|v| v.as_f64()).collect();
1128
1129 if values1.is_empty() || values1.len() != values2.len() {
1130 return Ok(Value::Null);
1131 }
1132
1133 let n = values1.len() as f64;
1134 let mean1: f64 = values1.iter().sum::<f64>() / n;
1135 let mean2: f64 = values2.iter().sum::<f64>() / n;
1136
1137 let cov: f64 = values1
1138 .iter()
1139 .zip(values2.iter())
1140 .map(|(x, y)| (x - mean1) * (y - mean2))
1141 .sum::<f64>()
1142 / n;
1143
1144 Ok(number_value(cov))
1145 }
1146}
1147
1148defn!(StandardizeFn, vec![arg!(array)], None);
1153
1154impl Function for StandardizeFn {
1155 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1156 self.signature.validate(args, ctx)?;
1157
1158 let arr = args[0].as_array().unwrap();
1159
1160 let values: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
1161
1162 if values.is_empty() {
1163 return Ok(Value::Array(vec![]));
1164 }
1165
1166 let n = values.len() as f64;
1167 let mean: f64 = values.iter().sum::<f64>() / n;
1168 let variance: f64 = values.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / n;
1169 let std_dev = variance.sqrt();
1170
1171 let result: Vec<Value> = values
1172 .iter()
1173 .map(|x| {
1174 let standardized = if std_dev.abs() < f64::EPSILON {
1175 0.0
1176 } else {
1177 (x - mean) / std_dev
1178 };
1179 number_value(standardized)
1180 })
1181 .collect();
1182
1183 Ok(Value::Array(result))
1184 }
1185}
1186
1187defn!(QuartilesFn, vec![arg!(array)], None);
1192
1193impl Function for QuartilesFn {
1194 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1195 self.signature.validate(args, ctx)?;
1196
1197 let arr = args[0].as_array().unwrap();
1198
1199 let mut values: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
1200
1201 if values.is_empty() {
1202 return Ok(Value::Null);
1203 }
1204
1205 values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1206
1207 let n = values.len();
1208 let min = values[0];
1209 let max = values[n - 1];
1210
1211 let q1 = percentile_value(&values, 25.0);
1212 let q2 = percentile_value(&values, 50.0);
1213 let q3 = percentile_value(&values, 75.0);
1214 let iqr = q3 - q1;
1215
1216 let mut result = serde_json::Map::new();
1217 result.insert("min".to_string(), number_value(min));
1218 result.insert("q1".to_string(), number_value(q1));
1219 result.insert("q2".to_string(), number_value(q2));
1220 result.insert("q3".to_string(), number_value(q3));
1221 result.insert("max".to_string(), number_value(max));
1222 result.insert("iqr".to_string(), number_value(iqr));
1223
1224 Ok(Value::Object(result))
1225 }
1226}
1227
1228fn percentile_value(sorted_values: &[f64], p: f64) -> f64 {
1229 let n = sorted_values.len();
1230 if n == 1 {
1231 return sorted_values[0];
1232 }
1233
1234 let k = (p / 100.0) * (n - 1) as f64;
1235 let f = k.floor() as usize;
1236 let c = k.ceil() as usize;
1237
1238 if f == c {
1239 sorted_values[f]
1240 } else {
1241 let d = k - f as f64;
1242 sorted_values[f] * (1.0 - d) + sorted_values[c] * d
1243 }
1244}
1245
1246defn!(OutliersIqrFn, vec![arg!(array)], Some(arg!(number)));
1251
1252impl Function for OutliersIqrFn {
1253 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1254 self.signature.validate(args, ctx)?;
1255
1256 let arr = args[0].as_array().unwrap();
1257
1258 let multiplier = if args.len() > 1 {
1259 args[1].as_f64().unwrap_or(1.5)
1260 } else {
1261 1.5
1262 };
1263
1264 let mut values: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
1265
1266 if values.is_empty() {
1267 return Ok(Value::Array(vec![]));
1268 }
1269
1270 values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1271
1272 let q1 = percentile_value(&values, 25.0);
1273 let q3 = percentile_value(&values, 75.0);
1274 let iqr = q3 - q1;
1275
1276 let lower_bound = q1 - multiplier * iqr;
1277 let upper_bound = q3 + multiplier * iqr;
1278
1279 let outliers: Vec<Value> = arr
1280 .iter()
1281 .filter(|v| {
1282 if let Some(n) = v.as_f64() {
1283 n < lower_bound || n > upper_bound
1284 } else {
1285 false
1286 }
1287 })
1288 .cloned()
1289 .collect();
1290
1291 Ok(Value::Array(outliers))
1292 }
1293}
1294
1295defn!(OutliersZscoreFn, vec![arg!(array)], Some(arg!(number)));
1300
1301impl Function for OutliersZscoreFn {
1302 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1303 self.signature.validate(args, ctx)?;
1304
1305 let arr = args[0].as_array().unwrap();
1306
1307 let threshold = if args.len() > 1 {
1308 args[1].as_f64().unwrap_or(2.0)
1309 } else {
1310 2.0
1311 };
1312
1313 let values: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
1314
1315 if values.is_empty() {
1316 return Ok(Value::Array(vec![]));
1317 }
1318
1319 let n = values.len() as f64;
1320 let mean: f64 = values.iter().sum::<f64>() / n;
1321 let variance: f64 = values.iter().map(|v| (v - mean).powi(2)).sum::<f64>() / n;
1322 let stddev = variance.sqrt();
1323
1324 if stddev.abs() < f64::EPSILON {
1325 return Ok(Value::Array(vec![]));
1326 }
1327
1328 let outliers: Vec<Value> = arr
1329 .iter()
1330 .filter(|v| {
1331 if let Some(n) = v.as_f64() {
1332 let z = (n - mean) / stddev;
1333 z.abs() > threshold
1334 } else {
1335 false
1336 }
1337 })
1338 .cloned()
1339 .collect();
1340
1341 Ok(Value::Array(outliers))
1342 }
1343}
1344
1345defn!(TrendFn, vec![arg!(array)], None);
1350
1351impl Function for TrendFn {
1352 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1353 self.signature.validate(args, ctx)?;
1354
1355 let arr = args[0].as_array().unwrap();
1356
1357 if arr.len() < 2 {
1358 return Ok(Value::String("stable".to_string()));
1359 }
1360
1361 let values: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
1362
1363 if values.len() < 2 {
1364 return Ok(Value::String("stable".to_string()));
1365 }
1366
1367 let n = values.len() as f64;
1369 let sum_x: f64 = (0..values.len()).map(|i| i as f64).sum();
1370 let sum_y: f64 = values.iter().sum();
1371 let sum_xy: f64 = values.iter().enumerate().map(|(i, y)| i as f64 * y).sum();
1372 let sum_x2: f64 = (0..values.len()).map(|i| (i as f64).powi(2)).sum();
1373
1374 let slope = (n * sum_xy - sum_x * sum_y) / (n * sum_x2 - sum_x.powi(2));
1375
1376 let mean: f64 = sum_y / n;
1378 let threshold = mean.abs() * 0.01;
1379
1380 let trend = if slope > threshold {
1381 "increasing"
1382 } else if slope < -threshold {
1383 "decreasing"
1384 } else {
1385 "stable"
1386 };
1387
1388 Ok(Value::String(trend.to_string()))
1389 }
1390}
1391
1392defn!(TrendSlopeFn, vec![arg!(array)], None);
1397
1398impl Function for TrendSlopeFn {
1399 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1400 self.signature.validate(args, ctx)?;
1401
1402 let arr = args[0].as_array().unwrap();
1403
1404 if arr.len() < 2 {
1405 return Ok(number_value(0.0));
1406 }
1407
1408 let values: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
1409
1410 if values.len() < 2 {
1411 return Ok(number_value(0.0));
1412 }
1413
1414 let n = values.len() as f64;
1416 let sum_x: f64 = (0..values.len()).map(|i| i as f64).sum();
1417 let sum_y: f64 = values.iter().sum();
1418 let sum_xy: f64 = values.iter().enumerate().map(|(i, y)| i as f64 * y).sum();
1419 let sum_x2: f64 = (0..values.len()).map(|i| (i as f64).powi(2)).sum();
1420
1421 let slope = (n * sum_xy - sum_x * sum_y) / (n * sum_x2 - sum_x.powi(2));
1422
1423 Ok(number_value(slope))
1424 }
1425}
1426
1427defn!(RateOfChangeFn, vec![arg!(array)], None);
1432
1433impl Function for RateOfChangeFn {
1434 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1435 self.signature.validate(args, ctx)?;
1436
1437 let arr = args[0].as_array().unwrap();
1438
1439 if arr.len() < 2 {
1440 return Ok(Value::Array(vec![]));
1441 }
1442
1443 let values: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
1444
1445 if values.len() < 2 {
1446 return Ok(Value::Array(vec![]));
1447 }
1448
1449 let changes: Vec<Value> = values
1450 .windows(2)
1451 .map(|w| {
1452 let prev = w[0];
1453 let curr = w[1];
1454 let pct_change = if prev != 0.0 {
1455 ((curr - prev) / prev) * 100.0
1456 } else {
1457 0.0
1458 };
1459 number_value(pct_change)
1460 })
1461 .collect();
1462
1463 Ok(Value::Array(changes))
1464 }
1465}
1466
1467defn!(CumulativeSumFn, vec![arg!(array)], None);
1472
1473impl Function for CumulativeSumFn {
1474 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1475 self.signature.validate(args, ctx)?;
1476
1477 let arr = args[0].as_array().unwrap();
1478
1479 let values: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
1480
1481 let mut running_sum = 0.0;
1482 let cumsum: Vec<Value> = values
1483 .iter()
1484 .map(|v| {
1485 running_sum += v;
1486 number_value(running_sum)
1487 })
1488 .collect();
1489
1490 Ok(Value::Array(cumsum))
1491 }
1492}
1493
1494defn!(SkewFn, vec![arg!(array)], None);
1499
1500impl Function for SkewFn {
1501 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1502 self.signature.validate(args, ctx)?;
1503 let arr = args[0].as_array().unwrap();
1504 let numbers: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
1505 let n = numbers.len();
1506 if n < 3 {
1507 return Ok(Value::Null);
1508 }
1509 let mean = numbers.iter().sum::<f64>() / n as f64;
1510 let m2: f64 = numbers.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / n as f64;
1511 let m3: f64 = numbers.iter().map(|x| (x - mean).powi(3)).sum::<f64>() / n as f64;
1512 if m2 == 0.0 {
1513 return Ok(number_value(0.0));
1514 }
1515 let skewness = m3 / m2.powf(1.5);
1516 Ok(number_value(skewness))
1517 }
1518}
1519
1520defn!(KurtosisFn, vec![arg!(array)], None);
1525
1526impl Function for KurtosisFn {
1527 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1528 self.signature.validate(args, ctx)?;
1529 let arr = args[0].as_array().unwrap();
1530 let numbers: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
1531 let n = numbers.len();
1532 if n < 4 {
1533 return Ok(Value::Null);
1534 }
1535 let mean = numbers.iter().sum::<f64>() / n as f64;
1536 let m2: f64 = numbers.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / n as f64;
1537 let m4: f64 = numbers.iter().map(|x| (x - mean).powi(4)).sum::<f64>() / n as f64;
1538 if m2 == 0.0 {
1539 return Ok(number_value(0.0));
1540 }
1541 let kurt = (m4 / (m2 * m2)) - 3.0;
1542 Ok(number_value(kurt))
1543 }
1544}
1545
1546defn!(MadFn, vec![arg!(array)], None);
1551
1552impl Function for MadFn {
1553 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
1554 self.signature.validate(args, ctx)?;
1555 let arr = args[0].as_array().unwrap();
1556 let mut numbers: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
1557 if numbers.is_empty() {
1558 return Ok(Value::Null);
1559 }
1560 numbers.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1561 let len = numbers.len();
1562 let median = if len.is_multiple_of(2) {
1563 (numbers[len / 2 - 1] + numbers[len / 2]) / 2.0
1564 } else {
1565 numbers[len / 2]
1566 };
1567 let mut abs_devs: Vec<f64> = numbers.iter().map(|x| (x - median).abs()).collect();
1568 abs_devs.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1569 let mad = if len.is_multiple_of(2) {
1570 (abs_devs[len / 2 - 1] + abs_devs[len / 2]) / 2.0
1571 } else {
1572 abs_devs[len / 2]
1573 };
1574 Ok(number_value(mad))
1575 }
1576}
1577
1578#[cfg(test)]
1579mod tests {
1580 use crate::Runtime;
1581 use serde_json::json;
1582
1583 fn setup_runtime() -> Runtime {
1584 Runtime::builder()
1585 .with_standard()
1586 .with_all_extensions()
1587 .build()
1588 }
1589
1590 #[test]
1591 #[allow(clippy::approx_constant)]
1592 fn test_round() {
1593 let runtime = setup_runtime();
1594 let expr = runtime.compile("round(`3.14159`, `2`)").unwrap();
1595 let result = expr.search(&json!(null)).unwrap();
1596 assert!((result.as_f64().unwrap() - 3.14_f64).abs() < 0.001);
1597 }
1598
1599 #[test]
1600 fn test_to_fixed_clamps_huge_precision() {
1601 let runtime = setup_runtime();
1603 let expr = runtime.compile("to_fixed(`1`, `1000000000`)").unwrap();
1604 let result = expr.search(&json!(null)).unwrap();
1605 assert!(result.as_str().unwrap().len() <= 103);
1607 }
1608
1609 #[test]
1610 fn test_histogram_rejects_excessive_bins() {
1611 let runtime = setup_runtime();
1612 let expr = runtime
1613 .compile("histogram(`[1,2,3]`, `100000000`)")
1614 .unwrap();
1615 assert!(expr.search(&json!(null)).is_err());
1616 }
1617
1618 #[test]
1619 fn test_sqrt() {
1620 let runtime = setup_runtime();
1621 let expr = runtime.compile("sqrt(`16`)").unwrap();
1622 let result = expr.search(&json!(null)).unwrap();
1623 assert_eq!(result.as_f64().unwrap() as i64, 4);
1624 }
1625
1626 #[test]
1627 fn test_clamp() {
1628 let runtime = setup_runtime();
1629 let expr = runtime.compile("clamp(`5`, `0`, `3`)").unwrap();
1630 let result = expr.search(&json!(null)).unwrap();
1631 assert_eq!(result.as_f64().unwrap() as i64, 3);
1632 }
1633
1634 #[test]
1635 fn test_add() {
1636 let runtime = setup_runtime();
1637 let expr = runtime.compile("add(`1`, `2`)").unwrap();
1638 let result = expr.search(&json!(null)).unwrap();
1639 assert_eq!(result.as_f64().unwrap() as i64, 3);
1640 }
1641
1642 #[test]
1643 fn test_subtract() {
1644 let runtime = setup_runtime();
1645 let expr = runtime.compile("subtract(`10`, `3`)").unwrap();
1646 let result = expr.search(&json!(null)).unwrap();
1647 assert_eq!(result.as_f64().unwrap() as i64, 7);
1648 }
1649
1650 #[test]
1651 fn test_multiply() {
1652 let runtime = setup_runtime();
1653 let expr = runtime.compile("multiply(`4`, `5`)").unwrap();
1654 let result = expr.search(&json!(null)).unwrap();
1655 assert_eq!(result.as_f64().unwrap() as i64, 20);
1656 }
1657
1658 #[test]
1659 fn test_divide() {
1660 let runtime = setup_runtime();
1661 let expr = runtime.compile("divide(`10`, `4`)").unwrap();
1662 let result = expr.search(&json!(null)).unwrap();
1663 assert_eq!(result.as_f64().unwrap(), 2.5);
1664 }
1665
1666 #[test]
1667 fn test_mode_numbers() {
1668 let runtime = setup_runtime();
1669 let expr = runtime.compile("mode(`[1, 2, 2, 3]`)").unwrap();
1670 let result = expr.search(&json!(null)).unwrap();
1671 assert_eq!(result.as_f64().unwrap() as i64, 2);
1672 }
1673
1674 #[test]
1675 fn test_mode_strings() {
1676 let runtime = setup_runtime();
1677 let expr = runtime
1678 .compile("mode(`[\"a\", \"b\", \"a\", \"c\"]`)")
1679 .unwrap();
1680 let result = expr.search(&json!(null)).unwrap();
1681 assert_eq!(result.as_str().unwrap(), "a");
1682 }
1683
1684 #[test]
1685 fn test_mode_empty() {
1686 let runtime = setup_runtime();
1687 let expr = runtime.compile("mode(`[]`)").unwrap();
1688 let result = expr.search(&json!(null)).unwrap();
1689 assert!(result.is_null());
1690 }
1691
1692 #[test]
1693 fn test_to_fixed() {
1694 let runtime = setup_runtime();
1695 let expr = runtime.compile("to_fixed(`3.14159`, `2`)").unwrap();
1696 let result = expr.search(&json!(null)).unwrap();
1697 assert_eq!(result.as_str().unwrap(), "3.14");
1698 }
1699
1700 #[test]
1701 fn test_to_fixed_padding() {
1702 let runtime = setup_runtime();
1703 let expr = runtime.compile("to_fixed(`3.1`, `3`)").unwrap();
1704 let result = expr.search(&json!(null)).unwrap();
1705 assert_eq!(result.as_str().unwrap(), "3.100");
1706 }
1707
1708 #[test]
1709 fn test_format_number_with_separators() {
1710 let runtime = setup_runtime();
1711 let expr = runtime.compile("format_number(`1234567.89`, `2`)").unwrap();
1712 let result = expr.search(&json!(null)).unwrap();
1713 assert_eq!(result.as_str().unwrap(), "1,234,567.89");
1714 }
1715
1716 #[test]
1717 fn test_format_number_with_k_suffix() {
1718 let runtime = setup_runtime();
1719 let expr = runtime.compile("format_number(`1500`, `1`, 'k')").unwrap();
1720 let result = expr.search(&json!(null)).unwrap();
1721 assert_eq!(result.as_str().unwrap(), "1.5k");
1722 }
1723
1724 #[test]
1725 fn test_format_number_with_m_suffix() {
1726 let runtime = setup_runtime();
1727 let expr = runtime
1728 .compile("format_number(`1500000`, `1`, 'M')")
1729 .unwrap();
1730 let result = expr.search(&json!(null)).unwrap();
1731 assert_eq!(result.as_str().unwrap(), "1.5M");
1732 }
1733
1734 #[test]
1735 fn test_format_number_auto_suffix() {
1736 let runtime = setup_runtime();
1737 let expr = runtime
1738 .compile("format_number(`1500000000`, `2`, 'auto')")
1739 .unwrap();
1740 let result = expr.search(&json!(null)).unwrap();
1741 assert_eq!(result.as_str().unwrap(), "1.50B");
1742 }
1743
1744 #[test]
1745 fn test_histogram() {
1746 let runtime = setup_runtime();
1747 let expr = runtime.compile("histogram(@, `3`)").unwrap();
1748 let data = json!([1, 2, 3, 4, 5, 6, 7, 8, 9]);
1749 let result = expr.search(&data).unwrap();
1750 let arr = result.as_array().unwrap();
1751 assert_eq!(arr.len(), 3);
1752 for bin in arr {
1754 let obj = bin.as_object().unwrap();
1755 assert!(obj.contains_key("min"));
1756 assert!(obj.contains_key("max"));
1757 assert!(obj.contains_key("count"));
1758 }
1759 }
1760
1761 #[test]
1762 fn test_normalize() {
1763 let runtime = setup_runtime();
1764 let expr = runtime.compile("normalize(@)").unwrap();
1765 let data = json!([0, 50, 100]);
1766 let result = expr.search(&data).unwrap();
1767 let arr = result.as_array().unwrap();
1768 assert_eq!(arr.len(), 3);
1769 assert!((arr[0].as_f64().unwrap() - 0.0).abs() < 0.001);
1770 assert!((arr[1].as_f64().unwrap() - 0.5).abs() < 0.001);
1771 assert!((arr[2].as_f64().unwrap() - 1.0).abs() < 0.001);
1772 }
1773
1774 #[test]
1775 fn test_z_score() {
1776 let runtime = setup_runtime();
1777 let expr = runtime.compile("z_score(@)").unwrap();
1778 let data = json!([1, 2, 3, 4, 5]);
1779 let result = expr.search(&data).unwrap();
1780 let arr = result.as_array().unwrap();
1781 assert_eq!(arr.len(), 5);
1782 assert!((arr[2].as_f64().unwrap() - 0.0).abs() < 0.001);
1784 }
1785
1786 #[test]
1787 fn test_correlation_positive() {
1788 let runtime = setup_runtime();
1789 let expr = runtime
1790 .compile("correlation(`[1, 2, 3]`, `[1, 2, 3]`)")
1791 .unwrap();
1792 let result = expr.search(&json!(null)).unwrap();
1793 assert!((result.as_f64().unwrap() - 1.0).abs() < 0.001);
1794 }
1795
1796 #[test]
1797 fn test_correlation_negative() {
1798 let runtime = setup_runtime();
1799 let expr = runtime
1800 .compile("correlation(`[1, 2, 3]`, `[3, 2, 1]`)")
1801 .unwrap();
1802 let result = expr.search(&json!(null)).unwrap();
1803 assert!((result.as_f64().unwrap() - (-1.0)).abs() < 0.001);
1804 }
1805
1806 #[test]
1807 fn test_quantile_median() {
1808 let runtime = setup_runtime();
1809 let expr = runtime
1810 .compile("quantile(`[1, 2, 3, 4, 5]`, `0.5`)")
1811 .unwrap();
1812 let result = expr.search(&json!(null)).unwrap();
1813 assert_eq!(result.as_f64().unwrap(), 3.0);
1814 }
1815
1816 #[test]
1817 fn test_quantile_quartiles() {
1818 let runtime = setup_runtime();
1819 let expr = runtime
1821 .compile("quantile(`[1, 2, 3, 4, 5]`, `0.25`)")
1822 .unwrap();
1823 let result = expr.search(&json!(null)).unwrap();
1824 assert_eq!(result.as_f64().unwrap(), 2.0);
1825
1826 let expr = runtime
1828 .compile("quantile(`[1, 2, 3, 4, 5]`, `0.75`)")
1829 .unwrap();
1830 let result = expr.search(&json!(null)).unwrap();
1831 assert_eq!(result.as_f64().unwrap(), 4.0);
1832 }
1833
1834 #[test]
1835 fn test_moving_avg() {
1836 let runtime = setup_runtime();
1837 let expr = runtime
1838 .compile("moving_avg(`[1, 2, 3, 4, 5, 6]`, `3`)")
1839 .unwrap();
1840 let result = expr.search(&json!(null)).unwrap();
1841 let arr = result.as_array().unwrap();
1842 assert_eq!(arr.len(), 6);
1843 assert!(arr[0].is_null());
1844 assert!(arr[1].is_null());
1845 assert_eq!(arr[2].as_f64().unwrap(), 2.0); assert_eq!(arr[3].as_f64().unwrap(), 3.0); assert_eq!(arr[4].as_f64().unwrap(), 4.0); assert_eq!(arr[5].as_f64().unwrap(), 5.0); }
1850
1851 #[test]
1852 fn test_ewma() {
1853 let runtime = setup_runtime();
1854 let expr = runtime.compile("ewma(`[1, 2, 3, 4, 5]`, `0.5`)").unwrap();
1855 let result = expr.search(&json!(null)).unwrap();
1856 let arr = result.as_array().unwrap();
1857 assert_eq!(arr.len(), 5);
1858 assert_eq!(arr[0].as_f64().unwrap(), 1.0);
1860 assert_eq!(arr[1].as_f64().unwrap(), 1.5); assert_eq!(arr[2].as_f64().unwrap(), 2.25); }
1864
1865 #[test]
1866 fn test_covariance() {
1867 let runtime = setup_runtime();
1868 let expr = runtime
1869 .compile("covariance(`[1, 2, 3]`, `[1, 2, 3]`)")
1870 .unwrap();
1871 let result = expr.search(&json!(null)).unwrap();
1872 assert!((result.as_f64().unwrap() - 0.666666).abs() < 0.01);
1874 }
1875
1876 #[test]
1877 fn test_covariance_negative() {
1878 let runtime = setup_runtime();
1879 let expr = runtime
1880 .compile("covariance(`[1, 2, 3]`, `[3, 2, 1]`)")
1881 .unwrap();
1882 let result = expr.search(&json!(null)).unwrap();
1883 assert!((result.as_f64().unwrap() - (-0.666666)).abs() < 0.01);
1884 }
1885
1886 #[test]
1887 fn test_standardize() {
1888 let runtime = setup_runtime();
1889 let expr = runtime
1890 .compile("standardize(`[10, 20, 30, 40, 50]`)")
1891 .unwrap();
1892 let result = expr.search(&json!(null)).unwrap();
1893 let arr = result.as_array().unwrap();
1894 assert_eq!(arr.len(), 5);
1895 assert!((arr[0].as_f64().unwrap() - (-1.414)).abs() < 0.01);
1898 assert!(arr[2].as_f64().unwrap().abs() < 0.001);
1900 assert!((arr[4].as_f64().unwrap() - 1.414).abs() < 0.01);
1902 }
1903
1904 #[test]
1905 fn test_trend_increasing() {
1906 let runtime = setup_runtime();
1907 let expr = runtime.compile("trend(`[1, 2, 3, 5, 8]`)").unwrap();
1908 let result = expr.search(&json!(null)).unwrap();
1909 assert_eq!(result.as_str().unwrap(), "increasing");
1910 }
1911
1912 #[test]
1913 fn test_trend_decreasing() {
1914 let runtime = setup_runtime();
1915 let expr = runtime.compile("trend(`[10, 9, 8, 7, 6]`)").unwrap();
1916 let result = expr.search(&json!(null)).unwrap();
1917 assert_eq!(result.as_str().unwrap(), "decreasing");
1918 }
1919
1920 #[test]
1921 fn test_trend_stable() {
1922 let runtime = setup_runtime();
1923 let expr = runtime.compile("trend(`[5, 5, 5, 5, 5]`)").unwrap();
1924 let result = expr.search(&json!(null)).unwrap();
1925 assert_eq!(result.as_str().unwrap(), "stable");
1926 }
1927
1928 #[test]
1929 fn test_trend_slope() {
1930 let runtime = setup_runtime();
1931 let expr = runtime.compile("trend_slope(`[0, 1, 2, 3, 4]`)").unwrap();
1932 let result = expr.search(&json!(null)).unwrap();
1933 assert!((result.as_f64().unwrap() - 1.0).abs() < 0.001);
1935 }
1936
1937 #[test]
1938 fn test_rate_of_change() {
1939 let runtime = setup_runtime();
1940 let expr = runtime
1941 .compile("rate_of_change(`[100, 110, 105]`)")
1942 .unwrap();
1943 let result = expr.search(&json!(null)).unwrap();
1944 let arr = result.as_array().unwrap();
1945 assert_eq!(arr.len(), 2);
1946 assert!((arr[0].as_f64().unwrap() - 10.0).abs() < 0.01);
1948 assert!((arr[1].as_f64().unwrap() - (-4.545)).abs() < 0.01);
1950 }
1951
1952 #[test]
1953 fn test_cumulative_sum() {
1954 let runtime = setup_runtime();
1955 let expr = runtime.compile("cumulative_sum(`[1, 2, 3, 4]`)").unwrap();
1956 let result = expr.search(&json!(null)).unwrap();
1957 let arr = result.as_array().unwrap();
1958 assert_eq!(arr.len(), 4);
1959 assert_eq!(arr[0].as_f64().unwrap(), 1.0);
1960 assert_eq!(arr[1].as_f64().unwrap(), 3.0);
1961 assert_eq!(arr[2].as_f64().unwrap(), 6.0);
1962 assert_eq!(arr[3].as_f64().unwrap(), 10.0);
1963 }
1964
1965 #[test]
1966 fn test_skew_symmetric() {
1967 let runtime = setup_runtime();
1968 let expr = runtime.compile("skew(`[1, 2, 3, 4, 5]`)").unwrap();
1969 let result = expr.search(&json!(null)).unwrap();
1970 assert!((result.as_f64().unwrap() - 0.0).abs() < 0.001);
1971 }
1972
1973 #[test]
1974 fn test_skew_right() {
1975 let runtime = setup_runtime();
1976 let expr = runtime.compile("skew(`[1, 1, 1, 10]`)").unwrap();
1977 let result = expr.search(&json!(null)).unwrap();
1978 assert!(result.as_f64().unwrap() > 0.0);
1980 }
1981
1982 #[test]
1983 fn test_skew_too_few() {
1984 let runtime = setup_runtime();
1985 let expr = runtime.compile("skew(`[1, 2]`)").unwrap();
1986 let result = expr.search(&json!(null)).unwrap();
1987 assert!(result.is_null());
1988 }
1989
1990 #[test]
1991 fn test_skew_constant() {
1992 let runtime = setup_runtime();
1993 let expr = runtime.compile("skew(`[5, 5, 5, 5]`)").unwrap();
1994 let result = expr.search(&json!(null)).unwrap();
1995 assert_eq!(result.as_f64().unwrap(), 0.0);
1996 }
1997
1998 #[test]
1999 fn test_kurtosis_uniform_like() {
2000 let runtime = setup_runtime();
2001 let expr = runtime.compile("kurtosis(`[1, 2, 3, 4, 5]`)").unwrap();
2002 let result = expr.search(&json!(null)).unwrap();
2003 assert!(result.as_f64().unwrap() < 0.0);
2005 }
2006
2007 #[test]
2008 fn test_kurtosis_too_few() {
2009 let runtime = setup_runtime();
2010 let expr = runtime.compile("kurtosis(`[1, 2, 3]`)").unwrap();
2011 let result = expr.search(&json!(null)).unwrap();
2012 assert!(result.is_null());
2013 }
2014
2015 #[test]
2016 fn test_kurtosis_constant() {
2017 let runtime = setup_runtime();
2018 let expr = runtime.compile("kurtosis(`[5, 5, 5, 5]`)").unwrap();
2019 let result = expr.search(&json!(null)).unwrap();
2020 assert_eq!(result.as_f64().unwrap(), 0.0);
2021 }
2022
2023 #[test]
2024 fn test_mad_simple() {
2025 let runtime = setup_runtime();
2026 let expr = runtime.compile("mad(`[1, 2, 3, 4, 5]`)").unwrap();
2027 let result = expr.search(&json!(null)).unwrap();
2028 assert_eq!(result.as_f64().unwrap(), 1.0);
2030 }
2031
2032 #[test]
2033 fn test_mad_empty() {
2034 let runtime = setup_runtime();
2035 let expr = runtime.compile("mad(`[]`)").unwrap();
2036 let result = expr.search(&json!(null)).unwrap();
2037 assert!(result.is_null());
2038 }
2039
2040 #[test]
2041 fn test_mad_single() {
2042 let runtime = setup_runtime();
2043 let expr = runtime.compile("mad(`[42]`)").unwrap();
2044 let result = expr.search(&json!(null)).unwrap();
2045 assert_eq!(result.as_f64().unwrap(), 0.0);
2047 }
2048
2049 #[test]
2050 fn test_mad_even_length() {
2051 let runtime = setup_runtime();
2052 let expr = runtime.compile("mad(`[1, 2, 3, 4]`)").unwrap();
2053 let result = expr.search(&json!(null)).unwrap();
2054 assert_eq!(result.as_f64().unwrap(), 1.0);
2056 }
2057}