#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use core::cmp::Ordering;
use core::marker::PhantomData;
use crate::backend::{TickInt, Ticks, CANONICAL_BYTES};
use crate::error::{Code, Result, TimeError};
use crate::profile::Profile;
use crate::tier::{Tier, GROUP_BASE};
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
pub enum Rounding {
Trunc,
Ceil,
#[default]
HalfEven,
HalfUp,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum Precision {
Tick,
Tier(Tier),
}
impl Precision {
pub fn tier(self) -> Tier {
match self {
Precision::Tick => Tier::TICK,
Precision::Tier(t) => t,
}
}
pub fn is_exact(self) -> bool {
matches!(self, Precision::Tick) || self.tier().is_tick()
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum Sign {
Positive,
Negative,
}
#[cfg_attr(feature = "u512", derive(Copy))]
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
pub struct Delta {
ticks: Ticks,
}
impl Delta {
pub fn zero() -> Self {
Delta {
ticks: <Ticks as TickInt>::zero(),
}
}
pub fn one_tick() -> Self {
Delta {
ticks: <Ticks as TickInt>::one(),
}
}
pub fn from_ticks(ticks: Ticks) -> Self {
Delta { ticks }
}
pub fn from_u64(v: u64) -> Self {
Delta {
ticks: <Ticks as TickInt>::from_u64(v),
}
}
pub fn from_tier(tier: Tier, count: u64) -> Result<Self> {
let t = tier.ticks();
let c = <Ticks as TickInt>::from_u64(count);
t.try_mul(&c)
.map(|ticks| Delta { ticks })
.ok_or(TimeError::new(Code::E0021))
}
pub fn ticks(&self) -> &Ticks {
&self.ticks
}
pub fn is_zero(&self) -> bool {
self.ticks.is_zero_ticks()
}
pub fn checked_add(&self, other: &Delta) -> Result<Delta> {
self.ticks
.try_add(&other.ticks)
.map(|ticks| Delta { ticks })
.ok_or(TimeError::new(Code::E0021))
}
pub fn checked_sub(&self, other: &Delta) -> Result<Delta> {
self.ticks
.try_sub(&other.ticks)
.map(|ticks| Delta { ticks })
.ok_or(TimeError::new(Code::E0020))
}
pub fn mul_u64(&self, n: u64) -> Result<Delta> {
self.ticks
.try_mul(&<Ticks as TickInt>::from_u64(n))
.map(|ticks| Delta { ticks })
.ok_or(TimeError::new(Code::E0021))
}
pub fn div_u64(&self, n: u64) -> Result<Delta> {
if n == 0 {
return Err(TimeError::with_context(Code::E0021, "division by zero"));
}
let (q, _) = self.ticks.quot_rem(&<Ticks as TickInt>::from_u64(n));
Ok(Delta { ticks: q })
}
pub fn divmod(&self, divisor: &Delta) -> Result<(Delta, Delta)> {
if divisor.is_zero() {
return Err(TimeError::with_context(Code::E0021, "division by zero"));
}
let (q, r) = self.ticks.quot_rem(&divisor.ticks);
Ok((Delta { ticks: q }, Delta { ticks: r }))
}
pub fn tier_of(&self) -> Option<Tier> {
if self.is_zero() {
return None;
}
Tier::all_descending().find(|t| t.ticks() <= self.ticks)
}
pub fn in_tier(&self, tier: Tier) -> (Ticks, Ticks) {
self.ticks.quot_rem(&tier.ticks())
}
}
#[cfg_attr(feature = "u512", derive(Copy))]
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Signed {
sign: Sign,
mag: Delta,
}
impl Signed {
pub fn new(sign: Sign, mag: Delta) -> Self {
if mag.is_zero() {
Signed {
sign: Sign::Positive,
mag,
}
} else {
Signed { sign, mag }
}
}
pub fn zero() -> Self {
Signed::new(Sign::Positive, Delta::zero())
}
pub fn sign(&self) -> Sign {
self.sign
}
pub fn magnitude(&self) -> &Delta {
&self.mag
}
pub fn is_zero(&self) -> bool {
self.mag.is_zero()
}
pub fn into_magnitude(self) -> Delta {
self.mag
}
}
impl PartialOrd for Signed {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Signed {
fn cmp(&self, other: &Self) -> Ordering {
match (self.sign, other.sign) {
(Sign::Positive, Sign::Positive) => self.mag.cmp(&other.mag),
(Sign::Negative, Sign::Negative) => other.mag.cmp(&self.mag),
(Sign::Positive, Sign::Negative) => Ordering::Greater,
(Sign::Negative, Sign::Positive) => Ordering::Less,
}
}
}
#[cfg_attr(feature = "u512", derive(Copy))]
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct SignedWindow {
lo: Signed,
hi: Signed,
}
impl SignedWindow {
pub fn new(lo: Signed, hi: Signed) -> Result<Self> {
if lo > hi {
return Err(TimeError::new(Code::E0022));
}
Ok(SignedWindow { lo, hi })
}
pub fn symmetric(half_width: Delta) -> Self {
SignedWindow {
lo: Signed::new(Sign::Negative, half_width.clone()),
hi: Signed::new(Sign::Positive, half_width),
}
}
pub fn lo(&self) -> &Signed {
&self.lo
}
pub fn hi(&self) -> &Signed {
&self.hi
}
#[cfg(feature = "alloc")]
pub fn describe(&self) -> alloc::string::String {
use alloc::format;
let s = |v: &Signed| {
let m = v.magnitude().ticks().to_dec_string();
match v.sign() {
Sign::Negative => format!("-{m}"),
Sign::Positive => m,
}
};
format!("[{}, {}] ticks", s(&self.lo), s(&self.hi))
}
}
pub struct Instant<P: Profile> {
ticks: Ticks,
_p: PhantomData<P>,
}
impl<P: Profile> Clone for Instant<P> {
fn clone(&self) -> Self {
Instant {
ticks: self.ticks.clone(),
_p: PhantomData,
}
}
}
#[cfg(feature = "u512")]
impl<P: Profile> Copy for Instant<P> {}
impl<P: Profile> PartialEq for Instant<P> {
fn eq(&self, other: &Self) -> bool {
self.ticks == other.ticks
}
}
impl<P: Profile> Eq for Instant<P> {}
impl<P: Profile> PartialOrd for Instant<P> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<P: Profile> Ord for Instant<P> {
fn cmp(&self, other: &Self) -> Ordering {
self.ticks.cmp(&other.ticks)
}
}
impl<P: Profile> core::hash::Hash for Instant<P> {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.ticks.hash(state);
}
}
impl<P: Profile> core::fmt::Debug for Instant<P> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Instant<{}>({:?})", P::TAG, self.ticks)
}
}
#[cfg(feature = "u512")]
impl<P: Profile> Instant<P> {
pub const ZERO: Self = Instant {
ticks: bnum::types::U512::MIN,
_p: PhantomData,
};
}
impl<P: Profile> Instant<P> {
pub fn zero() -> Self {
Instant {
ticks: <Ticks as TickInt>::zero(),
_p: PhantomData,
}
}
pub fn from_ticks(ticks: Ticks) -> Result<Self> {
if ticks > P::domain_max() {
return Err(TimeError::new(Code::E0021));
}
Ok(Instant {
ticks,
_p: PhantomData,
})
}
pub fn from_u64(v: u64) -> Result<Self> {
Self::from_ticks(<Ticks as TickInt>::from_u64(v))
}
pub fn ticks(&self) -> &Ticks {
&self.ticks
}
pub fn tier_value(&self, tier: Tier) -> u16 {
let (shifted, _) = self.ticks.quot_rem(&tier.ticks());
let (_, r) = shifted.quot_rem(&<Ticks as TickInt>::from_u64(GROUP_BASE as u64));
let b = r.to_canonical_bytes();
u16::from_be_bytes([b[CANONICAL_BYTES - 2], b[CANONICAL_BYTES - 1]])
}
#[cfg(feature = "alloc")]
pub fn groups(&self, from: Tier, to: Tier) -> Result<Vec<u16>> {
if from < to {
return Err(TimeError::with_context(
Code::E0006,
"group range must descend",
));
}
let mut out = Vec::new();
let mut k = from.index();
while k >= to.index() {
out.push(self.tier_value(Tier::new(k)?));
k -= 1;
}
Ok(out)
}
pub fn floor_to(&self, tier: Tier) -> Self {
let t = tier.ticks();
let (q, _) = self.ticks.quot_rem(&t);
let ticks = q
.try_mul(&t)
.expect("floor of an in-domain value is in domain");
Instant {
ticks,
_p: PhantomData,
}
}
pub fn ceil_to(&self, tier: Tier) -> Result<Self> {
let t = tier.ticks();
let (q, r) = self.ticks.quot_rem(&t);
if r.is_zero_ticks() {
return Ok(self.clone());
}
let next = q
.try_add(&<Ticks as TickInt>::one())
.and_then(|n| n.try_mul(&t))
.ok_or(TimeError::new(Code::E0021))?;
Self::from_ticks(next)
}
pub fn round_to(&self, tier: Tier, mode: Rounding) -> Result<Self> {
let t = tier.ticks();
let (q, r) = self.ticks.quot_rem(&t);
if r.is_zero_ticks() {
return Ok(self.clone());
}
let up = match mode {
Rounding::Trunc => false,
Rounding::Ceil => true,
Rounding::HalfUp | Rounding::HalfEven => {
let twice = r
.try_add(&r)
.expect("2r < 2 x tier, which is in domain");
match twice.cmp(&t) {
Ordering::Greater => true,
Ordering::Less => false,
Ordering::Equal => match mode {
Rounding::HalfUp => true,
_ => q.is_odd(),
},
}
}
};
if up {
self.ceil_to(tier)
} else {
Ok(self.floor_to(tier))
}
}
pub fn window_at(&self, precision: Precision) -> Result<Window<P>> {
match precision {
Precision::Tick => Window::new(self.clone(), self.clone()),
Precision::Tier(tier) => {
if tier.is_tick() {
return Window::new(self.clone(), self.clone());
}
let lo = self.floor_to(tier);
let span = tier
.ticks()
.try_sub(&<Ticks as TickInt>::one())
.expect("a tier is at least one tick");
let hi_ticks = match lo.ticks.try_add(&span) {
Some(t) if t <= P::domain_max() => t,
_ => P::domain_max(),
};
Window::new(lo, Self::from_ticks(hi_ticks)?)
}
}
}
pub fn since(&self, earlier: &Self) -> Result<Delta> {
self.ticks
.try_sub(&earlier.ticks)
.map(Delta::from_ticks)
.ok_or(TimeError::new(Code::E0020))
}
pub fn between(&self, other: &Self) -> Signed {
match self.ticks.cmp(&other.ticks) {
Ordering::Greater | Ordering::Equal => Signed::new(
Sign::Positive,
Delta::from_ticks(
self.ticks
.try_sub(&other.ticks)
.expect("self >= other"),
),
),
Ordering::Less => Signed::new(
Sign::Negative,
Delta::from_ticks(
other
.ticks
.try_sub(&self.ticks)
.expect("other > self"),
),
),
}
}
pub fn checked_add(&self, d: &Delta) -> Result<Self> {
let ticks = self
.ticks
.try_add(d.ticks())
.ok_or(TimeError::new(Code::E0021))?;
Self::from_ticks(ticks)
}
pub fn checked_sub(&self, d: &Delta) -> Result<Self> {
self.ticks
.try_sub(d.ticks())
.map(|ticks| Instant {
ticks,
_p: PhantomData,
})
.ok_or(TimeError::new(Code::E0020))
}
pub fn to_bytes(&self) -> [u8; CANONICAL_BYTES] {
self.ticks.to_canonical_bytes()
}
pub fn from_bytes(bytes: &[u8; CANONICAL_BYTES]) -> Result<Self> {
let ticks =
<Ticks as TickInt>::from_canonical_bytes(bytes).ok_or(TimeError::new(Code::E0021))?;
Self::from_ticks(ticks)
}
pub fn rebase<Q: Profile>(&self) -> Result<(Instant<Q>, Signed)> {
let from = P::origin_offset();
let to = Q::origin_offset();
let (shift, ticks) = match to.cmp(&from) {
Ordering::Greater | Ordering::Equal => {
let d = to.try_sub(&from).expect("to >= from");
(
Signed::new(Sign::Positive, Delta::from_ticks(d.clone())),
self.ticks
.try_add(&d)
.ok_or(TimeError::new(Code::E0021))?,
)
}
Ordering::Less => {
let d = from.try_sub(&to).expect("from > to");
(
Signed::new(Sign::Negative, Delta::from_ticks(d.clone())),
self.ticks
.try_sub(&d)
.ok_or(TimeError::new(Code::E0020))?,
)
}
};
Ok((Instant::<Q>::from_ticks(ticks)?, shift))
}
}
pub struct Window<P: Profile> {
lo: Instant<P>,
hi: Instant<P>,
}
impl<P: Profile> Clone for Window<P> {
fn clone(&self) -> Self {
Window {
lo: self.lo.clone(),
hi: self.hi.clone(),
}
}
}
#[cfg(feature = "u512")]
impl<P: Profile> Copy for Window<P> {}
impl<P: Profile> PartialEq for Window<P> {
fn eq(&self, other: &Self) -> bool {
self.lo == other.lo && self.hi == other.hi
}
}
impl<P: Profile> Eq for Window<P> {}
impl<P: Profile> core::fmt::Debug for Window<P> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Window<{}>[{:?}, {:?}]", P::TAG, self.lo.ticks, self.hi.ticks)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum IntervalOrdering {
Before,
After,
EqualExact,
Indeterminate,
}
impl<P: Profile> Window<P> {
pub fn new(lo: Instant<P>, hi: Instant<P>) -> Result<Self> {
if lo > hi {
return Err(TimeError::new(Code::E0022));
}
Ok(Window { lo, hi })
}
pub fn exact(at: Instant<P>) -> Self {
Window {
lo: at.clone(),
hi: at,
}
}
pub fn lo(&self) -> &Instant<P> {
&self.lo
}
pub fn hi(&self) -> &Instant<P> {
&self.hi
}
pub fn width(&self) -> Delta {
self.hi
.since(&self.lo)
.expect("Window maintains lo <= hi")
}
pub fn is_exact(&self) -> bool {
self.lo == self.hi
}
pub fn contains(&self, t: &Instant<P>) -> bool {
self.lo <= *t && *t <= self.hi
}
pub fn overlaps(&self, other: &Self) -> bool {
self.lo <= other.hi && other.lo <= self.hi
}
pub fn compare(&self, other: &Self) -> IntervalOrdering {
if self.is_exact() && other.is_exact() && self.lo == other.lo {
return IntervalOrdering::EqualExact;
}
if self.hi < other.lo {
IntervalOrdering::Before
} else if self.lo > other.hi {
IntervalOrdering::After
} else {
IntervalOrdering::Indeterminate
}
}
pub fn try_compare(&self, other: &Self) -> Result<Ordering> {
match self.compare(other) {
IntervalOrdering::Before => Ok(Ordering::Less),
IntervalOrdering::After => Ok(Ordering::Greater),
IntervalOrdering::EqualExact => Ok(Ordering::Equal),
IntervalOrdering::Indeterminate => Err(TimeError::new(Code::E0023)),
}
}
pub fn checked_add(&self, d: &Delta) -> Result<Self> {
Window::new(self.lo.checked_add(d)?, self.hi.checked_add(d)?)
}
pub fn checked_sub(&self, d: &Delta) -> Result<Self> {
Window::new(self.lo.checked_sub(d)?, self.hi.checked_sub(d)?)
}
pub fn widen(&self, d: &Delta) -> Result<(Self, bool)> {
let hi = self.hi.checked_add(d)?;
match self.lo.checked_sub(d) {
Ok(lo) => Ok((Window::new(lo, hi)?, false)),
Err(_) => Ok((Window::new(Instant::zero(), hi)?, true)),
}
}
pub fn checked_add_span(&self, s: &Span) -> Result<Self> {
Window::new(self.lo.checked_add(s.lo())?, self.hi.checked_add(s.hi())?)
}
pub fn checked_sub_span(&self, s: &Span) -> Result<Self> {
Window::new(self.lo.checked_sub(s.hi())?, self.hi.checked_sub(s.lo())?)
}
pub fn since_window(&self, earlier: &Self) -> Result<(Span, bool)> {
let hi = self.hi.since(&earlier.lo)?;
match self.lo.since(&earlier.hi) {
Ok(lo) => Ok((Span::new(lo, hi)?, false)),
Err(_) => Ok((Span::new(Delta::zero(), hi)?, true)),
}
}
pub fn hull(&self, other: &Self) -> Self {
Window {
lo: if self.lo <= other.lo {
self.lo.clone()
} else {
other.lo.clone()
},
hi: if self.hi >= other.hi {
self.hi.clone()
} else {
other.hi.clone()
},
}
}
pub fn intersect(&self, other: &Self) -> Option<Self> {
if !self.overlaps(other) {
return None;
}
Some(Window {
lo: if self.lo >= other.lo {
self.lo.clone()
} else {
other.lo.clone()
},
hi: if self.hi <= other.hi {
self.hi.clone()
} else {
other.hi.clone()
},
})
}
pub fn midpoint(&self, mode: Rounding) -> Result<Instant<P>> {
let width = self.width();
let (half, rem) = width.divmod(&Delta::from_u64(2))?;
let mut ticks = self
.lo
.ticks
.try_add(half.ticks())
.ok_or(TimeError::new(Code::E0021))?;
if !rem.is_zero() {
let bump = match mode {
Rounding::Trunc => false,
Rounding::Ceil | Rounding::HalfUp => true,
Rounding::HalfEven => ticks.is_odd(),
};
if bump {
ticks = ticks
.try_add(&<Ticks as TickInt>::one())
.ok_or(TimeError::new(Code::E0021))?;
}
}
Instant::from_ticks(ticks)
}
}
#[cfg_attr(feature = "u512", derive(Copy))]
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Span {
lo: Delta,
hi: Delta,
}
impl Span {
pub fn new(lo: Delta, hi: Delta) -> Result<Span> {
if lo > hi {
return Err(TimeError::new(Code::E0022));
}
Ok(Span { lo, hi })
}
pub fn exact(d: Delta) -> Span {
Span {
lo: d.clone(),
hi: d,
}
}
pub fn zero() -> Span {
Span::exact(Delta::zero())
}
pub fn lo(&self) -> &Delta {
&self.lo
}
pub fn hi(&self) -> &Delta {
&self.hi
}
pub fn uncertainty(&self) -> Delta {
self.hi
.checked_sub(&self.lo)
.expect("Span maintains lo <= hi")
}
pub fn is_exact(&self) -> bool {
self.lo == self.hi
}
pub fn checked_add(&self, other: &Span) -> Result<Span> {
Span::new(
self.lo.checked_add(&other.lo)?,
self.hi.checked_add(&other.hi)?,
)
}
pub fn checked_sub(&self, other: &Span) -> Result<(Span, bool)> {
let hi = self.hi.checked_sub(&other.lo)?;
match self.lo.checked_sub(&other.hi) {
Ok(lo) => Ok((Span::new(lo, hi)?, false)),
Err(_) => Ok((Span::new(Delta::zero(), hi)?, true)),
}
}
pub fn midpoint(&self, mode: Rounding) -> Result<Delta> {
let (half, rem) = self.uncertainty().divmod(&Delta::from_u64(2))?;
let mut d = self.lo.checked_add(&half)?;
if !rem.is_zero() {
let bump = match mode {
Rounding::Trunc => false,
Rounding::Ceil | Rounding::HalfUp => true,
Rounding::HalfEven => d.ticks().is_odd(),
};
if bump {
d = d.checked_add(&Delta::one_tick())?;
}
}
Ok(d)
}
}
pub struct Stated<P: Profile> {
value: Instant<P>,
precision: Precision,
}
impl<P: Profile> Clone for Stated<P> {
fn clone(&self) -> Self {
Stated {
value: self.value.clone(),
precision: self.precision,
}
}
}
#[cfg(feature = "u512")]
impl<P: Profile> Copy for Stated<P> {}
impl<P: Profile> PartialEq for Stated<P> {
fn eq(&self, other: &Self) -> bool {
self.value == other.value && self.precision == other.precision
}
}
impl<P: Profile> Eq for Stated<P> {}
impl<P: Profile> core::fmt::Debug for Stated<P> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"Stated<{}>({:?} @ {:?})",
P::TAG,
self.value.ticks(),
self.precision
)
}
}
impl<P: Profile> Stated<P> {
pub fn new(value: Instant<P>, precision: Precision) -> Self {
Stated { value, precision }
}
pub fn exact(value: Instant<P>) -> Self {
Stated {
value,
precision: Precision::Tick,
}
}
pub fn value(&self) -> &Instant<P> {
&self.value
}
pub fn precision(&self) -> Precision {
self.precision
}
pub fn is_exact(&self) -> bool {
self.precision.is_exact()
}
pub fn window(&self) -> Result<Window<P>> {
self.value.window_at(self.precision)
}
pub fn compare(&self, other: &Self) -> Result<IntervalOrdering> {
Ok(self.window()?.compare(&other.window()?))
}
pub fn try_compare(&self, other: &Self) -> Result<Ordering> {
self.window()?.try_compare(&other.window()?)
}
pub fn coarsen(&self, to: Tier) -> Result<Stated<P>> {
if to < self.precision.tier() {
return Err(TimeError::with_context(
Code::E0023,
"cannot restate at a finer precision than the value was given at",
));
}
Ok(Stated {
value: self.value.floor_to(to),
precision: if to.is_tick() {
Precision::Tick
} else {
Precision::Tier(to)
},
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::profile::UC1;
type I = Instant<UC1>;
fn at(n: u64) -> I {
I::from_u64(n).unwrap()
}
fn d(n: u64) -> Delta {
Delta::from_u64(n)
}
#[test]
fn nothing_precedes_the_datum() {
assert_eq!(I::zero().ticks(), &<Ticks as TickInt>::zero());
let err = I::zero().checked_sub(&Delta::one_tick()).unwrap_err();
assert_eq!(err.code, Code::E0020);
assert_eq!(at(5).checked_sub(&d(6)).unwrap_err().code, Code::E0020);
assert_eq!(at(5).checked_sub(&d(5)).unwrap(), I::zero());
}
#[test]
fn since_fails_backwards_but_between_does_not() {
let early = at(10);
let late = at(30);
assert_eq!(late.since(&early).unwrap(), d(20));
assert_eq!(early.since(&late).unwrap_err().code, Code::E0020);
let b = early.between(&late);
assert_eq!(b.sign(), Sign::Negative);
assert_eq!(b.magnitude(), &d(20));
let f = late.between(&early);
assert_eq!(f.sign(), Sign::Positive);
assert_eq!(f.magnitude(), &d(20));
assert_eq!(early.between(&early).sign(), Sign::Positive);
assert!(early.between(&early).is_zero());
}
#[test]
fn domain_ceiling_is_enforced() {
let max = I::from_ticks(<Ticks as TickInt>::domain_max()).unwrap();
assert_eq!(
max.checked_add(&Delta::one_tick()).unwrap_err().code,
Code::E0021
);
assert_eq!(max.checked_add(&Delta::zero()).unwrap(), max.clone());
assert_eq!(<Ticks as TickInt>::domain_max().bit_len(), 512);
}
#[test]
fn order_is_chronological_and_total() {
let a = at(1);
let b = at(2);
assert!(a < b && b > a && a == a.clone());
assert_eq!(a.cmp(&b), Ordering::Less);
for (x, y) in [(1u64, 2u64), (2, 1), (7, 7)] {
let (x, y) = (at(x), at(y));
let n = [x < y, x == y, x > y].iter().filter(|b| **b).count();
assert_eq!(n, 1);
}
}
#[test]
fn floor_ceil_bracket_the_value() {
let t = Tier::BEAT;
let beat = t.ticks();
let v = I::from_ticks(beat.try_mul(&<Ticks as TickInt>::from_u64(2)).unwrap())
.unwrap()
.checked_add(&Delta::one_tick())
.unwrap();
let lo = v.floor_to(t);
let hi = v.ceil_to(t).unwrap();
assert!(lo <= v && v <= hi);
assert_eq!(lo.ticks(), &beat.try_mul(&<Ticks as TickInt>::from_u64(2)).unwrap());
assert_eq!(hi.ticks(), &beat.try_mul(&<Ticks as TickInt>::from_u64(3)).unwrap());
assert_eq!(lo.floor_to(t), lo);
assert_eq!(lo.ceil_to(t).unwrap(), lo);
}
#[test]
fn truncation_is_monotone() {
let t = Tier::ARC;
let step = Tier::BEAT.ticks();
let mut prev: Option<I> = None;
for n in 0..40u64 {
let v = I::from_ticks(step.try_mul(&<Ticks as TickInt>::from_u64(n * 7)).unwrap())
.unwrap();
let f = v.floor_to(t);
if let Some(p) = prev {
assert!(p <= f);
}
prev = Some(f);
}
}
#[test]
fn rounding_modes_behave() {
let t = Tier::new(-11).unwrap(); let unit = t.ticks();
let half = unit.quot_rem(&<Ticks as TickInt>::from_u64(2)).0;
let mk = |mult: u64, extra: &Ticks| {
I::from_ticks(
unit.try_mul(&<Ticks as TickInt>::from_u64(mult))
.unwrap()
.try_add(extra)
.unwrap(),
)
.unwrap()
};
let below = mk(2, &half);
assert_eq!(below.round_to(t, Rounding::Trunc).unwrap(), mk(2, &<Ticks as TickInt>::zero()));
assert_eq!(below.round_to(t, Rounding::Ceil).unwrap(), mk(3, &<Ticks as TickInt>::zero()));
assert_eq!(
below.round_to(t, Rounding::HalfEven).unwrap(),
mk(2, &<Ticks as TickInt>::zero())
);
let above = mk(2, &half.try_add(&<Ticks as TickInt>::one()).unwrap());
assert_eq!(
above.round_to(t, Rounding::HalfEven).unwrap(),
mk(3, &<Ticks as TickInt>::zero())
);
let w = Window::new(at(0), at(3)).unwrap();
assert_eq!(w.midpoint(Rounding::Trunc).unwrap(), at(1));
assert_eq!(w.midpoint(Rounding::Ceil).unwrap(), at(2));
assert_eq!(w.midpoint(Rounding::HalfUp).unwrap(), at(2));
assert_eq!(w.midpoint(Rounding::HalfEven).unwrap(), at(2));
let w2 = Window::new(at(0), at(4)).unwrap();
for m in [Rounding::Trunc, Rounding::Ceil, Rounding::HalfEven, Rounding::HalfUp] {
assert_eq!(w2.midpoint(m).unwrap(), at(2));
}
}
#[test]
fn tier_precision_denotes_a_closed_interval() {
let t = Tier::BEAT;
let v = at(12_345);
let w = v.window_at(Precision::Tier(t)).unwrap();
assert_eq!(w.lo(), &v.floor_to(t));
assert_eq!(
w.width().ticks(),
&t.ticks().try_sub(&<Ticks as TickInt>::one()).unwrap()
);
assert!(w.contains(&v));
assert!(!w.is_exact());
let e = v.window_at(Precision::Tick).unwrap();
assert!(e.is_exact());
assert_eq!(e.width(), Delta::zero());
assert_eq!(e.lo(), e.hi());
assert!(v.window_at(Precision::Tier(Tier::TICK)).unwrap().is_exact());
}
#[test]
fn comparison_across_precision_can_be_indeterminate() {
let t = Tier::BEAT;
let a = at(10).window_at(Precision::Tier(t)).unwrap();
let b = at(20).window_at(Precision::Tier(t)).unwrap();
assert_eq!(a.compare(&b), IntervalOrdering::Indeterminate);
assert_eq!(a.try_compare(&b).unwrap_err().code, Code::E0023);
let far = I::from_ticks(t.ticks().try_mul(&<Ticks as TickInt>::from_u64(5)).unwrap())
.unwrap()
.window_at(Precision::Tier(t))
.unwrap();
assert_eq!(a.compare(&far), IntervalOrdering::Before);
assert_eq!(far.compare(&a), IntervalOrdering::After);
assert_eq!(a.try_compare(&far).unwrap(), Ordering::Less);
let x = Window::exact(at(7));
assert_eq!(x.compare(&Window::exact(at(7))), IntervalOrdering::EqualExact);
}
#[test]
fn window_arithmetic_is_interval_arithmetic() {
let w = Window::new(at(10), at(20)).unwrap();
let s = w.checked_add(&d(5)).unwrap();
assert_eq!((s.lo(), s.hi()), (&at(15), &at(25)));
assert_eq!(s.width(), w.width());
assert_eq!(Window::new(at(20), at(10)).unwrap_err().code, Code::E0022);
let (wide, clipped) = w.widen(&d(5)).unwrap();
assert_eq!((wide.lo(), wide.hi()), (&at(5), &at(25)));
assert!(!clipped);
let (wide2, clipped2) = w.widen(&d(50)).unwrap();
assert_eq!(wide2.lo(), &I::zero());
assert!(clipped2);
let other = Window::new(at(15), at(30)).unwrap();
assert!(w.overlaps(&other));
assert_eq!(w.hull(&other), Window::new(at(10), at(30)).unwrap());
assert_eq!(w.intersect(&other), Some(Window::new(at(15), at(20)).unwrap()));
assert_eq!(w.intersect(&Window::new(at(40), at(50)).unwrap()), None);
}
#[test]
fn signed_window_is_inert() {
let claim = UC1::big_bang_claim();
assert_eq!(claim.lo().sign(), Sign::Negative);
assert_eq!(claim.hi().sign(), Sign::Positive);
#[cfg(feature = "alloc")]
assert!(claim.describe().contains("ticks"));
}
#[test]
fn canonical_binary_is_64_bytes_and_round_trips() {
for n in [0u64, 1, 255, 256, 65_535, u64::MAX] {
let v = at(n);
let b = v.to_bytes();
assert_eq!(b.len(), 64);
assert_eq!(I::from_bytes(&b).unwrap(), v);
}
assert_eq!(I::zero().to_bytes(), [0u8; 64]);
let max = I::from_ticks(<Ticks as TickInt>::domain_max()).unwrap();
assert_eq!(max.to_bytes(), [0xffu8; 64]);
}
#[test]
fn byte_order_is_chronological_order() {
let mut vals: Vec<I> = (0..64u64).map(|i| at(i * 2_654_435_761)).collect();
vals.push(at(0));
vals.push(I::from_ticks(<Ticks as TickInt>::domain_max()).unwrap());
vals.sort();
let mut bytes: Vec<[u8; 64]> = vals.iter().map(|v| v.to_bytes()).collect();
let numeric = bytes.clone();
bytes.sort();
assert_eq!(bytes, numeric, "byte order diverges from numeric order");
}
#[test]
fn rebase_is_identity_within_a_profile() {
let v = at(1_000_000);
let (out, shift) = v.rebase::<UC1>().unwrap();
assert_eq!(out, v);
assert!(shift.is_zero());
}
#[test]
fn delta_arithmetic() {
assert_eq!(d(3).checked_add(&d(4)).unwrap(), d(7));
assert_eq!(d(3).checked_sub(&d(4)).unwrap_err().code, Code::E0020);
assert_eq!(d(12).mul_u64(3).unwrap(), d(36));
assert_eq!(d(12).div_u64(5).unwrap(), d(2));
assert_eq!(d(12).divmod(&d(5)).unwrap(), (d(2), d(2)));
assert_eq!(d(1).div_u64(0).unwrap_err().code, Code::E0021);
assert!(Delta::zero().is_zero());
assert_eq!(Delta::one_tick(), d(1));
}
#[test]
fn delta_tier_of_finds_the_largest_fitting_tier() {
let beat = Delta::from_ticks(Tier::BEAT.ticks());
assert_eq!(beat.tier_of(), Some(Tier::BEAT));
let just_under = beat.checked_sub(&Delta::one_tick()).unwrap();
assert_eq!(just_under.tier_of(), Some(Tier::new(-1).unwrap()));
assert_eq!(Delta::one_tick().tier_of(), Some(Tier::TICK));
assert_eq!(Delta::zero().tier_of(), None);
let three_beats = Delta::from_tier(Tier::BEAT, 3).unwrap();
assert_eq!(three_beats.in_tier(Tier::BEAT).0, <Ticks as TickInt>::from_u64(3));
assert!(three_beats.in_tier(Tier::BEAT).1.is_zero_ticks());
}
#[test]
fn tier_values_are_base_5_groups() {
let v = I::from_ticks(
Tier::BEAT
.ticks()
.try_mul(&<Ticks as TickInt>::from_u64(2))
.unwrap()
.try_add(
&Tier::ARC
.ticks()
.try_mul(&<Ticks as TickInt>::from_u64(3))
.unwrap(),
)
.unwrap(),
)
.unwrap();
assert_eq!(v.tier_value(Tier::BEAT), 2);
assert_eq!(v.tier_value(Tier::ARC), 3);
assert_eq!(v.tier_value(Tier::SWEEP), 0);
for t in Tier::all_ascending() {
assert!(v.tier_value(t) < GROUP_BASE);
}
}
#[cfg(feature = "alloc")]
#[test]
fn groups_descend_and_reassemble() {
let v = at(987_654_321);
let gs = v.groups(Tier::BEAT, Tier::TICK).unwrap();
assert_eq!(gs.len(), 13); let mut acc = <Ticks as TickInt>::zero();
let base = <Ticks as TickInt>::from_u64(GROUP_BASE as u64);
for g in &gs {
acc = acc
.try_mul(&base)
.unwrap()
.try_add(&<Ticks as TickInt>::from_u64(*g as u64))
.unwrap();
}
assert_eq!(&acc, v.ticks());
assert_eq!(
v.groups(Tier::TICK, Tier::BEAT).unwrap_err().code,
Code::E0006
);
}
}
#[cfg(test)]
mod rule_u_tests {
use super::*;
use crate::profile::UC1;
type I = Instant<UC1>;
fn at(n: u64) -> I {
I::from_u64(n).unwrap()
}
fn d(n: u64) -> Delta {
Delta::from_u64(n)
}
fn w(lo: u64, hi: u64) -> Window<UC1> {
Window::new(at(lo), at(hi)).unwrap()
}
fn sp(lo: u64, hi: u64) -> Span {
Span::new(d(lo), d(hi)).unwrap()
}
#[test]
fn span_is_an_interval_of_magnitudes() {
let s = sp(10, 20);
assert_eq!(s.uncertainty(), d(10));
assert!(!s.is_exact());
assert!(Span::exact(d(7)).is_exact());
assert_eq!(Span::exact(d(7)).uncertainty(), Delta::zero());
assert!(Span::zero().is_exact());
assert_eq!(Span::new(d(20), d(10)).unwrap_err().code, Code::E0022);
}
#[test]
fn span_addition_is_interval_addition() {
let a = sp(10, 20);
let b = sp(1, 5);
let s = a.checked_add(&b).unwrap();
assert_eq!((s.lo(), s.hi()), (&d(11), &d(25)));
assert_eq!(s.uncertainty(), d(14));
assert!(s.uncertainty() >= a.uncertainty());
assert!(s.uncertainty() >= b.uncertainty());
}
#[test]
fn span_subtraction_clamps_at_zero_and_says_so() {
let (s, clamped) = sp(10, 20).checked_sub(&sp(1, 5)).unwrap();
assert_eq!((s.lo(), s.hi()), (&d(5), &d(19)));
assert!(!clamped);
let (s, clamped) = sp(10, 20).checked_sub(&sp(15, 25)).unwrap();
assert_eq!(s.lo(), &Delta::zero());
assert_eq!(s.hi(), &d(5));
assert!(clamped, "the clamp must be reported, not hidden");
assert_eq!(
sp(1, 2).checked_sub(&sp(10, 20)).unwrap_err().code,
Code::E0020
);
}
#[test]
fn span_midpoint_must_be_asked_for() {
let s = sp(0, 3);
assert_eq!(s.midpoint(Rounding::Trunc).unwrap(), d(1));
assert_eq!(s.midpoint(Rounding::Ceil).unwrap(), d(2));
assert_eq!(s.midpoint(Rounding::HalfUp).unwrap(), d(2));
assert_eq!(sp(0, 4).midpoint(Rounding::Trunc).unwrap(), d(2));
}
#[test]
fn uncertain_instant_plus_uncertain_duration_widens() {
let anchor = w(100, 110); let elapsed = sp(1000, 1005); let out = anchor.checked_add_span(&elapsed).unwrap();
assert_eq!((out.lo(), out.hi()), (&at(1100), &at(1115)));
assert_eq!(out.width(), d(15));
assert!(out.width() >= anchor.width());
assert!(out.width() >= elapsed.uncertainty());
}
#[test]
fn subtracting_a_span_is_outward_and_strict_at_the_datum() {
let out = w(1000, 1010).checked_sub_span(&sp(100, 200)).unwrap();
assert_eq!((out.lo(), out.hi()), (&at(800), &at(910)));
assert!(out.width() > w(1000, 1010).width());
assert_eq!(
w(10, 20).checked_sub_span(&sp(0, 100)).unwrap_err().code,
Code::E0020
);
}
#[test]
fn elapsed_between_windows_is_a_span() {
let earlier = w(100, 110);
let later = w(1000, 1010);
let (s, clamped) = later.since_window(&earlier).unwrap();
assert_eq!((s.lo(), s.hi()), (&d(890), &d(910)));
assert!(!clamped);
let (s, clamped) = w(100, 200).since_window(&w(150, 250)).unwrap();
assert_eq!(s.lo(), &Delta::zero());
assert_eq!(s.hi(), &d(50));
assert!(clamped);
assert_eq!(
w(10, 20).since_window(&w(100, 200)).unwrap_err().code,
Code::E0020
);
}
#[test]
fn round_trip_through_span_preserves_containment() {
let start = w(10_000, 10_050);
let s = sp(300, 320);
let moved = start.checked_add_span(&s).unwrap();
let back = moved.checked_sub_span(&s).unwrap();
assert!(back.contains(start.lo()));
assert!(back.contains(start.hi()));
assert!(back.width() >= start.width());
}
#[test]
fn stated_comparison_uses_interval_semantics() {
let a = Stated::new(at(10).floor_to(Tier::BEAT), Precision::Tier(Tier::BEAT));
let b = Stated::new(at(20).floor_to(Tier::BEAT), Precision::Tier(Tier::BEAT));
assert_eq!(a.compare(&b).unwrap(), IntervalOrdering::Indeterminate);
assert_eq!(a.try_compare(&b).unwrap_err().code, Code::E0023);
let x = Stated::exact(at(7));
assert_eq!(
x.compare(&Stated::exact(at(7))).unwrap(),
IntervalOrdering::EqualExact
);
assert_eq!(x.try_compare(&Stated::exact(at(8))).unwrap(), Ordering::Less);
assert!(x.is_exact());
}
#[test]
fn stated_comparison_across_unequal_precision() {
let coarse = Stated::new(at(0).floor_to(Tier::BEAT), Precision::Tier(Tier::BEAT));
let fine = Stated::exact(at(5));
assert_eq!(coarse.compare(&fine).unwrap(), IntervalOrdering::Indeterminate);
assert_eq!(coarse.try_compare(&fine).unwrap_err().code, Code::E0023);
let far = Stated::exact(
I::from_ticks(
Tier::BEAT
.ticks()
.try_mul(&<Ticks as TickInt>::from_u64(3))
.unwrap(),
)
.unwrap(),
);
assert_eq!(coarse.try_compare(&far).unwrap(), Ordering::Less);
assert_eq!(far.try_compare(&coarse).unwrap(), Ordering::Greater);
}
#[test]
fn stated_cannot_be_refined_only_coarsened() {
let s = Stated::new(at(1_000_000).floor_to(Tier::BEAT), Precision::Tier(Tier::BEAT));
assert!(s.coarsen(Tier::ARC).is_ok());
assert_eq!(s.coarsen(Tier::ARC).unwrap().precision(), Precision::Tier(Tier::ARC));
assert_eq!(
s.coarsen(Tier::new(-3).unwrap()).unwrap_err().code,
Code::E0023
);
assert_eq!(s.coarsen(Tier::TICK).unwrap_err().code, Code::E0023);
assert!(s.coarsen(Tier::ARC).unwrap().window().unwrap().width() > s.window().unwrap().width());
}
#[test]
fn stated_equality_is_about_the_statement() {
let a = Stated::new(at(0), Precision::Tier(Tier::BEAT));
let b = Stated::new(at(0), Precision::Tier(Tier::ARC));
assert_ne!(a, b);
assert_eq!(a, Stated::new(at(0), Precision::Tier(Tier::BEAT)));
}
#[test]
fn coarse_window_at_the_ceiling_is_clipped_to_the_domain() {
let top = I::from_ticks(<Ticks as TickInt>::domain_max()).unwrap();
let t32 = Tier::new(crate::tier::K_MAX).unwrap();
let win = top.window_at(Precision::Tier(t32)).unwrap();
assert!(win.contains(&top));
assert_eq!(win.hi(), &top);
assert_eq!(win.lo(), &top.floor_to(t32));
assert!(!win.is_exact());
}
}