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
/// The p-th percentile of `values` by a named method.
///
/// There is no single "95th percentile": the caller names the definition, so
/// a dashboard and a billing job cannot quietly disagree.
///
/// # Panics
/// Panics on an empty list, a non-finite value, p outside 0..=100, an unknown
/// method, or decimals outside 0..=12.
pub fn percentile(values: &[f64], p: f64, method: &str, decimals: i64) -> f64 {
if values.is_empty() {
panic!("values must not be empty");
}
for v in values {
if !v.is_finite() {
panic!("values must be finite numbers, received {}", v);
}
}
if !p.is_finite() || p < 0.0 || p > 100.0 {
panic!("p must be between 0 and 100, received {}", p);
}
// Checked up front as well as in round_float (same wording), so a bad
// decimals is reported before the method is looked at, as in 1.x.
if !(0..=12).contains(&decimals) {
panic!("decimals must be a whole number from 0 to 12, received {}", decimals);
}
let mut sorted = values.to_vec();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
let n = sorted.len();
let result = match method {
"nearest-rank" => {
// The smallest value with at least p% of the sample at or below it.
let mut rank = ((p * n as f64) / 100.0).ceil() as usize;
if rank < 1 {
rank = 1;
}
sorted[rank - 1]
}
"linear" => {
// Hyndman & Fan type 7: zero-based position (n - 1) * p / 100.
let h = ((n - 1) as f64 * p) / 100.0;
let lo = h.floor();
let i = lo as usize;
if i >= n - 1 {
sorted[n - 1]
} else {
sorted[i] + (h - lo) * (sorted[i + 1] - sorted[i])
}
}
other => panic!("unknown percentile method \"{}\"", other),
};
// math.round-float rounds on the exact value of the double: 2.675 gives 2.67.
round_float(result, decimals)
}
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();
Value::Float(percentile(
&values,
args[1].as_f64(),
args[2].as_str(),
args[3].as_i64(),
))
}