Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::math_round_float::round_float; ← from math.round-float ^1.0.0 · built alongside by fune
// Summed afresh, left to right, so the result never depends on the history
// a running sum would carry.
fn window_mean(values: &[f64], end: usize, window: usize) -> f64 {
let mut sum = 0.0;
for v in &values[end + 1 - window..=end] {
sum += *v;
}
sum / window as f64
}
/// Simple or exponential moving average, one value per full window.
///
/// The exponential average is seeded with the simple average of the first
/// window and carried unrounded; only the returned values are rounded, by
/// math.round-float on the exact value of each double (2.675 gives 2.67).
///
/// # Panics
/// Panics on a non-finite value, a window below 1, an unknown kind, or
/// decimals outside 0..=12.
pub fn moving_average(values: &[f64], window: i64, kind: &str, decimals: i64) -> Vec<f64> {
for v in values {
if !v.is_finite() {
panic!("values must be finite numbers, received {}", v);
}
}
if window < 1 {
panic!("window must be a whole number of 1 or greater, received {}", window);
}
if kind != "simple" && kind != "exponential" {
panic!("unknown moving average kind \"{}\"", kind);
}
// Checked here as well as in round_float (same wording), so a series too
// short to produce any output still refuses bad decimals.
if !(0..=12).contains(&decimals) {
panic!("decimals must be a whole number from 0 to 12, received {}", decimals);
}
let window = window as usize;
let mut out = Vec::new();
if values.len() < window {
return out;
}
if kind == "simple" {
for end in window - 1..values.len() {
out.push(round_float(window_mean(values, end, window), decimals));
}
return out;
}
let alpha = 2.0 / (window as f64 + 1.0);
let mut ema = window_mean(values, window - 1, window);
out.push(round_float(ema, decimals));
for v in &values[window..] {
ema = ema + alpha * (*v - ema);
out.push(round_float(ema, decimals));
}
out
}
pub fn fune_vector(args: &[Value]) -> Value {
// Refuse what the typed signature cannot hold, with the wording TypeScript
// and Python use, rather than let the conversion below quietly change it.
for v in args[0].as_arr() {
if !matches!(v, Value::Int(_) | Value::Float(_)) {
panic!("values must be finite numbers, received {:?}", v);
}
}
let values: Vec<f64> = args[0].as_arr().iter().map(|v| v.as_f64()).collect();
let out = moving_average(&values, args[1].as_i64(), args[2].as_str(), args[3].as_i64());
Value::Arr(out.into_iter().map(Value::Float).collect())
}