Merge pull request #962 from sharkdp/codex/quantity-module-layout

Group units, formatting, and statistics under quantity
This commit is contained in:
David Peter authored and GitHub committed 2026-10-05 11:04:56 +02:00
commit ecf8a24b1e
20 files changed
+266 -265

No files matched your search

+8 -10
View File
@@ -2,8 +2,6 @@ use std::collections::BTreeMap;
use serde::Serialize;
use crate::util::units::Second;
/// Set of values that will be exported.
// NOTE: `serde` is used for JSON serialization, but not for CSV serialization due to the
// `parameters` map. Update `src/hyperfine/export/csv.rs` with new fields, as appropriate.
@@ -18,29 +16,29 @@ pub struct BenchmarkResult {
pub command_with_unused_parameters: String,
/// The average run time
pub mean: Second,
pub mean: f64,
/// The standard deviation of all run times. Not available if only one run has been performed
pub stddev: Option<Second>,
pub stddev: Option<f64>,
/// The median run time
pub median: Second,
pub median: f64,
/// Time spent in user mode
pub user: Second,
pub user: f64,
/// Time spent in kernel mode
pub system: Second,
pub system: f64,
/// Minimum of all measured times
pub min: Second,
pub min: f64,
/// Maximum of all measured times
pub max: Second,
pub max: f64,
/// All run time measurements
#[serde(skip_serializing_if = "Option::is_none")]
pub times: Option<Vec<Second>>,
pub times: Option<Vec<f64>>,
/// Maximum memory usage of the process, in bytes
#[serde(skip_serializing_if = "Option::is_none")]
+1 -2
View File
@@ -1,8 +1,7 @@
use std::process::ExitStatus;
use crate::outlier_detection::modified_zscores;
use crate::quantity::statistics::{max, mean, median, min, modified_zscores, standard_deviation};
use crate::quantity::{second, Information, Time};
use crate::util::statistics::{max, mean, median, min, standard_deviation};
/// Performance measurements and exit status from running a single command
#[derive(Debug, Default, Copy, Clone)]
+3 -2
View File
@@ -14,11 +14,12 @@ use crate::options::{
};
use crate::outlier_detection::OUTLIER_THRESHOLD;
use crate::output::console_writeln;
use crate::output::format::{format_duration, format_duration_unit};
use crate::output::progress_bar::get_progress_bar;
use crate::output::warnings::{OutlierWarningOptions, Warnings};
use crate::parameter::ParameterNameAndValue;
use crate::quantity::{byte, const_time_from_seconds, ratio, second, Time, Zero};
use crate::quantity::{
byte, const_time_from_seconds, format_duration, format_duration_unit, ratio, second, Time, Zero,
};
use crate::util::exit_code::extract_exit_code;
use benchmark_result::BenchmarkResult;
use measurement::{Measurement, Measurements};
+4 -4
View File
@@ -1,13 +1,13 @@
use std::cmp::Ordering;
use super::benchmark_result::BenchmarkResult;
use crate::{options::SortOrder, util::units::Scalar};
use crate::options::SortOrder;
#[derive(Debug)]
pub struct BenchmarkResultWithRelativeSpeed<'a> {
pub result: &'a BenchmarkResult,
pub relative_speed: Scalar,
pub relative_speed_stddev: Option<Scalar>,
pub relative_speed: f64,
pub relative_speed_stddev: Option<f64>,
pub is_reference: bool,
// Less means faster
pub relative_ordering: Ordering,
@@ -119,7 +119,7 @@ pub fn compute(
}
#[cfg(test)]
fn create_result(name: &str, mean: Scalar) -> BenchmarkResult {
fn create_result(name: &str, mean: f64) -> BenchmarkResult {
use std::collections::BTreeMap;
BenchmarkResult {
+3 -3
View File
@@ -5,7 +5,7 @@ use csv::WriterBuilder;
use super::Exporter;
use crate::benchmark::benchmark_result::BenchmarkResult;
use crate::options::SortOrder;
use crate::util::units::Unit;
use crate::quantity::TimeUnit;
use anyhow::Result;
@@ -16,7 +16,7 @@ impl Exporter for CsvExporter {
fn serialize(
&self,
results: &[BenchmarkResult],
_unit: Option<Unit>,
_unit: Option<TimeUnit>,
_sort_order: SortOrder,
) -> Result<Vec<u8>> {
let mut writer = WriterBuilder::new().from_writer(vec![]);
@@ -110,7 +110,7 @@ fn test_csv() {
let actual = String::from_utf8(
exporter
.serialize(&results, Some(Unit::Second), SortOrder::Command)
.serialize(&results, Some(TimeUnit::Second), SortOrder::Command)
.unwrap(),
)
.unwrap();
+2 -2
View File
@@ -4,7 +4,7 @@ use serde_json::to_vec_pretty;
use super::Exporter;
use crate::benchmark::benchmark_result::BenchmarkResult;
use crate::options::SortOrder;
use crate::util::units::Unit;
use crate::quantity::TimeUnit;
use anyhow::Result;
@@ -20,7 +20,7 @@ impl Exporter for JsonExporter {
fn serialize(
&self,
results: &[BenchmarkResult],
_unit: Option<Unit>,
_unit: Option<TimeUnit>,
_sort_order: SortOrder,
) -> Result<Vec<u8>> {
let mut output = to_vec_pretty(&HyperfineSummary { results });
+9 -6
View File
@@ -1,8 +1,7 @@
use crate::benchmark::relative_speed::BenchmarkResultWithRelativeSpeed;
use crate::benchmark::{benchmark_result::BenchmarkResult, relative_speed};
use crate::options::SortOrder;
use crate::output::format::format_duration_value;
use crate::util::units::Unit;
use crate::quantity::{format_duration_value, TimeUnit};
use super::Exporter;
use anyhow::Result;
@@ -13,7 +12,11 @@ pub enum Alignment {
}
pub trait MarkupExporter {
fn table_results(&self, entries: &[BenchmarkResultWithRelativeSpeed], unit: Unit) -> String {
fn table_results(
&self,
entries: &[BenchmarkResultWithRelativeSpeed],
unit: TimeUnit,
) -> String {
// prepare table header strings
let notation = format!("[{}]", unit.short_name());
@@ -95,13 +98,13 @@ pub trait MarkupExporter {
fn command(&self, size: &str) -> String;
}
fn determine_unit_from_results(results: &[BenchmarkResult]) -> Unit {
fn determine_unit_from_results(results: &[BenchmarkResult]) -> TimeUnit {
if let Some(first_result) = results.first() {
// Use the first BenchmarkResult entry to determine the unit for all entries.
format_duration_value(first_result.mean, None).1
} else {
// Default to `Second`.
Unit::Second
TimeUnit::Second
}
}
@@ -109,7 +112,7 @@ impl<T: MarkupExporter> Exporter for T {
fn serialize(
&self,
results: &[BenchmarkResult],
unit: Option<Unit>,
unit: Option<TimeUnit>,
sort_order: SortOrder,
) -> Result<Vec<u8>> {
let unit = unit.unwrap_or_else(|| determine_unit_from_results(results));
+4 -4
View File
@@ -19,7 +19,7 @@ use self::orgmode::OrgmodeExporter;
use crate::benchmark::benchmark_result::BenchmarkResult;
use crate::options::SortOrder;
use crate::output::console_writeln;
use crate::util::units::Unit;
use crate::quantity::TimeUnit;
use anyhow::{Context, Result};
use clap::ArgMatches;
@@ -49,7 +49,7 @@ trait Exporter {
fn serialize(
&self,
results: &[BenchmarkResult],
unit: Option<Unit>,
unit: Option<TimeUnit>,
sort_order: SortOrder,
) -> Result<Vec<u8>>;
}
@@ -67,7 +67,7 @@ struct ExporterWithTarget {
/// Handles the management of multiple file exporters.
pub struct ExportManager {
exporters: Vec<ExporterWithTarget>,
time_unit: Option<Unit>,
time_unit: Option<TimeUnit>,
sort_order: SortOrder,
}
@@ -76,7 +76,7 @@ impl ExportManager {
/// in the given ArgMatches
pub fn from_cli_arguments(
matches: &ArgMatches,
time_unit: Option<Unit>,
time_unit: Option<TimeUnit>,
sort_order: SortOrder,
) -> Result<Self> {
let mut export_manager = Self {
+8 -8
View File
@@ -2,13 +2,13 @@ use super::Exporter;
use crate::benchmark::benchmark_result::BenchmarkResult;
use crate::export::asciidoc::AsciidocExporter;
use crate::export::orgmode::OrgmodeExporter;
use crate::util::units::Unit;
use crate::quantity::TimeUnit;
use crate::{export::markdown::MarkdownExporter, options::SortOrder};
use std::collections::BTreeMap;
fn get_output<E: Exporter + Default>(
results: &[BenchmarkResult],
unit: Option<Unit>,
unit: Option<TimeUnit>,
sort_order: SortOrder,
) -> String {
let exporter = E::default();
@@ -206,14 +206,14 @@ fn test_markup_export_manual_ms() {
},
];
insta::assert_snapshot!(get_output::<MarkdownExporter>(&timing_results, Some(Unit::MilliSecond), SortOrder::Command), @r#"
insta::assert_snapshot!(get_output::<MarkdownExporter>(&timing_results, Some(TimeUnit::MilliSecond), SortOrder::Command), @r#"
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|:---|---:|---:|---:|---:|
| `sleep 2` | 2005.0 ± 2.0 | 2002.0 | 2008.0 | 18.97 ± 0.29 |
| `sleep 0.1` | 105.7 ± 1.6 | 102.3 | 108.0 | 1.00 |
"#);
insta::assert_snapshot!(get_output::<AsciidocExporter>(&timing_results, Some(Unit::MilliSecond), SortOrder::Command), @r#"
insta::assert_snapshot!(get_output::<AsciidocExporter>(&timing_results, Some(TimeUnit::MilliSecond), SortOrder::Command), @r#"
[cols="<,>,>,>,>"]
|===
| Command
@@ -236,7 +236,7 @@ fn test_markup_export_manual_ms() {
|===
"#);
insta::assert_snapshot!(get_output::<OrgmodeExporter>(&timing_results, Some(Unit::MilliSecond), SortOrder::Command), @r#"
insta::assert_snapshot!(get_output::<OrgmodeExporter>(&timing_results, Some(TimeUnit::MilliSecond), SortOrder::Command), @r#"
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|--+--+--+--+--|
| =sleep 2= | 2005.0 ± 2.0 | 2002.0 | 2008.0 | 18.97 ± 0.29 |
@@ -280,21 +280,21 @@ fn test_markup_export_manual_s() {
},
];
insta::assert_snapshot!(get_output::<MarkdownExporter>(&results, Some(Unit::Second), SortOrder::Command), @r#"
insta::assert_snapshot!(get_output::<MarkdownExporter>(&results, Some(TimeUnit::Second), SortOrder::Command), @r#"
| Command | Mean [s] | Min [s] | Max [s] | Relative |
|:---|---:|---:|---:|---:|
| `sleep 2` | 2.005 ± 0.002 | 2.002 | 2.008 | 18.97 ± 0.29 |
| `sleep 0.1` | 0.106 ± 0.002 | 0.102 | 0.108 | 1.00 |
"#);
insta::assert_snapshot!(get_output::<MarkdownExporter>(&results, Some(Unit::Second), SortOrder::MeanTime), @r#"
insta::assert_snapshot!(get_output::<MarkdownExporter>(&results, Some(TimeUnit::Second), SortOrder::MeanTime), @r#"
| Command | Mean [s] | Min [s] | Max [s] | Relative |
|:---|---:|---:|---:|---:|
| `sleep 0.1` | 0.106 ± 0.002 | 0.102 | 0.108 | 1.00 |
| `sleep 2` | 2.005 ± 0.002 | 2.002 | 2.008 | 18.97 ± 0.29 |
"#);
insta::assert_snapshot!(get_output::<AsciidocExporter>(&results, Some(Unit::Second), SortOrder::Command), @r#"
insta::assert_snapshot!(get_output::<AsciidocExporter>(&results, Some(TimeUnit::Second), SortOrder::Command), @r#"
[cols="<,>,>,>,>"]
|===
| Command
+7 -8
View File
@@ -9,8 +9,7 @@ use clap::ArgMatches;
use crate::command::Commands;
use crate::error::OptionsError;
use crate::quantity::{second, Time};
use crate::util::units::Unit;
use crate::quantity::{second, Time, TimeUnit};
use anyhow::Result;
@@ -246,7 +245,7 @@ pub struct Options {
pub command_output_policies: Vec<CommandOutputPolicy>,
/// Which time unit to use when displaying results
pub time_unit: Option<Unit>,
pub time_unit: Option<TimeUnit>,
}
impl Default for Options {
@@ -430,11 +429,11 @@ impl Options {
}
options.time_unit = match matches.get_one::<String>("time-unit").map(|s| s.as_str()) {
Some("µs" | "us" | "microsecond" | "microseconds") => Some(Unit::MicroSecond),
Some("ms" | "millisecond" | "milliseconds") => Some(Unit::MilliSecond),
Some("s" | "second" | "seconds") => Some(Unit::Second),
Some("min" | "minute" | "minutes") => Some(Unit::Minute),
Some("h" | "hour" | "hours") => Some(Unit::Hour),
Some("µs" | "us" | "microsecond" | "microseconds") => Some(TimeUnit::MicroSecond),
Some("ms" | "millisecond" | "milliseconds") => Some(TimeUnit::MilliSecond),
Some("s" | "second" | "seconds") => Some(TimeUnit::Second),
Some("min" | "minute" | "minutes") => Some(TimeUnit::Minute),
Some("h" | "hour" | "hours") => Some(TimeUnit::Hour),
_ => None,
};
+2 -27
View File
@@ -5,42 +5,17 @@
//! The ASQC Basic References in Quality Control: Statistical Techniques, Edward F. Mykytka,
//! Ph.D., Editor.
use crate::util::statistics::median;
/// Minimum modified Z-score for a datapoint to be an outlier. Here, 1.4826 is a factor that
/// converts the MAD to an estimator for the standard deviation. The second factor is the number
/// of standard deviations.
pub const OUTLIER_THRESHOLD: f64 = 1.4826 * 10.0;
/// Compute modifized Z-scores for a given sample. A (unmodified) Z-score is defined by
/// `(x_i - x_mean)/x_stddev` whereas the modified Z-score is defined by `(x_i - x_median)/MAD`
/// where MAD is the median absolute deviation.
///
/// References:
/// - <https://en.wikipedia.org/wiki/Median_absolute_deviation>
pub fn modified_zscores(xs: &[f64]) -> Vec<f64> {
assert!(!xs.is_empty());
// Compute sample median:
let x_median = median(xs.iter().copied());
// Compute the absolute deviations from the median:
let deviations: Vec<f64> = xs.iter().map(|x| (x - x_median).abs()).collect();
// Compute median absolute deviation:
let mad = median(deviations.iter().copied());
// Handle MAD == 0 case
let mad = if mad > 0.0 { mad } else { f64::EPSILON };
// Compute modified Z-scores (x_i - x_median) / MAD
xs.iter().map(|&x| (x - x_median) / mad).collect()
}
/// Return the number of outliers in a given sample. Outliers are defined as data points with a
/// modified Z-score that is larger than `OUTLIER_THRESHOLD`.
#[cfg(test)]
pub fn num_outliers(xs: &[f64]) -> usize {
use crate::quantity::statistics::modified_zscores;
if xs.is_empty() {
return 0;
}
-78
View File
@@ -1,78 +0,0 @@
use crate::util::units::{Second, Unit};
/// Format the given duration as a string. The output-unit can be enforced by setting `unit` to
/// `Some(target_unit)`. If `unit` is `None`, it will be determined automatically.
pub fn format_duration(duration: Second, unit: Option<Unit>) -> String {
let (duration_fmt, _) = format_duration_unit(duration, unit);
duration_fmt
}
/// Like `format_duration`, but returns the target unit as well.
pub fn format_duration_unit(duration: Second, unit: Option<Unit>) -> (String, Unit) {
let (out_str, out_unit) = format_duration_value(duration, unit);
(format!("{} {}", out_str, out_unit.short_name()), out_unit)
}
/// Like `format_duration`, but returns the target unit as well.
pub fn format_duration_value(duration: Second, unit: Option<Unit>) -> (String, Unit) {
if (duration < 0.001 && unit.is_none()) || unit == Some(Unit::MicroSecond) {
(Unit::MicroSecond.format(duration), Unit::MicroSecond)
} else if (duration < 1.0 && unit.is_none()) || unit == Some(Unit::MilliSecond) {
(Unit::MilliSecond.format(duration), Unit::MilliSecond)
} else {
let unit = unit.unwrap_or(Unit::Second);
(unit.format(duration), unit)
}
}
#[test]
fn test_format_duration_unit_basic() {
let (out_str, out_unit) = format_duration_unit(1.3, None);
assert_eq!("1.300 s", out_str);
assert_eq!(Unit::Second, out_unit);
let (out_str, out_unit) = format_duration_unit(1.0, None);
assert_eq!("1.000 s", out_str);
assert_eq!(Unit::Second, out_unit);
let (out_str, out_unit) = format_duration_unit(0.999, None);
assert_eq!("999.0 ms", out_str);
assert_eq!(Unit::MilliSecond, out_unit);
let (out_str, out_unit) = format_duration_unit(0.0005, None);
assert_eq!("500.0 µs", out_str);
assert_eq!(Unit::MicroSecond, out_unit);
let (out_str, out_unit) = format_duration_unit(0.0, None);
assert_eq!("0.0 µs", out_str);
assert_eq!(Unit::MicroSecond, out_unit);
let (out_str, out_unit) = format_duration_unit(1000.0, None);
assert_eq!("1000.000 s", out_str);
assert_eq!(Unit::Second, out_unit);
}
#[test]
fn test_format_duration_unit_with_unit() {
let (out_str, out_unit) = format_duration_unit(1.3, Some(Unit::Second));
assert_eq!("1.300 s", out_str);
assert_eq!(Unit::Second, out_unit);
let (out_str, out_unit) = format_duration_unit(1.3, Some(Unit::MilliSecond));
assert_eq!("1300.0 ms", out_str);
assert_eq!(Unit::MilliSecond, out_unit);
let (out_str, out_unit) = format_duration_unit(1.3, Some(Unit::MicroSecond));
assert_eq!("1300000.0 µs", out_str);
assert_eq!(Unit::MicroSecond, out_unit);
}
-1
View File
@@ -1,4 +1,3 @@
pub mod format;
pub mod progress_bar;
pub mod warnings;
+1 -2
View File
@@ -1,8 +1,7 @@
use std::fmt;
use crate::benchmark::MIN_EXECUTION_TIME;
use crate::output::format::format_duration;
use crate::quantity::{second, Time};
use crate::quantity::{format_duration, second, Time};
pub struct OutlierWarningOptions {
pub warmup_in_use: bool,
-45
View File
@@ -1,45 +0,0 @@
use std::marker::PhantomData;
pub use uom::num_traits::Zero;
pub use uom::si::f64::{Information, Ratio, Time};
pub use uom::si::information::byte;
pub use uom::si::ratio::ratio;
pub use uom::si::time::{microsecond, nanosecond, second};
pub const fn const_time_from_seconds(value: f64) -> Time {
// Quantity::new in uom is not yet const: https://docs.rs/uom/0.36.0/uom/si/struct.Quantity.html
Time {
dimension: PhantomData,
units: PhantomData,
value,
}
}
#[cfg(test)]
use uom::si::{information::kibibyte, time::millisecond};
#[test]
fn test_time() {
let time = Time::new::<millisecond>(123.4);
assert_eq!(time.get::<millisecond>(), 123.4);
let time_s = time.get::<second>();
approx::assert_relative_eq!(time_s, 0.1234);
let time_us = time.get::<microsecond>();
approx::assert_relative_eq!(time_us, 123400.0);
}
#[test]
fn test_information() {
use uom::si::information::pebibyte;
let information = Information::new::<kibibyte>(8.);
assert_eq!(information.get::<byte>(), 8192.);
let information_kib = information.get::<kibibyte>();
assert_eq!(information_kib, 8.);
let largest_exactly_representable = Information::new::<byte>(9_007_199_254_740_992.);
assert_eq!(largest_exactly_representable.get::<pebibyte>(), 8.);
}
+133
View File
@@ -0,0 +1,133 @@
use std::marker::PhantomData;
pub use uom::num_traits::Zero;
pub use uom::si::f64::{Information, Ratio, Time};
pub use uom::si::information::byte;
pub use uom::si::ratio::ratio;
pub use uom::si::time::{microsecond, nanosecond, second};
pub use units::TimeUnit;
pub mod statistics;
mod units;
pub const fn const_time_from_seconds(value: f64) -> Time {
// Quantity::new in uom is not yet const: https://docs.rs/uom/0.36.0/uom/si/struct.Quantity.html
Time {
dimension: PhantomData,
units: PhantomData,
value,
}
}
/// Format the given duration as a string. The output-unit can be enforced by setting `unit` to
/// `Some(target_unit)`. If `unit` is `None`, it will be determined automatically.
pub fn format_duration(duration: f64, unit: Option<TimeUnit>) -> String {
let (duration_fmt, _) = format_duration_unit(duration, unit);
duration_fmt
}
/// Like `format_duration`, but returns the target unit as well.
pub fn format_duration_unit(duration: f64, unit: Option<TimeUnit>) -> (String, TimeUnit) {
let (out_str, out_unit) = format_duration_value(duration, unit);
(format!("{} {}", out_str, out_unit.short_name()), out_unit)
}
/// Like `format_duration`, but returns the target unit as well.
pub fn format_duration_value(duration: f64, unit: Option<TimeUnit>) -> (String, TimeUnit) {
if (duration < 0.001 && unit.is_none()) || unit == Some(TimeUnit::MicroSecond) {
(
TimeUnit::MicroSecond.format(duration),
TimeUnit::MicroSecond,
)
} else if (duration < 1.0 && unit.is_none()) || unit == Some(TimeUnit::MilliSecond) {
(
TimeUnit::MilliSecond.format(duration),
TimeUnit::MilliSecond,
)
} else {
let unit = unit.unwrap_or(TimeUnit::Second);
(unit.format(duration), unit)
}
}
#[cfg(test)]
use uom::si::{information::kibibyte, time::millisecond};
#[test]
fn test_time() {
let time = Time::new::<millisecond>(123.4);
assert_eq!(time.get::<millisecond>(), 123.4);
let time_s = time.get::<second>();
approx::assert_relative_eq!(time_s, 0.1234);
let time_us = time.get::<microsecond>();
approx::assert_relative_eq!(time_us, 123400.0);
}
#[test]
fn test_information() {
use uom::si::information::pebibyte;
let information = Information::new::<kibibyte>(8.);
assert_eq!(information.get::<byte>(), 8192.);
let information_kib = information.get::<kibibyte>();
assert_eq!(information_kib, 8.);
let largest_exactly_representable = Information::new::<byte>(9_007_199_254_740_992.);
assert_eq!(largest_exactly_representable.get::<pebibyte>(), 8.);
}
#[test]
fn test_format_duration_unit_basic() {
let (out_str, out_unit) = format_duration_unit(1.3, None);
assert_eq!("1.300 s", out_str);
assert_eq!(TimeUnit::Second, out_unit);
let (out_str, out_unit) = format_duration_unit(1.0, None);
assert_eq!("1.000 s", out_str);
assert_eq!(TimeUnit::Second, out_unit);
let (out_str, out_unit) = format_duration_unit(0.999, None);
assert_eq!("999.0 ms", out_str);
assert_eq!(TimeUnit::MilliSecond, out_unit);
let (out_str, out_unit) = format_duration_unit(0.0005, None);
assert_eq!("500.0 µs", out_str);
assert_eq!(TimeUnit::MicroSecond, out_unit);
let (out_str, out_unit) = format_duration_unit(0.0, None);
assert_eq!("0.0 µs", out_str);
assert_eq!(TimeUnit::MicroSecond, out_unit);
let (out_str, out_unit) = format_duration_unit(1000.0, None);
assert_eq!("1000.000 s", out_str);
assert_eq!(TimeUnit::Second, out_unit);
}
#[test]
fn test_format_duration_unit_with_unit() {
let (out_str, out_unit) = format_duration_unit(1.3, Some(TimeUnit::Second));
assert_eq!("1.300 s", out_str);
assert_eq!(TimeUnit::Second, out_unit);
let (out_str, out_unit) = format_duration_unit(1.3, Some(TimeUnit::MilliSecond));
assert_eq!("1300.0 ms", out_str);
assert_eq!(TimeUnit::MilliSecond, out_unit);
let (out_str, out_unit) = format_duration_unit(1.3, Some(TimeUnit::MicroSecond));
assert_eq!("1300000.0 µs", out_str);
assert_eq!(TimeUnit::MicroSecond, out_unit);
}
@@ -106,6 +106,31 @@ pub fn standard_deviation<Q: UnsafeRawValue>(values: &[Q], mean: Q) -> Q {
Q::unsafe_from_raw_value((sum / (values.len() as f64 - 1.0)).sqrt())
}
/// Compute modifized Z-scores for a given sample. A (unmodified) Z-score is defined by
/// `(x_i - x_mean)/x_stddev` whereas the modified Z-score is defined by `(x_i - x_median)/MAD`
/// where MAD is the median absolute deviation.
///
/// References:
/// - <https://en.wikipedia.org/wiki/Median_absolute_deviation>
pub fn modified_zscores(xs: &[f64]) -> Vec<f64> {
assert!(!xs.is_empty());
// Compute sample median:
let x_median = median(xs.iter().copied());
// Compute the absolute deviations from the median:
let deviations: Vec<f64> = xs.iter().map(|x| (x - x_median).abs()).collect();
// Compute median absolute deviation:
let mad = median(deviations.iter().copied());
// Handle MAD == 0 case
let mad = if mad > 0.0 { mad } else { f64::EPSILON };
// Compute modified Z-scores (x_i - x_median) / MAD
xs.iter().map(|&x| (x - x_median) / mad).collect()
}
#[cfg(test)]
mod tests {
use super::*;
+56
View File
@@ -0,0 +1,56 @@
//! This module contains common units.
/// Supported time units
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TimeUnit {
Second,
MilliSecond,
MicroSecond,
Minute,
Hour,
}
impl TimeUnit {
/// The abbreviation of the TimeUnit.
pub fn short_name(self) -> String {
match self {
TimeUnit::Second => String::from("s"),
TimeUnit::MilliSecond => String::from("ms"),
TimeUnit::MicroSecond => String::from("µs"),
TimeUnit::Minute => String::from("min"),
TimeUnit::Hour => String::from("h"),
}
}
/// Returns the value in seconds formatted for the TimeUnit.
pub fn format(self, value: f64) -> String {
match self {
TimeUnit::Second => format!("{value:.3}"),
TimeUnit::MilliSecond => format!("{:.1}", value * 1e3),
TimeUnit::MicroSecond => format!("{:.1}", value * 1e6),
TimeUnit::Minute => format!("{:.1}", value / 60.0),
TimeUnit::Hour => format!("{:.1}", value / 3600.0),
}
}
}
#[test]
fn test_unit_short_name() {
assert_eq!("s", TimeUnit::Second.short_name());
assert_eq!("ms", TimeUnit::MilliSecond.short_name());
assert_eq!("µs", TimeUnit::MicroSecond.short_name());
assert_eq!("min", TimeUnit::Minute.short_name());
assert_eq!("h", TimeUnit::Hour.short_name());
}
// Note - the values are rounded when formatted.
#[test]
fn test_unit_format() {
let value: f64 = 123.456789;
assert_eq!("123.457", TimeUnit::Second.format(value));
assert_eq!("123456.8", TimeUnit::MilliSecond.format(value));
assert_eq!("1234.6", TimeUnit::MicroSecond.format(0.00123456));
assert_eq!("2.1", TimeUnit::Minute.format(value));
assert_eq!("0.0", TimeUnit::Hour.format(value));
}
-2
View File
@@ -1,5 +1,3 @@
pub mod exit_code;
pub mod number;
pub mod randomized_environment_offset;
pub mod statistics;
pub mod units;
-61
View File
@@ -1,61 +0,0 @@
//! This module contains common units.
pub type Scalar = f64;
/// Type alias for unit of time
pub type Second = Scalar;
/// Supported time units
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Unit {
Second,
MilliSecond,
MicroSecond,
Minute,
Hour,
}
impl Unit {
/// The abbreviation of the Unit.
pub fn short_name(self) -> String {
match self {
Unit::Second => String::from("s"),
Unit::MilliSecond => String::from("ms"),
Unit::MicroSecond => String::from("µs"),
Unit::Minute => String::from("min"),
Unit::Hour => String::from("h"),
}
}
/// Returns the Second value formatted for the Unit.
pub fn format(self, value: Second) -> String {
match self {
Unit::Second => format!("{value:.3}"),
Unit::MilliSecond => format!("{:.1}", value * 1e3),
Unit::MicroSecond => format!("{:.1}", value * 1e6),
Unit::Minute => format!("{:.1}", value / 60.0),
Unit::Hour => format!("{:.1}", value / 3600.0),
}
}
}
#[test]
fn test_unit_short_name() {
assert_eq!("s", Unit::Second.short_name());
assert_eq!("ms", Unit::MilliSecond.short_name());
assert_eq!("µs", Unit::MicroSecond.short_name());
assert_eq!("min", Unit::Minute.short_name());
assert_eq!("h", Unit::Hour.short_name());
}
// Note - the values are rounded when formatted.
#[test]
fn test_unit_format() {
let value: Second = 123.456789;
assert_eq!("123.457", Unit::Second.format(value));
assert_eq!("123456.8", Unit::MilliSecond.format(value));
assert_eq!("1234.6", Unit::MicroSecond.format(0.00123456));
assert_eq!("2.1", Unit::Minute.format(value));
assert_eq!("0.0", Unit::Hour.format(value));
}