Skip to main content

use_standard_deviation/
lib.rs

1//! Standard deviation helpers for `f64` slices.
2//!
3//! The functions are intentionally explicit and distinguish between population
4//! and sample standard deviation.
5//!
6//! # Examples
7//!
8//! ```rust
9//! use use_standard_deviation::population_standard_deviation;
10//!
11//! let values = [2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0];
12//! assert_eq!(population_standard_deviation(&values).unwrap(), 2.0);
13//! ```
14
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub enum StandardDeviationError {
17    EmptyInput,
18    InsufficientData,
19}
20
21pub fn population_standard_deviation(values: &[f64]) -> Result<f64, StandardDeviationError> {
22    if values.is_empty() {
23        return Err(StandardDeviationError::EmptyInput);
24    }
25
26    Ok((sum_squared_deviations(values) / values.len() as f64).sqrt())
27}
28
29pub fn sample_standard_deviation(values: &[f64]) -> Result<f64, StandardDeviationError> {
30    if values.len() < 2 {
31        return Err(StandardDeviationError::InsufficientData);
32    }
33
34    Ok((sum_squared_deviations(values) / (values.len() as f64 - 1.0)).sqrt())
35}
36
37fn sum_squared_deviations(values: &[f64]) -> f64 {
38    let mean = values.iter().sum::<f64>() / values.len() as f64;
39    values.iter().map(|value| (value - mean).powi(2)).sum()
40}
41
42#[cfg(test)]
43mod tests {
44    use super::{population_standard_deviation, sample_standard_deviation, StandardDeviationError};
45
46    fn approx_eq(left: f64, right: f64) {
47        assert!((left - right).abs() < 1.0e-10, "left={left}, right={right}");
48    }
49
50    #[test]
51    fn computes_population_and_sample_standard_deviation() {
52        let values = [2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0];
53        let expected_sample_standard_deviation = (32.0_f64 / 7.0_f64).sqrt();
54
55        approx_eq(population_standard_deviation(&values).unwrap(), 2.0);
56        approx_eq(
57            sample_standard_deviation(&values).unwrap(),
58            expected_sample_standard_deviation,
59        );
60    }
61
62    #[test]
63    fn rejects_invalid_standard_deviation_inputs() {
64        assert_eq!(
65            population_standard_deviation(&[]),
66            Err(StandardDeviationError::EmptyInput)
67        );
68        assert_eq!(
69            sample_standard_deviation(&[5.0]),
70            Err(StandardDeviationError::InsufficientData)
71        );
72    }
73
74    #[test]
75    fn handles_single_value_population_standard_deviation() {
76        approx_eq(population_standard_deviation(&[5.0]).unwrap(), 0.0);
77    }
78}