pub(crate) fn min_max_float64_slice(v: &[f64]) -> Option<(f64, f64)> {
let mut min = f64::INFINITY;
let mut max = f64::NEG_INFINITY;
if v.is_empty() {
return None;
}
for e in v {
if e < &min { min = *e; }
if e > &max { max = *e; }
}
Some((min, max))
}
pub(crate) fn round(mut input: f64) -> f64 {
if input.is_nan() {
return f64::NAN;
}
let mut sign = 1.0;
if input < 0.0 {
sign = -1.0;
input *= -1.0;
}
let decimal = input.fract();
let rounded = if decimal >= 0.5 { input.ceil() } else { input.floor() };
rounded * sign
}
pub(crate) fn linear_interpolate(before: f64, after: f64, at_point: f64) -> f64 {
before + (after - before) * at_point
}
pub(crate) fn interpolate_array(data: &[f64], fit_count: u32) -> Vec<f64> {
let mut interpolated_data = Vec::new();
let spring_factor = (data.len() - 1) as f64 / (fit_count - 1) as f64;
interpolated_data.push(data[0]);
for i in 1..fit_count - 1 {
let spring = f64::from(i) * spring_factor;
let before = spring.floor();
let after = spring.ceil();
let at_point = spring - before;
interpolated_data.push(linear_interpolate(
data[before as usize],
data[after as usize],
at_point,
));
}
interpolated_data.push(data[data.len() - 1]);
interpolated_data
}
pub(crate) fn calculate_height(interval: f64) -> usize {
if interval >= 1.0 {
return interval as usize;
}
let scale_factor = 10f64.powf(interval.log10().floor());
let scaled_delta = interval / scale_factor;
if scaled_delta < 2.0 {
return scaled_delta.ceil() as usize;
}
scaled_delta.floor() as usize
}
pub fn moving_average(data: &[f64], window: usize) -> Vec<f64> {
if window <= 1 || data.is_empty() {
return data.to_vec();
}
let n = data.len();
let half = window / 2;
let mut result = Vec::with_capacity(n);
for i in 0..n {
let start = i.saturating_sub(half);
let end = (i + half + 1).min(n);
let mut sum = 0.0;
let mut count = 0usize;
for &v in &data[start..end] {
if v.is_finite() {
sum += v;
count += 1;
}
}
result.push(if count > 0 { sum / count as f64 } else { f64::NAN });
}
result
}