use_standard_deviation/
lib.rs1#[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}