use wide::f64x4;
const LANES: usize = 4;
pub fn sum(values: &[f64]) -> f64 {
let chunks = values.chunks_exact(LANES);
let remainder = chunks.remainder();
let mut acc = f64x4::ZERO;
for chunk in chunks {
let v = f64x4::new([chunk[0], chunk[1], chunk[2], chunk[3]]);
acc += v;
}
let lanes = acc.to_array();
let mut total = lanes[0] + lanes[1] + lanes[2] + lanes[3];
for &x in remainder {
total += x;
}
total
}
pub fn sum_squares(values: &[f64]) -> f64 {
let chunks = values.chunks_exact(LANES);
let remainder = chunks.remainder();
let mut acc = f64x4::ZERO;
for chunk in chunks {
let v = f64x4::new([chunk[0], chunk[1], chunk[2], chunk[3]]);
acc += v * v;
}
let lanes = acc.to_array();
let mut total = lanes[0] + lanes[1] + lanes[2] + lanes[3];
for &x in remainder {
total += x * x;
}
total
}
pub fn mean(values: &[f64]) -> f64 {
if values.is_empty() {
return 0.0;
}
sum(values) / values.len() as f64
}
pub fn variance(values: &[f64]) -> f64 {
let n = values.len();
if n == 0 {
return 0.0;
}
let chunks = values.chunks_exact(LANES);
let remainder = chunks.remainder();
let mut sum_acc = f64x4::ZERO;
let mut sq_acc = f64x4::ZERO;
for chunk in chunks {
let v = f64x4::new([chunk[0], chunk[1], chunk[2], chunk[3]]);
sum_acc += v;
sq_acc += v * v;
}
let s_lanes = sum_acc.to_array();
let q_lanes = sq_acc.to_array();
let mut s = s_lanes[0] + s_lanes[1] + s_lanes[2] + s_lanes[3];
let mut q = q_lanes[0] + q_lanes[1] + q_lanes[2] + q_lanes[3];
for &x in remainder {
s += x;
q += x * x;
}
let n_f = n as f64;
let mean = s / n_f;
(q - s * mean) / n_f
}
pub fn std_dev(values: &[f64]) -> f64 {
variance(values).sqrt()
}
#[cfg(test)]
pub(crate) fn sum_scalar(values: &[f64]) -> f64 {
let mut s = 0.0;
for &x in values {
s += x;
}
s
}
#[cfg(test)]
pub(crate) fn sum_squares_scalar(values: &[f64]) -> f64 {
let mut s = 0.0;
for &x in values {
s += x * x;
}
s
}
#[cfg(test)]
pub(crate) fn variance_scalar(values: &[f64]) -> f64 {
let n = values.len();
if n == 0 {
return 0.0;
}
let s = sum_scalar(values);
let q = sum_squares_scalar(values);
let mean = s / n as f64;
(q - s * mean) / n as f64
}
#[cfg(test)]
mod tests {
use super::*;
fn approx_eq(a: f64, b: f64, rel: f64, abs: f64) -> bool {
let diff = (a - b).abs();
diff <= abs || diff <= rel * a.abs().max(b.abs())
}
#[test]
fn empty_slice() {
assert_eq!(sum(&[]), 0.0);
assert_eq!(sum_squares(&[]), 0.0);
assert_eq!(mean(&[]), 0.0);
assert_eq!(variance(&[]), 0.0);
assert_eq!(std_dev(&[]), 0.0);
}
#[test]
fn known_values() {
let v = [1.0, 2.0, 3.0, 4.0];
assert_eq!(sum(&v), 10.0);
assert_eq!(sum_squares(&v), 30.0);
assert_eq!(mean(&v), 2.5);
assert!(approx_eq(variance(&v), 1.25, 1e-12, 1e-12));
}
#[test]
fn parity_sum_random() {
for n in [0, 1, 2, 3, 4, 5, 7, 8, 16, 17, 31, 64, 100, 1000, 10_000] {
let values: Vec<f64> = (0..n).map(|i| ((i as f64) * 0.37182).sin() * 1e3).collect();
let s = sum(&values);
let r = sum_scalar(&values);
assert!(
approx_eq(s, r, 1e-12, 1e-9),
"sum mismatch at n={}: simd={} scalar={}",
n,
s,
r
);
let sq = sum_squares(&values);
let rq = sum_squares_scalar(&values);
assert!(
approx_eq(sq, rq, 1e-12, 1e-9),
"sum_squares mismatch at n={}: simd={} scalar={}",
n,
sq,
rq
);
let v = variance(&values);
let rv = variance_scalar(&values);
assert!(
approx_eq(v, rv, 1e-10, 1e-9),
"variance mismatch at n={}: simd={} scalar={}",
n,
v,
rv
);
}
}
#[test]
fn matches_standard_deviation_indicator() {
use crate::indicators::StandardDeviation;
use crate::Next;
use chrono::{TimeZone, Utc};
use std::time::Duration;
let values = [10.0, 20.0, 30.0, 20.0, 10.0];
let mut sd = StandardDeviation::new(Duration::from_secs(60 * 60 * 24 * 365)).unwrap();
let start = Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap();
let mut last = 0.0;
for (i, &v) in values.iter().enumerate() {
last = sd.next((start + chrono::Duration::days(i as i64), v));
}
let batch = std_dev(&values);
assert!(
approx_eq(last, batch, 1e-10, 1e-9),
"streaming={} batch={}",
last,
batch
);
}
}