mirror of
https://github.com/sharkdp/hyperfine.git
synced 2026-11-01 08:50:19 +00:00
Switch to unit-safe quantities
This commit is contained in:
1 parent
914d5cbc59
commit
f9aa299c0f
15 files changed
+399
-287
No files matched your search
+184
-82
@@ -1,103 +1,205 @@
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::{
|
||||
fmt::{self, Display, Formatter},
|
||||
ops::{Add, Div, Sub},
|
||||
};
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, PartialEq)]
|
||||
enum MetricPrefix {
|
||||
Nano,
|
||||
Micro,
|
||||
Milli,
|
||||
None,
|
||||
pub trait IsQuantity {
|
||||
type Value;
|
||||
|
||||
const IS_TIME: bool;
|
||||
const METRIC_PREFIX_TIME: i32;
|
||||
const IS_INFORMATION: bool;
|
||||
const BINARY_PREFIX_INFORMATION: i32;
|
||||
|
||||
fn unsafe_from(value: Self::Value) -> Self;
|
||||
}
|
||||
|
||||
impl Display for MetricPrefix {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
MetricPrefix::Nano => write!(f, "n"),
|
||||
MetricPrefix::Micro => write!(f, "µ"),
|
||||
MetricPrefix::Milli => write!(f, "m"),
|
||||
MetricPrefix::None => Ok(()),
|
||||
#[derive(Debug, Clone, Copy, Default, Serialize, PartialEq, Eq, PartialOrd)] // TODO: check partialord
|
||||
pub struct Quantity<
|
||||
Value,
|
||||
const IS_TIME: bool,
|
||||
const METRIC_PREFIX_TIME: i32,
|
||||
const IS_INFORMATION: bool,
|
||||
const BINARY_PREFIX_INFORMATION: i32,
|
||||
>(Value);
|
||||
|
||||
impl<
|
||||
Value,
|
||||
const IS_TIME: bool,
|
||||
const METRIC_PREFIX_TIME: i32,
|
||||
const IS_INFORMATION: bool,
|
||||
const BINARY_PREFIX_INFORMATION: i32,
|
||||
> IsQuantity
|
||||
for Quantity<Value, IS_TIME, METRIC_PREFIX_TIME, IS_INFORMATION, BINARY_PREFIX_INFORMATION>
|
||||
{
|
||||
type Value = Value;
|
||||
const IS_TIME: bool = IS_TIME;
|
||||
const METRIC_PREFIX_TIME: i32 = METRIC_PREFIX_TIME;
|
||||
const IS_INFORMATION: bool = IS_INFORMATION;
|
||||
const BINARY_PREFIX_INFORMATION: i32 = BINARY_PREFIX_INFORMATION;
|
||||
|
||||
fn unsafe_from(value: Self::Value) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
type Time<const PREFIX: i32> = Quantity<f64, true, PREFIX, false, 0>;
|
||||
type Information<const PREFIX: i32> = Quantity<u64, false, 0, true, PREFIX>;
|
||||
|
||||
pub type Second = Time<0>;
|
||||
pub type MilliSecond = Time<-3>;
|
||||
pub type MicroSecond = Time<-6>;
|
||||
|
||||
pub type Byte = Information<0>;
|
||||
pub type KibiByte = Information<10>;
|
||||
pub type MebiByte = Information<20>;
|
||||
pub type GibiByte = Information<30>;
|
||||
pub type TebiByte = Information<40>;
|
||||
|
||||
impl<
|
||||
Value: Copy + Default,
|
||||
const IS_TIME: bool,
|
||||
const METRIC_PREFIX_TIME: i32,
|
||||
const IS_INFORMATION: bool,
|
||||
const BINARY_PREFIX_INFORMATION: i32,
|
||||
> Quantity<Value, IS_TIME, METRIC_PREFIX_TIME, IS_INFORMATION, BINARY_PREFIX_INFORMATION>
|
||||
{
|
||||
pub const fn new(value: Value) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
|
||||
pub fn zero() -> Self {
|
||||
Self::new(Value::default())
|
||||
}
|
||||
|
||||
pub fn value_in<TargetUnit: IsQuantity>(&self) -> Value {
|
||||
const {
|
||||
assert!(IS_TIME == TargetUnit::IS_TIME);
|
||||
assert!(METRIC_PREFIX_TIME == TargetUnit::METRIC_PREFIX_TIME);
|
||||
assert!(IS_INFORMATION == TargetUnit::IS_INFORMATION);
|
||||
assert!(BINARY_PREFIX_INFORMATION == TargetUnit::BINARY_PREFIX_INFORMATION);
|
||||
}
|
||||
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, PartialEq)]
|
||||
enum BinaryPrefix {
|
||||
None,
|
||||
Kibi,
|
||||
Mebi,
|
||||
Gibi,
|
||||
Tebi,
|
||||
impl<const METRIC_PREFIX_TIME: i32> Time<METRIC_PREFIX_TIME> {
|
||||
pub fn convert_to<TargetUnit: IsQuantity<Value = f64>>(&self) -> TargetUnit {
|
||||
let factor = 10_f64.powi(METRIC_PREFIX_TIME - TargetUnit::METRIC_PREFIX_TIME);
|
||||
|
||||
TargetUnit::unsafe_from(self.0 * factor)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for BinaryPrefix {
|
||||
impl<const METRIC_PREFIX_TIME: i32> Add<Time<METRIC_PREFIX_TIME>> for Time<METRIC_PREFIX_TIME> {
|
||||
type Output = Time<METRIC_PREFIX_TIME>;
|
||||
|
||||
fn add(self, rhs: Time<METRIC_PREFIX_TIME>) -> Self::Output {
|
||||
Time::new(self.0 + rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const METRIC_PREFIX_TIME: i32> Sub<Time<METRIC_PREFIX_TIME>> for Time<METRIC_PREFIX_TIME> {
|
||||
type Output = Time<METRIC_PREFIX_TIME>;
|
||||
|
||||
fn sub(self, rhs: Time<METRIC_PREFIX_TIME>) -> Self::Output {
|
||||
Time::new(self.0 - rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const METRIC_PREFIX_TIME: i32> Div<Time<METRIC_PREFIX_TIME>> for Time<METRIC_PREFIX_TIME> {
|
||||
type Output = f64;
|
||||
|
||||
fn div(self, rhs: Time<METRIC_PREFIX_TIME>) -> Self::Output {
|
||||
self.0 / rhs.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<const METRIC_PREFIX_TIME: i32> Display for Time<METRIC_PREFIX_TIME> {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
BinaryPrefix::None => Ok(()),
|
||||
BinaryPrefix::Kibi => write!(f, "Ki"),
|
||||
BinaryPrefix::Mebi => write!(f, "Mi"),
|
||||
BinaryPrefix::Gibi => write!(f, "Gi"),
|
||||
BinaryPrefix::Tebi => write!(f, "Ti"),
|
||||
let prefix = match METRIC_PREFIX_TIME {
|
||||
-6 => "µ",
|
||||
-3 => "m",
|
||||
0 => "s",
|
||||
_ => unreachable!(),
|
||||
};
|
||||
write!(f, "{} {}s", self.0, prefix)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const BINARY_PREFIX_INFORMATION: i32> Display for Information<BINARY_PREFIX_INFORMATION> {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
let prefix = match BINARY_PREFIX_INFORMATION {
|
||||
0 => "",
|
||||
10 => "Ki",
|
||||
20 => "Mi",
|
||||
30 => "Gi",
|
||||
40 => "Ti",
|
||||
_ => unreachable!(),
|
||||
};
|
||||
write!(f, "{} {}B", self.0, prefix)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! quantity_fn {
|
||||
($name:ident, $unwrapped_values:ident, $body:expr) => {
|
||||
pub fn $name<
|
||||
const IS_TIME: bool,
|
||||
const METRIC_PREFIX_TIME: i32,
|
||||
const IS_INFORMATION: bool,
|
||||
const BINARY_PREFIX_INFORMATION: i32,
|
||||
>(
|
||||
values: &[Quantity<
|
||||
f64,
|
||||
IS_TIME,
|
||||
METRIC_PREFIX_TIME,
|
||||
IS_INFORMATION,
|
||||
BINARY_PREFIX_INFORMATION,
|
||||
>],
|
||||
) -> Quantity<f64, IS_TIME, METRIC_PREFIX_TIME, IS_INFORMATION, BINARY_PREFIX_INFORMATION> {
|
||||
let $unwrapped_values: Vec<f64> = values.iter().map(|v| v.0).collect();
|
||||
let result_value = $body;
|
||||
Quantity::new(result_value)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, PartialEq)]
|
||||
pub enum Unit {
|
||||
Second(MetricPrefix),
|
||||
Byte(BinaryPrefix),
|
||||
}
|
||||
|
||||
pub const SECOND: Unit = Unit::Second(MetricPrefix::None);
|
||||
pub const MILLISECOND: Unit = Unit::Second(MetricPrefix::Milli);
|
||||
pub const MICROSECOND: Unit = Unit::Second(MetricPrefix::Micro);
|
||||
pub const BYTE: Unit = Unit::Byte(BinaryPrefix::None);
|
||||
|
||||
impl Display for Unit {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Unit::Second(prefix) => write!(f, "{prefix}s",),
|
||||
Unit::Byte(prefix) => write!(f, "{prefix}B"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, PartialEq)]
|
||||
pub struct Quantity<Value> {
|
||||
value: Value,
|
||||
unit: Unit,
|
||||
}
|
||||
|
||||
impl<Value> Quantity<Value> {
|
||||
pub fn new(value: Value, unit: Unit) -> Self {
|
||||
Self { value, unit }
|
||||
}
|
||||
}
|
||||
|
||||
impl<Value: Display> Display for Quantity<Value> {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "{} {}", self.value, self.unit)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mean(values: &[Quantity<f64>]) -> Quantity<f64> {
|
||||
statistical::mean(values).unwrap()
|
||||
}
|
||||
|
||||
pub type Time = Quantity<f64>;
|
||||
pub type Memory = Quantity<u64>;
|
||||
quantity_fn!(mean, values, statistical::mean(&values));
|
||||
quantity_fn!(median, values, statistical::median(&values));
|
||||
quantity_fn!(min, values, crate::util::min_max::min(&values));
|
||||
quantity_fn!(max, values, crate::util::min_max::max(&values));
|
||||
quantity_fn!(standard_deviation, values, {
|
||||
let mean_value = statistical::mean(&values);
|
||||
statistical::standard_deviation(&values, Some(mean_value))
|
||||
});
|
||||
|
||||
#[test]
|
||||
fn basic() {
|
||||
let time = Time {
|
||||
value: 123.4,
|
||||
unit: Unit::Second(MetricPrefix::Milli),
|
||||
};
|
||||
fn test_quantity() {
|
||||
let time = MilliSecond::new(123.4);
|
||||
assert_eq!(time.value_in::<MilliSecond>(), 123.4);
|
||||
|
||||
let time_s = time.convert_to::<Second>();
|
||||
approx::assert_relative_eq!(time_s.value_in::<Second>(), 0.1234);
|
||||
|
||||
let time_us = time.convert_to::<MicroSecond>();
|
||||
approx::assert_relative_eq!(time_us.value_in::<MicroSecond>(), 123400.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format() {
|
||||
let time = MilliSecond::new(123.4);
|
||||
assert_eq!(time.to_string(), "123.4 ms");
|
||||
|
||||
let peak_memory_usage = Memory {
|
||||
value: 8,
|
||||
unit: Unit::Byte(BinaryPrefix::Kibi),
|
||||
};
|
||||
let peak_memory_usage = KibiByte::new(8);
|
||||
assert_eq!(peak_memory_usage.to_string(), "8 KiB");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mean() {
|
||||
let values = vec![MilliSecond::new(123.4), MilliSecond::new(234.5)];
|
||||
let result = mean(&values);
|
||||
assert_eq!(result.to_string(), "178.95 ms");
|
||||
}
|
||||
Reference in new issue
Block a user