#[derive(Debug, Clone)]
pub struct HarmonicMeanF64 {
reciprocal_sum: f64,
count: u64,
}
impl HarmonicMeanF64 {
#[inline]
#[must_use]
pub const fn new() -> Self {
Self {
reciprocal_sum: 0.0,
count: 0,
}
}
#[inline]
pub fn update(&mut self, sample: f64) -> Result<(), crate::DataError> {
check_finite!(sample);
assert!(sample > 0.0, "harmonic mean requires positive values");
self.count += 1;
self.reciprocal_sum += 1.0 / sample;
Ok(())
}
#[inline]
#[must_use]
pub fn mean(&self) -> Option<f64> {
if self.count == 0 {
None
} else {
Some(self.count as f64 / self.reciprocal_sum)
}
}
#[inline]
#[must_use]
pub fn count(&self) -> u64 {
self.count
}
#[inline]
#[must_use]
pub fn is_primed(&self) -> bool {
self.count > 0
}
#[inline]
pub fn reset(&mut self) {
self.reciprocal_sum = 0.0;
self.count = 0;
}
}
impl Default for HarmonicMeanF64 {
#[inline]
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty() {
let hm = HarmonicMeanF64::new();
assert!(hm.mean().is_none());
}
#[test]
fn known_values() {
let mut hm = HarmonicMeanF64::new();
hm.update(1.0).unwrap();
hm.update(4.0).unwrap();
let m = hm.mean().unwrap();
assert!((m - 1.6).abs() < 1e-10, "expected 1.6, got {m}");
}
#[test]
fn harmonic_leq_arithmetic() {
let mut hm = HarmonicMeanF64::new();
let vals = [2.0, 4.0, 8.0];
let mut sum = 0.0;
for &v in &vals {
hm.update(v).unwrap();
sum += v;
}
let arithmetic = sum / vals.len() as f64;
let harmonic = hm.mean().unwrap();
assert!(
harmonic <= arithmetic,
"HM ({harmonic}) should be <= AM ({arithmetic})"
);
}
#[test]
fn equal_values() {
let mut hm = HarmonicMeanF64::new();
for _ in 0..100 {
hm.update(5.0).unwrap();
}
let m = hm.mean().unwrap();
assert!(
(m - 5.0).abs() < 1e-10,
"HM of equal values should equal that value"
);
}
#[test]
fn reset() {
let mut hm = HarmonicMeanF64::new();
hm.update(1.0).unwrap();
hm.reset();
assert_eq!(hm.count(), 0);
assert!(hm.mean().is_none());
}
#[test]
fn default_is_empty() {
let hm = HarmonicMeanF64::default();
assert_eq!(hm.count(), 0);
}
#[test]
#[should_panic(expected = "positive values")]
fn panics_on_zero() {
let mut hm = HarmonicMeanF64::new();
hm.update(0.0).unwrap();
}
#[test]
fn rejects_nan_and_inf() {
let mut hm = HarmonicMeanF64::new();
assert_eq!(hm.update(f64::NAN), Err(crate::DataError::NotANumber));
assert_eq!(hm.update(f64::INFINITY), Err(crate::DataError::Infinite));
assert_eq!(
hm.update(f64::NEG_INFINITY),
Err(crate::DataError::Infinite)
);
assert_eq!(hm.count(), 0);
}
}