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_div::round_div; ← from math.round-div ^1.0.0 · built alongside by fune
const MAX_SAFE: i128 = 9_007_199_254_740_991;
/// Weighted average of integers, rounded once at the end.
///
/// The numerator and denominator are exact integers, so the rounding mode the
/// caller names is the only rounding that ever happens.
///
/// # Panics
/// Panics on mismatched or empty lists, a negative weight, all-zero weights,
/// decimals outside 0..=9, or a weighted sum too large to hold exactly.
pub fn weighted_average(values: &[i64], weights: &[i64], decimals: i64, mode: &str) -> f64 {
if values.len() != weights.len() {
panic!(
"values and weights must be the same length, received {} and {}",
values.len(),
weights.len()
);
}
if values.is_empty() {
panic!("values must not be empty");
}
if !(0..=9).contains(&decimals) {
panic!("decimals must be a whole number from 0 to 9, received {}", decimals);
}
let mut numerator: i128 = 0;
let mut denominator: i128 = 0;
for (&v, &w) in values.iter().zip(weights) {
if (v as i128).abs() > MAX_SAFE {
panic!("values must be integers, received {}", v);
}
if (w as i128).abs() > MAX_SAFE {
panic!("weights must be integers, received {}", w);
}
if w < 0 {
panic!("weights must not be negative, received {}", w);
}
numerator += v as i128 * w as i128;
denominator += w as i128;
}
if denominator == 0 {
panic!("weights must not all be zero");
}
let scale = 10i128.pow(decimals as u32);
let scaled = numerator * scale;
if scaled.abs() > MAX_SAFE || denominator > MAX_SAFE {
panic!("weighted sum is too large to average exactly; lower decimals or rescale the values");
}
round_div(scaled as i64, denominator as i64, mode) as f64 / scale as f64 + 0.0
}
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(_)) && !matches!(v, Value::Float(f) if f.fract() == 0.0) {
panic!("values must be integers, received {:?}", v);
}
}
for v in args[1].as_arr() {
if !matches!(v, Value::Int(_)) && !matches!(v, Value::Float(f) if f.fract() == 0.0) {
panic!("weights must be integers, received {:?}", v);
}
}
let values: Vec<i64> = args[0].as_arr().iter().map(|v| v.as_i64()).collect();
let weights: Vec<i64> = args[1].as_arr().iter().map(|v| v.as_i64()).collect();
Value::Float(weighted_average(
&values,
&weights,
args[2].as_i64(),
args[3].as_str(),
))
}