You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

117 lines
3.6 KiB

use std::fmt;
use yansi::Color::Blue;
use crate::format::F64Formatter;
#[derive(Debug)]
/// A struct holding statistical data regarding a unsorted set of numerical
/// values.
pub struct Stats {
/// Minimum of the input values.
pub min: f64,
/// Maximum of the input values.
pub max: f64,
/// Average of the input values.
pub avg: f64,
/// Standard deviation of the input values.
pub std: f64,
/// Variance of the input values.
pub var: f64,
/// Number of samples of the input values.
pub samples: usize,
precision: Option<usize>, // If None, then human friendly display will be used
}
impl Stats {
/// Creates a Stats struct from a vector of numerical data.
///
/// `precision` is an Option with the number of decimals to display. If
/// "None" is used, human units will be used, with an heuristic based on the
/// input data for deciding the units and the decimal places.
pub fn new(vec: &[f64], precision: Option<usize>) -> Self {
let mut max = vec[0];
let mut min = max;
let mut temp: f64 = 0.0;
let sum = vec.iter().sum::<f64>();
let avg = sum / vec.len() as f64;
for val in vec.iter() {
max = max.max(*val);
min = min.min(*val);
temp += (avg - *val).powi(2);
}
let var = temp / vec.len() as f64;
let std = var.sqrt();
Self {
min,
max,
avg,
std,
var,
samples: vec.len(),
precision,
}
}
}
impl fmt::Display for Stats {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let formatter = match self.precision {
None => F64Formatter::new_with_range(self.min..self.max),
Some(n) => F64Formatter::new(n),
};
writeln!(
f,
"Samples = {len}; Min = {min}; Max = {max}",
len = Blue.paint(self.samples.to_string()),
min = Blue.paint(formatter.format(self.min)),
max = Blue.paint(formatter.format(self.max)),
)?;
writeln!(
f,
"Average = {avg}; Variance = {var}; STD = {std}",
avg = Blue.paint(formatter.format(self.avg)),
var = Blue.paint(format!("{:.3}", self.var)),
std = Blue.paint(format!("{:.3}", self.std)),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use float_eq::assert_float_eq;
use yansi::Paint;
#[test]
fn basic_test() {
let stats = Stats::new(&[1.1, 3.3, 2.2], Some(3));
assert_eq!(3_usize, stats.samples);
assert_float_eq!(stats.avg, 2.2, rmax <= f64::EPSILON);
assert_float_eq!(stats.min, 1.1, rmax <= f64::EPSILON);
assert_float_eq!(stats.max, 3.3, rmax <= f64::EPSILON);
assert_float_eq!(stats.var, 0.8066, abs <= 0.0001);
assert_float_eq!(stats.std, 0.8981, abs <= 0.0001);
}
#[test]
fn test_display() {
let stats = Stats::new(&[1.1, 3.3, 2.2], Some(3));
Paint::disable();
let display = format!("{stats}");
assert!(display.contains("Samples = 3"));
assert!(display.contains("Min = 1.100"));
assert!(display.contains("Max = 3.300"));
assert!(display.contains("Average = 2.200"));
}
#[test]
fn test_big_num() {
let stats = Stats::new(&[123456789.1234, 123456788.1234], None);
Paint::disable();
let display = format!("{stats}");
assert!(display.contains("Samples = 2"));
assert!(display.contains("Min = 123456788.123"));
assert!(display.contains("Max = 123456789.123"));
}
}