use crate::eval::functions::check_arity;
use crate::types::{ErrorKind, Value};
use super::percentile_inc::collect_numbers;
use super::percentrank_inc::round_to_sig;
pub fn percentrank_exc_fn(args: &[Value]) -> Value {
if let Some(err) = check_arity(args, 2, 3) {
return err;
}
let x = match &args[1] {
Value::Number(n) => *n,
_ => return Value::Error(ErrorKind::NA),
};
let sig = args.get(2).map(|v| match v {
Value::Number(n) => (*n as usize).max(1),
_ => 3,
}).unwrap_or(3);
let mut nums = collect_numbers(&args[0]);
if nums.is_empty() {
return Value::Error(ErrorKind::NA);
}
nums.sort_by(|a, b| a.partial_cmp(b).unwrap());
let min = nums[0];
let max = *nums.last().unwrap();
if x < min || x > max {
return Value::Error(ErrorKind::NA);
}
let n = nums.len();
let result = percentrank_exc_calc(&nums, x, n);
Value::Number(round_to_sig(result, sig))
}
#[cfg(test)]
mod tests;
pub fn percentrank_exc_calc(sorted: &[f64], x: f64, n: usize) -> f64 {
let count_below = sorted.iter().filter(|&&v| v < x).count();
let count_equal = sorted.iter().filter(|&&v| v == x).count();
if count_equal > 0 {
(count_below + 1) as f64 / (n + 1) as f64
} else {
let lo_rank = count_below as f64 / (n + 1) as f64;
let hi_rank = (count_below + 1) as f64 / (n + 1) as f64;
let lo_val = sorted[count_below - 1];
let hi_val = sorted[count_below];
let frac = (x - lo_val) / (hi_val - lo_val);
lo_rank + frac * (hi_rank - lo_rank)
}
}