use std::cmp::Ordering;
use std::ops::{Add, Div, Sub};
#[derive(Debug, Clone, Copy)]
enum DurationImpl {
Zero, Ticks { ticks: u64, frequency: u64 },
Nanos(u64),
FloatNanos(f64),
}
impl Ord for DurationImpl {
fn cmp(&self, other: &Self) -> Ordering {
fn normalize(value: DurationImpl) -> f64 {
match value {
DurationImpl::Zero => 0.0,
DurationImpl::Ticks { ticks, frequency } => {
ticks_to_nanos(ticks, frequency)
},
DurationImpl::Nanos(nanos) => nanos as f64,
DurationImpl::FloatNanos(nanos) => nanos,
}
}
let self_normalized = normalize(*self);
let other_normalized = normalize(*other);
self_normalized.total_cmp(&other_normalized)
}
}
impl PartialOrd for DurationImpl {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for DurationImpl {
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == Ordering::Equal
}
}
impl Eq for DurationImpl {}
#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Hash)]
pub enum StorageType {
Zero,
Ticks,
Nanos,
FloatNanos,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
pub struct Duration {
inner: DurationImpl,
}
impl Duration {
pub const ZERO: Duration = Duration {
inner: DurationImpl::Zero,
};
#[must_use]
pub fn from_nanos(nanos: u64) -> Duration {
match nanos {
0 => Duration::ZERO,
_ => Duration {
inner: DurationImpl::Nanos(nanos),
},
}
}
#[must_use]
pub fn from_float_nanos(nanos: f64) -> Duration {
match nanos {
0.0 => Duration::ZERO,
_ if nanos < 0.0 => panic!("nanos should not be negative"),
_ => Duration {
inner: DurationImpl::FloatNanos(nanos),
},
}
}
#[must_use]
pub fn from_ticks(ticks: u64, frequency: u64) -> Duration {
match ticks {
0 => Duration::ZERO,
_ => Duration {
inner: DurationImpl::Ticks { ticks, frequency },
},
}
}
#[must_use]
pub fn to_nanos(&self) -> f64 {
match self.inner {
DurationImpl::FloatNanos(nanos) => nanos,
DurationImpl::Nanos(nanos) => nanos as f64,
DurationImpl::Ticks { ticks, frequency } => {
ticks_to_nanos(ticks, frequency)
},
DurationImpl::Zero => 0.0,
}
}
#[must_use]
pub fn storage_type(&self) -> StorageType {
match self.inner {
DurationImpl::Zero => StorageType::Zero,
DurationImpl::Ticks { .. } => StorageType::Ticks,
DurationImpl::Nanos(_) => StorageType::Nanos,
DurationImpl::FloatNanos(_) => StorageType::FloatNanos,
}
}
#[must_use]
pub fn to_raw(&self) -> u64 {
match self.inner {
DurationImpl::Ticks { ticks, .. } => ticks,
DurationImpl::Nanos(nanos) => nanos,
DurationImpl::Zero => 0,
DurationImpl::FloatNanos(nanos) => nanos.round() as u64,
}
}
#[must_use]
pub fn to_std_duration(&self) -> std::time::Duration {
let nanos = match self.inner {
DurationImpl::Nanos(nanos) => nanos,
DurationImpl::Ticks { ticks, frequency } => {
ticks_to_nanos(ticks, frequency).round() as u64
},
DurationImpl::Zero => 0,
DurationImpl::FloatNanos(nanos) => nanos.round() as u64,
};
std::time::Duration::from_nanos(nanos)
}
}
impl Add for Duration {
type Output = Duration;
fn add(self, rhs: Self) -> Self::Output {
match (self.inner, rhs.inner) {
(DurationImpl::Nanos(self_nanos), DurationImpl::Nanos(rhs_nanos)) => {
Duration::from_nanos(self_nanos + rhs_nanos)
},
(
DurationImpl::Ticks { ticks, frequency },
DurationImpl::Ticks {
ticks: rhs_ticks, ..
},
) => Duration::from_ticks(ticks + rhs_ticks, frequency),
(
DurationImpl::Nanos(nanos),
DurationImpl::Ticks { ticks, frequency },
) => Duration::from_nanos(
nanos + ticks_to_nanos(ticks, frequency).round() as u64,
),
(
DurationImpl::Ticks { ticks, frequency },
DurationImpl::Nanos(nanos),
) => Duration::from_nanos(
ticks_to_nanos(ticks, frequency).round() as u64 + nanos,
),
(DurationImpl::Zero, other) => Duration { inner: other },
(other, DurationImpl::Zero) => Duration { inner: other },
(DurationImpl::FloatNanos(nanos), other) => {
Duration::from_float_nanos(nanos + Duration { inner: other }.to_nanos())
},
(other, DurationImpl::FloatNanos(nanos)) => {
Duration::from_float_nanos(Duration { inner: other }.to_nanos() + nanos)
},
}
}
}
impl Sub for Duration {
type Output = Duration;
fn sub(self, rhs: Self) -> Self::Output {
match (self.inner, rhs.inner) {
(DurationImpl::Nanos(self_nanos), DurationImpl::Nanos(rhs_nanos)) => {
Duration::from_nanos(self_nanos.saturating_sub(rhs_nanos))
},
(
DurationImpl::Ticks { ticks, frequency },
DurationImpl::Ticks {
ticks: rhs_ticks, ..
},
) => Duration::from_ticks(ticks.saturating_sub(rhs_ticks), frequency),
(
DurationImpl::Nanos(nanos),
DurationImpl::Ticks { ticks, frequency },
) => Duration::from_nanos(
nanos - ticks_to_nanos(ticks, frequency).round() as u64,
),
(
DurationImpl::Ticks { ticks, frequency },
DurationImpl::Nanos(nanos),
) => Duration::from_nanos(
ticks_to_nanos(ticks, frequency).round() as u64 - nanos,
),
(DurationImpl::Zero, _) => Duration::ZERO,
(other, DurationImpl::Zero) => Duration { inner: other },
(DurationImpl::FloatNanos(nanos), other) => {
Duration::from_float_nanos(nanos - Duration { inner: other }.to_nanos())
},
(other, DurationImpl::FloatNanos(nanos)) => {
Duration::from_float_nanos(Duration { inner: other }.to_nanos() - nanos)
},
}
}
}
impl<T> Div<T> for Duration
where
T: TryInto<u64> + Copy,
<T as std::convert::TryInto<u64>>::Error: std::fmt::Debug,
{
type Output = Duration;
fn div(self, rhs: T) -> Self::Output {
let rhs = rhs.try_into().expect("divisor must fit inside u64") as f64;
match self.storage_type() {
StorageType::Zero => Duration::ZERO,
StorageType::Ticks | StorageType::Nanos | StorageType::FloatNanos => {
Duration::from_float_nanos(self.to_nanos() / rhs)
},
}
}
}
pub fn ticks_to_nanos(tsc_ticks: u64, tsc_freq_hz: u64) -> f64 {
assert_ne!(tsc_freq_hz, 0);
const NANOS_PER_SECOND: u128 = 1_000_000_000;
let ticks = u128::from(tsc_ticks);
let ticks_per_second = u128::from(tsc_freq_hz);
let numerator = ticks * NANOS_PER_SECOND;
let whole_nanos = numerator / ticks_per_second;
let nanos_remainder = numerator % ticks_per_second;
let fractional_nanos = (nanos_remainder as f64) / (ticks_per_second as f64);
whole_nanos as f64 + fractional_nanos
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Instant {
Ticks(u64),
Std(std::time::Instant),
}
#[cfg(test)]
mod tests {
use super::*;
use speculoos::prelude::*;
#[test]
fn ticks_to_nanos_small_ticks() {
assert_that(&ticks_to_nanos(124, 3_200_000_000)).is_close_to(38.75, 0.01);
assert_that(&ticks_to_nanos(123, 3_200_000_000))
.is_close_to(38.4375, 0.0001);
assert_that(&ticks_to_nanos(122, 3_200_000_000)).is_close_to(38.125, 0.001);
assert_that(&ticks_to_nanos(121, 3_200_000_000))
.is_close_to(37.8125, 0.0001);
assert_that(&ticks_to_nanos(120, 3_200_000_000)).is_close_to(37.5, 0.1);
assert_that(&ticks_to_nanos(119, 3_200_000_000))
.is_close_to(37.1875, 0.001);
}
#[test]
fn ticks_to_nanos_big_ticks() {
assert_eq!(
ticks_to_nanos(267_597_764_222_976u64, 3_200_000_000),
83_624_301_319_680f64
);
assert_that(&ticks_to_nanos(267_597_764_222_971, 3_200_000_000))
.is_close_to(83_624_301_319_678.44, 0.01);
}
#[test]
fn ticks_to_nanos_low_freq() {
assert_eq!(ticks_to_nanos(0, 24_000_000), 0.0);
assert_that(&ticks_to_nanos(1, 24_000_000)).is_close_to(41.67, 0.01);
assert_that(&ticks_to_nanos(2, 24_000_000)).is_close_to(83.34, 0.01);
}
#[test]
fn from_nanos_conversion() {
let duration = Duration::from_nanos(10);
assert_eq!(duration.to_nanos(), 10.0);
assert_eq!(duration.to_raw(), 10);
assert_eq!(
duration.to_std_duration(),
std::time::Duration::from_nanos(10)
);
}
#[test]
fn from_ticks_conversion() {
let duration = Duration::from_ticks(30, 3_000_000_000);
assert_eq!(duration.to_nanos(), 10.0);
assert_eq!(duration.to_raw(), 30);
assert_eq!(
duration.to_std_duration(),
std::time::Duration::from_nanos(10)
);
}
#[test]
fn from_zero_nanos() {
let duration = Duration::from_nanos(0);
assert_eq!(duration.to_nanos(), 0.0);
assert_eq!(duration.to_raw(), 0);
assert_eq!(
duration.to_std_duration(),
std::time::Duration::from_nanos(0)
);
assert_eq!(duration, Duration::ZERO);
}
#[test]
fn from_zero_ticks() {
let duration = Duration::from_ticks(0, 3_000_000_000);
assert_eq!(duration.to_nanos(), 0.0);
assert_eq!(duration.to_raw(), 0);
assert_eq!(
duration.to_std_duration(),
std::time::Duration::from_nanos(0)
);
assert_eq!(duration, Duration::ZERO);
}
#[test]
fn addition_ticks() {
let dur1 = Duration::from_ticks(10, 3_000_000_000);
let dur2 = Duration::from_ticks(20, 3_000_000_000);
assert_eq!((dur1 + dur2).to_raw(), 30);
assert_eq!((dur1 + Duration::ZERO).to_raw(), 10);
assert_eq!((Duration::ZERO + dur1).to_raw(), 10);
}
#[test]
fn addition_nanos() {
let dur1 = Duration::from_nanos(10);
let dur2 = Duration::from_nanos(20);
assert_eq!((dur1 + dur2).to_nanos(), 30.0);
assert_eq!((dur1 + Duration::ZERO).to_nanos(), 10.0);
assert_eq!((Duration::ZERO + dur1).to_nanos(), 10.0);
}
#[test]
fn addition_mix() {
let dur1 = Duration::from_nanos(10);
let dur2 = Duration::from_ticks(20, 1_000_000_000);
assert_eq!((dur1 + dur2).to_nanos(), 30.0);
assert_eq!((dur2 + dur1).to_nanos(), 30.0);
assert_that(
&(Duration::from_float_nanos(10.0) + Duration::from_nanos(3)).to_nanos(),
)
.is_close_to(13.0, 0.01);
}
#[test]
fn subtraction_ticks() {
let dur1 = Duration::from_ticks(10, 3_000_000_000);
let dur2 = Duration::from_ticks(30, 3_000_000_000);
assert_eq!((dur2 - dur1).to_raw(), 20);
assert_eq!((dur1 - dur2).to_raw(), 0);
assert_eq!((dur1 - Duration::ZERO).to_raw(), 10);
assert_eq!((Duration::ZERO - dur1).to_raw(), 0); }
#[test]
fn subtraction_nanos() {
let dur1 = Duration::from_nanos(10);
let dur2 = Duration::from_nanos(30);
assert_eq!((dur2 - dur1).to_nanos(), 20.0);
assert_eq!((dur1 - dur2).to_nanos(), 0.0);
assert_eq!((dur1 - Duration::ZERO).to_nanos(), 10.0);
assert_eq!((Duration::ZERO - dur1).to_nanos(), 0.0); }
#[test]
fn subtraction_mix() {
let dur1 = Duration::from_nanos(10);
let dur2 = Duration::from_ticks(30, 1_000_000_000);
assert_eq!((dur2 - dur1).to_nanos(), 20.0);
let dur3 = Duration::from_nanos(50);
assert_eq!((dur3 - dur2).to_nanos(), 20.0);
assert_that(
&(Duration::from_float_nanos(10.0) - Duration::from_nanos(3)).to_nanos(),
)
.is_close_to(7.0, 0.01);
}
#[test]
fn division_ticks() {
let duration = Duration::from_ticks(7, 3_000_000_000);
assert_that(&(duration / 3).to_nanos()).is_close_to(0.77, 0.01);
}
#[test]
fn division_nanos() {
let duration = Duration::from_nanos(7);
assert_that(&(duration / 3).to_nanos()).is_close_to(2.33, 0.01);
}
#[test]
fn ord_is_sensible() {
assert!(Duration::ZERO < Duration::from_ticks(1, 100));
assert!(Duration::ZERO < Duration::from_nanos(1));
assert!(Duration::from_nanos(1) < Duration::from_nanos(2));
assert!(Duration::from_ticks(1, 10) < Duration::from_ticks(2, 10));
assert!(Duration::from_ticks(1, 1_000_000_000) < Duration::from_nanos(2));
assert!(Duration::from_nanos(2) > Duration::from_ticks(1, 1_000_000_000));
}
#[test]
fn eq_is_sensible() {
assert!(Duration::ZERO == Duration::ZERO);
assert!(Duration::from_nanos(1) == Duration::from_nanos(1));
assert!(Duration::from_ticks(1, 10) == Duration::from_ticks(1, 10));
assert!(Duration::from_ticks(1, 1_000_000_000) == Duration::from_nanos(1));
assert!(Duration::from_ticks(0, 1_000_000_000) == Duration::from_nanos(0));
assert!(Duration::from_ticks(0, 1_000_000_000) == Duration::ZERO);
assert!(Duration::from_nanos(0) == Duration::ZERO);
}
#[test]
fn storage_type_works() {
assert_eq!(Duration::ZERO.storage_type(), StorageType::Zero);
assert_eq!(Duration::from_nanos(1).storage_type(), StorageType::Nanos);
assert_eq!(
Duration::from_ticks(1, 10).storage_type(),
StorageType::Ticks
);
}
}