use std::ops::{Add, Sub};
#[derive(Debug, Clone)]
pub struct KahanSum {
sum: f64,
compensation: f64,
}
impl KahanSum {
pub fn new() -> Self {
Self {
sum: 0.0,
compensation: 0.0,
}
}
pub fn add(&mut self, value: f64) {
let y = value - self.compensation;
let t = self.sum + y;
self.compensation = (t - self.sum) - y;
self.sum = t;
}
pub fn sum(&self) -> f64 {
self.sum
}
pub fn reset(&mut self) {
self.sum = 0.0;
self.compensation = 0.0;
}
}
impl Default for KahanSum {
fn default() -> Self {
Self::new()
}
}
pub fn precise_euclidean_distance(vec1: &[f32], vec2: &[f32]) -> f64 {
assert_eq!(vec1.len(), vec2.len(), "Vectors must have the same length");
let mut kahan = KahanSum::new();
for (&a, &b) in vec1.iter().zip(vec2.iter()) {
let diff = a as f64 - b as f64;
kahan.add(diff * diff);
}
kahan.sum().max(0.0).sqrt()
}
pub fn precise_mean(values: &[f32]) -> f64 {
if values.is_empty() {
return 0.0;
}
let mut kahan = KahanSum::new();
for &value in values {
kahan.add(value as f64);
}
kahan.sum() / values.len() as f64
}
pub fn precise_variance(values: &[f32]) -> f64 {
if values.len() <= 1 {
return 0.0;
}
let mean = precise_mean(values);
let mut kahan = KahanSum::new();
for &value in values {
let diff = value as f64 - mean;
kahan.add(diff * diff);
}
kahan.sum() / (values.len() - 1) as f64
}
pub fn precise_std_dev(values: &[f32]) -> f64 {
precise_variance(values).sqrt()
}
pub fn precise_percentile(mut values: Vec<f32>, percentile: f64) -> f64 {
if values.is_empty() {
return 0.0;
}
values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let rank = percentile / 100.0 * (values.len() - 1) as f64;
let lower_index = rank.floor() as usize;
let upper_index = rank.ceil() as usize;
if lower_index == upper_index {
values[lower_index] as f64
} else {
let weight = rank - lower_index as f64;
let lower_value = values[lower_index] as f64;
let upper_value = values[upper_index] as f64;
lower_value + weight * (upper_value - lower_value)
}
}
pub fn log_sum_exp(values: &[f64]) -> f64 {
if values.is_empty() {
return f64::NEG_INFINITY;
}
let max_val = values.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
if max_val.is_infinite() {
return max_val;
}
let mut kahan = KahanSum::new();
for &value in values {
kahan.add((value - max_val).exp());
}
max_val + kahan.sum().ln()
}
pub fn stable_softmax(values: &[f64]) -> Vec<f64> {
if values.is_empty() {
return vec![];
}
let max_val = values.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
let shifted: Vec<f64> = values.iter().map(|&x| x - max_val).collect();
let sum_exp = shifted.iter().map(|&x| x.exp()).sum::<f64>();
shifted.iter().map(|&x| x.exp() / sum_exp).collect()
}
pub fn precise_correlation(x: &[f32], y: &[f32]) -> f64 {
assert_eq!(x.len(), y.len(), "Arrays must have the same length");
if x.len() <= 1 {
return 0.0;
}
let mean_x = precise_mean(x);
let mean_y = precise_mean(y);
let mut sum_xy = KahanSum::new();
let mut sum_x2 = KahanSum::new();
let mut sum_y2 = KahanSum::new();
for (&xi, &yi) in x.iter().zip(y.iter()) {
let dx = xi as f64 - mean_x;
let dy = yi as f64 - mean_y;
sum_xy.add(dx * dy);
sum_x2.add(dx * dx);
sum_y2.add(dy * dy);
}
let denominator = (sum_x2.sum() * sum_y2.sum()).sqrt();
if denominator == 0.0 {
0.0
} else {
sum_xy.sum() / denominator
}
}
pub mod constants {
pub const EPSILON: f64 = 1e-12;
pub const MAX_EXP: f64 = 700.0;
pub const MIN_LOG: f64 = 1e-15;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_kahan_sum_basic() {
let mut kahan = KahanSum::new();
kahan.add(1.0);
kahan.add(2.0);
kahan.add(3.0);
assert_eq!(kahan.sum(), 6.0);
}
#[test]
fn test_kahan_sum_precision() {
let mut basic_sum = 0.0f64;
let mut kahan = KahanSum::new();
for _ in 0..1_000_000 {
let value = 1e-10;
basic_sum += value;
kahan.add(value);
}
let expected = 1_000_000.0 * 1e-10;
assert!((kahan.sum() - expected).abs() < (basic_sum - expected).abs());
}
#[test]
fn test_precise_euclidean_distance() {
let vec1 = vec![1.0, 2.0, 3.0];
let vec2 = vec![1.0, 2.0, 3.0];
let distance = precise_euclidean_distance(&vec1, &vec2);
assert!((distance - 0.0).abs() < 1e-10);
let vec3 = vec![2.0, 3.0, 4.0];
let distance2 = precise_euclidean_distance(&vec1, &vec3);
assert!((distance2 - (3.0f64).sqrt()).abs() < 1e-10);
}
#[test]
fn test_precise_percentile() {
let values = vec![1.0, 2.0, 3.0, 4.0, 5.0];
assert_eq!(precise_percentile(values.clone(), 0.0), 1.0);
assert_eq!(precise_percentile(values.clone(), 50.0), 3.0);
assert_eq!(precise_percentile(values.clone(), 100.0), 5.0);
let percentile_25 = precise_percentile(values.clone(), 25.0);
assert!((percentile_25 - 2.0).abs() < 1e-10);
}
#[test]
fn test_log_sum_exp() {
let values = vec![1.0, 2.0, 3.0];
let result = log_sum_exp(&values);
let expected = (1.0f64.exp() + 2.0f64.exp() + 3.0f64.exp()).ln();
assert!((result - expected).abs() < 1e-10);
}
#[test]
fn test_precise_correlation() {
let x = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let y = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let correlation = precise_correlation(&x, &y);
assert!((correlation - 1.0).abs() < 1e-10);
let y_neg = vec![5.0, 4.0, 3.0, 2.0, 1.0];
let correlation_neg = precise_correlation(&x, &y_neg);
assert!((correlation_neg - (-1.0)).abs() < 1e-10); }
}