1#[cfg(feature = "alloc")]
18use alloc::vec::Vec;
19use core::cmp::Ordering;
20use core::marker::PhantomData;
21
22use crate::backend::{TickInt, Ticks, CANONICAL_BYTES};
23use crate::error::{Code, Result, TimeError};
24use crate::profile::Profile;
25use crate::tier::{Tier, GROUP_BASE};
26
27#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
33pub enum Rounding {
34 Trunc,
36 Ceil,
38 #[default]
40 HalfEven,
41 HalfUp,
43}
44
45#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
50pub enum Precision {
51 Tick,
53 Tier(Tier),
55}
56
57impl Precision {
58 pub fn tier(self) -> Tier {
60 match self {
61 Precision::Tick => Tier::TICK,
62 Precision::Tier(t) => t,
63 }
64 }
65
66 pub fn is_exact(self) -> bool {
68 matches!(self, Precision::Tick) || self.tier().is_tick()
69 }
70}
71
72#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
74pub enum Sign {
75 Positive,
77 Negative,
79}
80
81#[cfg_attr(feature = "u512", derive(Copy))]
88#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
89pub struct Delta {
90 ticks: Ticks,
91}
92
93impl Delta {
94 pub fn zero() -> Self {
96 Delta {
97 ticks: <Ticks as TickInt>::zero(),
98 }
99 }
100
101 pub fn one_tick() -> Self {
103 Delta {
104 ticks: <Ticks as TickInt>::one(),
105 }
106 }
107
108 pub fn from_ticks(ticks: Ticks) -> Self {
110 Delta { ticks }
111 }
112
113 pub fn from_u64(v: u64) -> Self {
115 Delta {
116 ticks: <Ticks as TickInt>::from_u64(v),
117 }
118 }
119
120 pub fn from_tier(tier: Tier, count: u64) -> Result<Self> {
122 let t = tier.ticks();
123 let c = <Ticks as TickInt>::from_u64(count);
124 t.try_mul(&c)
125 .map(|ticks| Delta { ticks })
126 .ok_or(TimeError::new(Code::E0021))
127 }
128
129 pub fn ticks(&self) -> &Ticks {
131 &self.ticks
132 }
133
134 pub fn is_zero(&self) -> bool {
136 self.ticks.is_zero_ticks()
137 }
138
139 pub fn checked_add(&self, other: &Delta) -> Result<Delta> {
141 self.ticks
142 .try_add(&other.ticks)
143 .map(|ticks| Delta { ticks })
144 .ok_or(TimeError::new(Code::E0021))
145 }
146
147 pub fn checked_sub(&self, other: &Delta) -> Result<Delta> {
149 self.ticks
150 .try_sub(&other.ticks)
151 .map(|ticks| Delta { ticks })
152 .ok_or(TimeError::new(Code::E0020))
153 }
154
155 pub fn mul_u64(&self, n: u64) -> Result<Delta> {
157 self.ticks
158 .try_mul(&<Ticks as TickInt>::from_u64(n))
159 .map(|ticks| Delta { ticks })
160 .ok_or(TimeError::new(Code::E0021))
161 }
162
163 pub fn div_u64(&self, n: u64) -> Result<Delta> {
165 if n == 0 {
166 return Err(TimeError::with_context(Code::E0021, "division by zero"));
167 }
168 let (q, _) = self.ticks.quot_rem(&<Ticks as TickInt>::from_u64(n));
169 Ok(Delta { ticks: q })
170 }
171
172 pub fn divmod(&self, divisor: &Delta) -> Result<(Delta, Delta)> {
174 if divisor.is_zero() {
175 return Err(TimeError::with_context(Code::E0021, "division by zero"));
176 }
177 let (q, r) = self.ticks.quot_rem(&divisor.ticks);
178 Ok((Delta { ticks: q }, Delta { ticks: r }))
179 }
180
181 pub fn tier_of(&self) -> Option<Tier> {
184 if self.is_zero() {
185 return None;
186 }
187 Tier::all_descending().find(|t| t.ticks() <= self.ticks)
188 }
189
190 pub fn in_tier(&self, tier: Tier) -> (Ticks, Ticks) {
192 self.ticks.quot_rem(&tier.ticks())
193 }
194}
195
196#[cfg_attr(feature = "u512", derive(Copy))]
203#[derive(Clone, PartialEq, Eq, Debug)]
204pub struct Signed {
205 sign: Sign,
206 mag: Delta,
207}
208
209impl Signed {
210 pub fn new(sign: Sign, mag: Delta) -> Self {
213 if mag.is_zero() {
214 Signed {
215 sign: Sign::Positive,
216 mag,
217 }
218 } else {
219 Signed { sign, mag }
220 }
221 }
222
223 pub fn zero() -> Self {
225 Signed::new(Sign::Positive, Delta::zero())
226 }
227
228 pub fn sign(&self) -> Sign {
230 self.sign
231 }
232
233 pub fn magnitude(&self) -> &Delta {
235 &self.mag
236 }
237
238 pub fn is_zero(&self) -> bool {
240 self.mag.is_zero()
241 }
242
243 pub fn into_magnitude(self) -> Delta {
246 self.mag
247 }
248}
249
250impl PartialOrd for Signed {
251 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
252 Some(self.cmp(other))
253 }
254}
255
256impl Ord for Signed {
257 fn cmp(&self, other: &Self) -> Ordering {
258 match (self.sign, other.sign) {
259 (Sign::Positive, Sign::Positive) => self.mag.cmp(&other.mag),
260 (Sign::Negative, Sign::Negative) => other.mag.cmp(&self.mag),
261 (Sign::Positive, Sign::Negative) => Ordering::Greater,
262 (Sign::Negative, Sign::Positive) => Ordering::Less,
263 }
264 }
265}
266
267#[cfg_attr(feature = "u512", derive(Copy))]
288#[derive(Clone, PartialEq, Eq, Debug)]
289pub struct SignedWindow {
290 lo: Signed,
291 hi: Signed,
292}
293
294impl SignedWindow {
295 pub fn new(lo: Signed, hi: Signed) -> Result<Self> {
297 if lo > hi {
298 return Err(TimeError::new(Code::E0022));
299 }
300 Ok(SignedWindow { lo, hi })
301 }
302
303 pub fn symmetric(half_width: Delta) -> Self {
307 SignedWindow {
308 lo: Signed::new(Sign::Negative, half_width.clone()),
309 hi: Signed::new(Sign::Positive, half_width),
310 }
311 }
312
313 pub fn lo(&self) -> &Signed {
315 &self.lo
316 }
317
318 pub fn hi(&self) -> &Signed {
320 &self.hi
321 }
322
323 #[cfg(feature = "alloc")]
326 pub fn describe(&self) -> alloc::string::String {
327 use alloc::format;
328 let s = |v: &Signed| {
329 let m = v.magnitude().ticks().to_dec_string();
330 match v.sign() {
331 Sign::Negative => format!("-{m}"),
332 Sign::Positive => m,
333 }
334 };
335 format!("[{}, {}] ticks", s(&self.lo), s(&self.hi))
336 }
337}
338
339pub struct Instant<P: Profile> {
350 ticks: Ticks,
351 _p: PhantomData<P>,
352}
353
354impl<P: Profile> Clone for Instant<P> {
359 fn clone(&self) -> Self {
360 Instant {
361 ticks: self.ticks.clone(),
362 _p: PhantomData,
363 }
364 }
365}
366
367#[cfg(feature = "u512")]
368impl<P: Profile> Copy for Instant<P> {}
369
370impl<P: Profile> PartialEq for Instant<P> {
371 fn eq(&self, other: &Self) -> bool {
372 self.ticks == other.ticks
373 }
374}
375
376impl<P: Profile> Eq for Instant<P> {}
377
378impl<P: Profile> PartialOrd for Instant<P> {
379 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
380 Some(self.cmp(other))
381 }
382}
383
384impl<P: Profile> Ord for Instant<P> {
388 fn cmp(&self, other: &Self) -> Ordering {
389 self.ticks.cmp(&other.ticks)
390 }
391}
392
393impl<P: Profile> core::hash::Hash for Instant<P> {
394 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
395 self.ticks.hash(state);
396 }
397}
398
399impl<P: Profile> core::fmt::Debug for Instant<P> {
400 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
401 write!(f, "Instant<{}>({:?})", P::TAG, self.ticks)
404 }
405}
406
407#[cfg(feature = "u512")]
414impl<P: Profile> Instant<P> {
415 pub const ZERO: Self = Instant {
417 ticks: bnum::types::U512::MIN,
418 _p: PhantomData,
419 };
420}
421
422impl<P: Profile> Instant<P> {
423 pub fn zero() -> Self {
427 Instant {
428 ticks: <Ticks as TickInt>::zero(),
429 _p: PhantomData,
430 }
431 }
432
433 pub fn from_ticks(ticks: Ticks) -> Result<Self> {
435 if ticks > P::domain_max() {
436 return Err(TimeError::new(Code::E0021));
437 }
438 Ok(Instant {
439 ticks,
440 _p: PhantomData,
441 })
442 }
443
444 pub fn from_u64(v: u64) -> Result<Self> {
446 Self::from_ticks(<Ticks as TickInt>::from_u64(v))
447 }
448
449 pub fn ticks(&self) -> &Ticks {
451 &self.ticks
452 }
453
454 pub fn tier_value(&self, tier: Tier) -> u16 {
456 let (shifted, _) = self.ticks.quot_rem(&tier.ticks());
457 let (_, r) = shifted.quot_rem(&<Ticks as TickInt>::from_u64(GROUP_BASE as u64));
458 let b = r.to_canonical_bytes();
461 u16::from_be_bytes([b[CANONICAL_BYTES - 2], b[CANONICAL_BYTES - 1]])
462 }
463
464 #[cfg(feature = "alloc")]
466 pub fn groups(&self, from: Tier, to: Tier) -> Result<Vec<u16>> {
467 if from < to {
468 return Err(TimeError::with_context(
469 Code::E0006,
470 "group range must descend",
471 ));
472 }
473 let mut out = Vec::new();
474 let mut k = from.index();
475 while k >= to.index() {
476 out.push(self.tier_value(Tier::new(k)?));
477 k -= 1;
478 }
479 Ok(out)
480 }
481
482 pub fn floor_to(&self, tier: Tier) -> Self {
484 let t = tier.ticks();
485 let (q, _) = self.ticks.quot_rem(&t);
486 let ticks = q
487 .try_mul(&t)
488 .expect("floor of an in-domain value is in domain");
489 Instant {
490 ticks,
491 _p: PhantomData,
492 }
493 }
494
495 pub fn ceil_to(&self, tier: Tier) -> Result<Self> {
497 let t = tier.ticks();
498 let (q, r) = self.ticks.quot_rem(&t);
499 if r.is_zero_ticks() {
500 return Ok(self.clone());
501 }
502 let next = q
503 .try_add(&<Ticks as TickInt>::one())
504 .and_then(|n| n.try_mul(&t))
505 .ok_or(TimeError::new(Code::E0021))?;
506 Self::from_ticks(next)
507 }
508
509 pub fn round_to(&self, tier: Tier, mode: Rounding) -> Result<Self> {
511 let t = tier.ticks();
512 let (q, r) = self.ticks.quot_rem(&t);
513 if r.is_zero_ticks() {
514 return Ok(self.clone());
515 }
516 let up = match mode {
517 Rounding::Trunc => false,
518 Rounding::Ceil => true,
519 Rounding::HalfUp | Rounding::HalfEven => {
520 let twice = r
521 .try_add(&r)
522 .expect("2r < 2 x tier, which is in domain");
523 match twice.cmp(&t) {
524 Ordering::Greater => true,
525 Ordering::Less => false,
526 Ordering::Equal => match mode {
527 Rounding::HalfUp => true,
528 _ => q.is_odd(),
530 },
531 }
532 }
533 };
534 if up {
535 self.ceil_to(tier)
536 } else {
537 Ok(self.floor_to(tier))
538 }
539 }
540
541 pub fn window_at(&self, precision: Precision) -> Result<Window<P>> {
547 match precision {
548 Precision::Tick => Window::new(self.clone(), self.clone()),
549 Precision::Tier(tier) => {
550 if tier.is_tick() {
551 return Window::new(self.clone(), self.clone());
552 }
553 let lo = self.floor_to(tier);
554 let span = tier
555 .ticks()
556 .try_sub(&<Ticks as TickInt>::one())
557 .expect("a tier is at least one tick");
558 let hi_ticks = match lo.ticks.try_add(&span) {
565 Some(t) if t <= P::domain_max() => t,
566 _ => P::domain_max(),
567 };
568 Window::new(lo, Self::from_ticks(hi_ticks)?)
569 }
570 }
571 }
572
573 pub fn since(&self, earlier: &Self) -> Result<Delta> {
576 self.ticks
577 .try_sub(&earlier.ticks)
578 .map(Delta::from_ticks)
579 .ok_or(TimeError::new(Code::E0020))
580 }
581
582 pub fn between(&self, other: &Self) -> Signed {
584 match self.ticks.cmp(&other.ticks) {
585 Ordering::Greater | Ordering::Equal => Signed::new(
586 Sign::Positive,
587 Delta::from_ticks(
588 self.ticks
589 .try_sub(&other.ticks)
590 .expect("self >= other"),
591 ),
592 ),
593 Ordering::Less => Signed::new(
594 Sign::Negative,
595 Delta::from_ticks(
596 other
597 .ticks
598 .try_sub(&self.ticks)
599 .expect("other > self"),
600 ),
601 ),
602 }
603 }
604
605 pub fn checked_add(&self, d: &Delta) -> Result<Self> {
607 let ticks = self
608 .ticks
609 .try_add(d.ticks())
610 .ok_or(TimeError::new(Code::E0021))?;
611 Self::from_ticks(ticks)
612 }
613
614 pub fn checked_sub(&self, d: &Delta) -> Result<Self> {
616 self.ticks
617 .try_sub(d.ticks())
618 .map(|ticks| Instant {
619 ticks,
620 _p: PhantomData,
621 })
622 .ok_or(TimeError::new(Code::E0020))
623 }
624
625 pub fn to_bytes(&self) -> [u8; CANONICAL_BYTES] {
631 self.ticks.to_canonical_bytes()
632 }
633
634 pub fn from_bytes(bytes: &[u8; CANONICAL_BYTES]) -> Result<Self> {
636 let ticks =
637 <Ticks as TickInt>::from_canonical_bytes(bytes).ok_or(TimeError::new(Code::E0021))?;
638 Self::from_ticks(ticks)
639 }
640
641 pub fn rebase<Q: Profile>(&self) -> Result<(Instant<Q>, Signed)> {
649 let from = P::origin_offset();
650 let to = Q::origin_offset();
651 let (shift, ticks) = match to.cmp(&from) {
652 Ordering::Greater | Ordering::Equal => {
653 let d = to.try_sub(&from).expect("to >= from");
654 (
655 Signed::new(Sign::Positive, Delta::from_ticks(d.clone())),
656 self.ticks
657 .try_add(&d)
658 .ok_or(TimeError::new(Code::E0021))?,
659 )
660 }
661 Ordering::Less => {
662 let d = from.try_sub(&to).expect("from > to");
663 (
664 Signed::new(Sign::Negative, Delta::from_ticks(d.clone())),
665 self.ticks
666 .try_sub(&d)
667 .ok_or(TimeError::new(Code::E0020))?,
668 )
669 }
670 };
671 Ok((Instant::<Q>::from_ticks(ticks)?, shift))
672 }
673}
674
675pub struct Window<P: Profile> {
685 lo: Instant<P>,
686 hi: Instant<P>,
687}
688
689impl<P: Profile> Clone for Window<P> {
691 fn clone(&self) -> Self {
692 Window {
693 lo: self.lo.clone(),
694 hi: self.hi.clone(),
695 }
696 }
697}
698
699#[cfg(feature = "u512")]
700impl<P: Profile> Copy for Window<P> {}
701
702impl<P: Profile> PartialEq for Window<P> {
703 fn eq(&self, other: &Self) -> bool {
704 self.lo == other.lo && self.hi == other.hi
705 }
706}
707
708impl<P: Profile> Eq for Window<P> {}
709
710impl<P: Profile> core::fmt::Debug for Window<P> {
711 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
712 write!(f, "Window<{}>[{:?}, {:?}]", P::TAG, self.lo.ticks, self.hi.ticks)
713 }
714}
715
716#[derive(Clone, Copy, PartialEq, Eq, Debug)]
725pub enum IntervalOrdering {
726 Before,
728 After,
730 EqualExact,
732 Indeterminate,
734}
735
736impl<P: Profile> Window<P> {
737 pub fn new(lo: Instant<P>, hi: Instant<P>) -> Result<Self> {
739 if lo > hi {
740 return Err(TimeError::new(Code::E0022));
741 }
742 Ok(Window { lo, hi })
743 }
744
745 pub fn exact(at: Instant<P>) -> Self {
747 Window {
748 lo: at.clone(),
749 hi: at,
750 }
751 }
752
753 pub fn lo(&self) -> &Instant<P> {
755 &self.lo
756 }
757
758 pub fn hi(&self) -> &Instant<P> {
760 &self.hi
761 }
762
763 pub fn width(&self) -> Delta {
766 self.hi
767 .since(&self.lo)
768 .expect("Window maintains lo <= hi")
769 }
770
771 pub fn is_exact(&self) -> bool {
773 self.lo == self.hi
774 }
775
776 pub fn contains(&self, t: &Instant<P>) -> bool {
778 self.lo <= *t && *t <= self.hi
779 }
780
781 pub fn overlaps(&self, other: &Self) -> bool {
783 self.lo <= other.hi && other.lo <= self.hi
784 }
785
786 pub fn compare(&self, other: &Self) -> IntervalOrdering {
788 if self.is_exact() && other.is_exact() && self.lo == other.lo {
789 return IntervalOrdering::EqualExact;
790 }
791 if self.hi < other.lo {
792 IntervalOrdering::Before
793 } else if self.lo > other.hi {
794 IntervalOrdering::After
795 } else {
796 IntervalOrdering::Indeterminate
797 }
798 }
799
800 pub fn try_compare(&self, other: &Self) -> Result<Ordering> {
803 match self.compare(other) {
804 IntervalOrdering::Before => Ok(Ordering::Less),
805 IntervalOrdering::After => Ok(Ordering::Greater),
806 IntervalOrdering::EqualExact => Ok(Ordering::Equal),
807 IntervalOrdering::Indeterminate => Err(TimeError::new(Code::E0023)),
808 }
809 }
810
811 pub fn checked_add(&self, d: &Delta) -> Result<Self> {
814 Window::new(self.lo.checked_add(d)?, self.hi.checked_add(d)?)
815 }
816
817 pub fn checked_sub(&self, d: &Delta) -> Result<Self> {
819 Window::new(self.lo.checked_sub(d)?, self.hi.checked_sub(d)?)
820 }
821
822 pub fn widen(&self, d: &Delta) -> Result<(Self, bool)> {
826 let hi = self.hi.checked_add(d)?;
827 match self.lo.checked_sub(d) {
828 Ok(lo) => Ok((Window::new(lo, hi)?, false)),
829 Err(_) => Ok((Window::new(Instant::zero(), hi)?, true)),
830 }
831 }
832
833 pub fn checked_add_span(&self, s: &Span) -> Result<Self> {
840 Window::new(self.lo.checked_add(s.lo())?, self.hi.checked_add(s.hi())?)
841 }
842
843 pub fn checked_sub_span(&self, s: &Span) -> Result<Self> {
849 Window::new(self.lo.checked_sub(s.hi())?, self.hi.checked_sub(s.lo())?)
850 }
851
852 pub fn since_window(&self, earlier: &Self) -> Result<(Span, bool)> {
860 let hi = self.hi.since(&earlier.lo)?;
861 match self.lo.since(&earlier.hi) {
862 Ok(lo) => Ok((Span::new(lo, hi)?, false)),
863 Err(_) => Ok((Span::new(Delta::zero(), hi)?, true)),
864 }
865 }
866
867 pub fn hull(&self, other: &Self) -> Self {
869 Window {
870 lo: if self.lo <= other.lo {
871 self.lo.clone()
872 } else {
873 other.lo.clone()
874 },
875 hi: if self.hi >= other.hi {
876 self.hi.clone()
877 } else {
878 other.hi.clone()
879 },
880 }
881 }
882
883 pub fn intersect(&self, other: &Self) -> Option<Self> {
885 if !self.overlaps(other) {
886 return None;
887 }
888 Some(Window {
889 lo: if self.lo >= other.lo {
890 self.lo.clone()
891 } else {
892 other.lo.clone()
893 },
894 hi: if self.hi <= other.hi {
895 self.hi.clone()
896 } else {
897 other.hi.clone()
898 },
899 })
900 }
901
902 pub fn midpoint(&self, mode: Rounding) -> Result<Instant<P>> {
907 let width = self.width();
908 let (half, rem) = width.divmod(&Delta::from_u64(2))?;
909 let mut ticks = self
910 .lo
911 .ticks
912 .try_add(half.ticks())
913 .ok_or(TimeError::new(Code::E0021))?;
914 if !rem.is_zero() {
915 let bump = match mode {
916 Rounding::Trunc => false,
917 Rounding::Ceil | Rounding::HalfUp => true,
918 Rounding::HalfEven => ticks.is_odd(),
919 };
920 if bump {
921 ticks = ticks
922 .try_add(&<Ticks as TickInt>::one())
923 .ok_or(TimeError::new(Code::E0021))?;
924 }
925 }
926 Instant::from_ticks(ticks)
927 }
928}
929
930#[cfg_attr(feature = "u512", derive(Copy))]
944#[derive(Clone, PartialEq, Eq, Debug)]
945pub struct Span {
946 lo: Delta,
947 hi: Delta,
948}
949
950impl Span {
951 pub fn new(lo: Delta, hi: Delta) -> Result<Span> {
953 if lo > hi {
954 return Err(TimeError::new(Code::E0022));
955 }
956 Ok(Span { lo, hi })
957 }
958
959 pub fn exact(d: Delta) -> Span {
961 Span {
962 lo: d.clone(),
963 hi: d,
964 }
965 }
966
967 pub fn zero() -> Span {
969 Span::exact(Delta::zero())
970 }
971
972 pub fn lo(&self) -> &Delta {
974 &self.lo
975 }
976
977 pub fn hi(&self) -> &Delta {
979 &self.hi
980 }
981
982 pub fn uncertainty(&self) -> Delta {
984 self.hi
985 .checked_sub(&self.lo)
986 .expect("Span maintains lo <= hi")
987 }
988
989 pub fn is_exact(&self) -> bool {
991 self.lo == self.hi
992 }
993
994 pub fn checked_add(&self, other: &Span) -> Result<Span> {
996 Span::new(
997 self.lo.checked_add(&other.lo)?,
998 self.hi.checked_add(&other.hi)?,
999 )
1000 }
1001
1002 pub fn checked_sub(&self, other: &Span) -> Result<(Span, bool)> {
1008 let hi = self.hi.checked_sub(&other.lo)?;
1009 match self.lo.checked_sub(&other.hi) {
1010 Ok(lo) => Ok((Span::new(lo, hi)?, false)),
1011 Err(_) => Ok((Span::new(Delta::zero(), hi)?, true)),
1012 }
1013 }
1014
1015 pub fn midpoint(&self, mode: Rounding) -> Result<Delta> {
1017 let (half, rem) = self.uncertainty().divmod(&Delta::from_u64(2))?;
1018 let mut d = self.lo.checked_add(&half)?;
1019 if !rem.is_zero() {
1020 let bump = match mode {
1021 Rounding::Trunc => false,
1022 Rounding::Ceil | Rounding::HalfUp => true,
1023 Rounding::HalfEven => d.ticks().is_odd(),
1024 };
1025 if bump {
1026 d = d.checked_add(&Delta::one_tick())?;
1027 }
1028 }
1029 Ok(d)
1030 }
1031}
1032
1033pub struct Stated<P: Profile> {
1048 value: Instant<P>,
1049 precision: Precision,
1050}
1051
1052impl<P: Profile> Clone for Stated<P> {
1053 fn clone(&self) -> Self {
1054 Stated {
1055 value: self.value.clone(),
1056 precision: self.precision,
1057 }
1058 }
1059}
1060
1061#[cfg(feature = "u512")]
1062impl<P: Profile> Copy for Stated<P> {}
1063
1064impl<P: Profile> PartialEq for Stated<P> {
1065 fn eq(&self, other: &Self) -> bool {
1069 self.value == other.value && self.precision == other.precision
1070 }
1071}
1072
1073impl<P: Profile> Eq for Stated<P> {}
1074
1075impl<P: Profile> core::fmt::Debug for Stated<P> {
1076 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1077 write!(
1078 f,
1079 "Stated<{}>({:?} @ {:?})",
1080 P::TAG,
1081 self.value.ticks(),
1082 self.precision
1083 )
1084 }
1085}
1086
1087impl<P: Profile> Stated<P> {
1088 pub fn new(value: Instant<P>, precision: Precision) -> Self {
1090 Stated { value, precision }
1091 }
1092
1093 pub fn exact(value: Instant<P>) -> Self {
1095 Stated {
1096 value,
1097 precision: Precision::Tick,
1098 }
1099 }
1100
1101 pub fn value(&self) -> &Instant<P> {
1104 &self.value
1105 }
1106
1107 pub fn precision(&self) -> Precision {
1109 self.precision
1110 }
1111
1112 pub fn is_exact(&self) -> bool {
1114 self.precision.is_exact()
1115 }
1116
1117 pub fn window(&self) -> Result<Window<P>> {
1119 self.value.window_at(self.precision)
1120 }
1121
1122 pub fn compare(&self, other: &Self) -> Result<IntervalOrdering> {
1124 Ok(self.window()?.compare(&other.window()?))
1125 }
1126
1127 pub fn try_compare(&self, other: &Self) -> Result<Ordering> {
1130 self.window()?.try_compare(&other.window()?)
1131 }
1132
1133 pub fn coarsen(&self, to: Tier) -> Result<Stated<P>> {
1136 if to < self.precision.tier() {
1137 return Err(TimeError::with_context(
1138 Code::E0023,
1139 "cannot restate at a finer precision than the value was given at",
1140 ));
1141 }
1142 Ok(Stated {
1143 value: self.value.floor_to(to),
1144 precision: if to.is_tick() {
1145 Precision::Tick
1146 } else {
1147 Precision::Tier(to)
1148 },
1149 })
1150 }
1151}
1152
1153#[cfg(test)]
1154mod tests {
1155 use super::*;
1156 use crate::profile::UC1;
1157
1158 type I = Instant<UC1>;
1159
1160 fn at(n: u64) -> I {
1161 I::from_u64(n).unwrap()
1162 }
1163 fn d(n: u64) -> Delta {
1164 Delta::from_u64(n)
1165 }
1166
1167 #[test]
1170 fn nothing_precedes_the_datum() {
1171 assert_eq!(I::zero().ticks(), &<Ticks as TickInt>::zero());
1172 let err = I::zero().checked_sub(&Delta::one_tick()).unwrap_err();
1173 assert_eq!(err.code, Code::E0020);
1174 assert_eq!(at(5).checked_sub(&d(6)).unwrap_err().code, Code::E0020);
1176 assert_eq!(at(5).checked_sub(&d(5)).unwrap(), I::zero());
1177 }
1178
1179 #[test]
1180 fn since_fails_backwards_but_between_does_not() {
1181 let early = at(10);
1182 let late = at(30);
1183 assert_eq!(late.since(&early).unwrap(), d(20));
1184 assert_eq!(early.since(&late).unwrap_err().code, Code::E0020);
1185
1186 let b = early.between(&late);
1188 assert_eq!(b.sign(), Sign::Negative);
1189 assert_eq!(b.magnitude(), &d(20));
1190 let f = late.between(&early);
1191 assert_eq!(f.sign(), Sign::Positive);
1192 assert_eq!(f.magnitude(), &d(20));
1193 assert_eq!(early.between(&early).sign(), Sign::Positive);
1195 assert!(early.between(&early).is_zero());
1196 }
1197
1198 #[test]
1201 fn domain_ceiling_is_enforced() {
1202 let max = I::from_ticks(<Ticks as TickInt>::domain_max()).unwrap();
1203 assert_eq!(
1204 max.checked_add(&Delta::one_tick()).unwrap_err().code,
1205 Code::E0021
1206 );
1207 assert_eq!(max.checked_add(&Delta::zero()).unwrap(), max.clone());
1208 assert_eq!(<Ticks as TickInt>::domain_max().bit_len(), 512);
1210 }
1211
1212 #[test]
1215 fn order_is_chronological_and_total() {
1216 let a = at(1);
1217 let b = at(2);
1218 assert!(a < b && b > a && a == a.clone());
1219 assert_eq!(a.cmp(&b), Ordering::Less);
1220 for (x, y) in [(1u64, 2u64), (2, 1), (7, 7)] {
1222 let (x, y) = (at(x), at(y));
1223 let n = [x < y, x == y, x > y].iter().filter(|b| **b).count();
1224 assert_eq!(n, 1);
1225 }
1226 }
1227
1228 #[test]
1231 fn floor_ceil_bracket_the_value() {
1232 let t = Tier::BEAT;
1233 let beat = t.ticks();
1234 let v = I::from_ticks(beat.try_mul(&<Ticks as TickInt>::from_u64(2)).unwrap())
1236 .unwrap()
1237 .checked_add(&Delta::one_tick())
1238 .unwrap();
1239 let lo = v.floor_to(t);
1240 let hi = v.ceil_to(t).unwrap();
1241 assert!(lo <= v && v <= hi);
1242 assert_eq!(lo.ticks(), &beat.try_mul(&<Ticks as TickInt>::from_u64(2)).unwrap());
1243 assert_eq!(hi.ticks(), &beat.try_mul(&<Ticks as TickInt>::from_u64(3)).unwrap());
1244 assert_eq!(lo.floor_to(t), lo);
1246 assert_eq!(lo.ceil_to(t).unwrap(), lo);
1247 }
1248
1249 #[test]
1250 fn truncation_is_monotone() {
1251 let t = Tier::ARC;
1254 let step = Tier::BEAT.ticks();
1255 let mut prev: Option<I> = None;
1256 for n in 0..40u64 {
1257 let v = I::from_ticks(step.try_mul(&<Ticks as TickInt>::from_u64(n * 7)).unwrap())
1258 .unwrap();
1259 let f = v.floor_to(t);
1260 if let Some(p) = prev {
1261 assert!(p <= f);
1262 }
1263 prev = Some(f);
1264 }
1265 }
1266
1267 #[test]
1270 fn rounding_modes_behave() {
1271 let t = Tier::new(-11).unwrap(); let unit = t.ticks();
1273 let half = unit.quot_rem(&<Ticks as TickInt>::from_u64(2)).0; let mk = |mult: u64, extra: &Ticks| {
1276 I::from_ticks(
1277 unit.try_mul(&<Ticks as TickInt>::from_u64(mult))
1278 .unwrap()
1279 .try_add(extra)
1280 .unwrap(),
1281 )
1282 .unwrap()
1283 };
1284
1285 let below = mk(2, &half);
1287 assert_eq!(below.round_to(t, Rounding::Trunc).unwrap(), mk(2, &<Ticks as TickInt>::zero()));
1288 assert_eq!(below.round_to(t, Rounding::Ceil).unwrap(), mk(3, &<Ticks as TickInt>::zero()));
1289 assert_eq!(
1290 below.round_to(t, Rounding::HalfEven).unwrap(),
1291 mk(2, &<Ticks as TickInt>::zero())
1292 );
1293
1294 let above = mk(2, &half.try_add(&<Ticks as TickInt>::one()).unwrap());
1296 assert_eq!(
1297 above.round_to(t, Rounding::HalfEven).unwrap(),
1298 mk(3, &<Ticks as TickInt>::zero())
1299 );
1300
1301 let w = Window::new(at(0), at(3)).unwrap();
1305 assert_eq!(w.midpoint(Rounding::Trunc).unwrap(), at(1));
1306 assert_eq!(w.midpoint(Rounding::Ceil).unwrap(), at(2));
1307 assert_eq!(w.midpoint(Rounding::HalfUp).unwrap(), at(2));
1308 assert_eq!(w.midpoint(Rounding::HalfEven).unwrap(), at(2));
1310 let w2 = Window::new(at(0), at(4)).unwrap();
1312 for m in [Rounding::Trunc, Rounding::Ceil, Rounding::HalfEven, Rounding::HalfUp] {
1313 assert_eq!(w2.midpoint(m).unwrap(), at(2));
1314 }
1315 }
1316
1317 #[test]
1320 fn tier_precision_denotes_a_closed_interval() {
1321 let t = Tier::BEAT;
1322 let v = at(12_345);
1323 let w = v.window_at(Precision::Tier(t)).unwrap();
1324 assert_eq!(w.lo(), &v.floor_to(t));
1325 assert_eq!(
1327 w.width().ticks(),
1328 &t.ticks().try_sub(&<Ticks as TickInt>::one()).unwrap()
1329 );
1330 assert!(w.contains(&v));
1331 assert!(!w.is_exact());
1332
1333 let e = v.window_at(Precision::Tick).unwrap();
1335 assert!(e.is_exact());
1336 assert_eq!(e.width(), Delta::zero());
1337 assert_eq!(e.lo(), e.hi());
1338 assert!(v.window_at(Precision::Tier(Tier::TICK)).unwrap().is_exact());
1340 }
1341
1342 #[test]
1343 fn comparison_across_precision_can_be_indeterminate() {
1344 let t = Tier::BEAT;
1345 let a = at(10).window_at(Precision::Tier(t)).unwrap();
1346 let b = at(20).window_at(Precision::Tier(t)).unwrap();
1347 assert_eq!(a.compare(&b), IntervalOrdering::Indeterminate);
1349 assert_eq!(a.try_compare(&b).unwrap_err().code, Code::E0023);
1350
1351 let far = I::from_ticks(t.ticks().try_mul(&<Ticks as TickInt>::from_u64(5)).unwrap())
1353 .unwrap()
1354 .window_at(Precision::Tier(t))
1355 .unwrap();
1356 assert_eq!(a.compare(&far), IntervalOrdering::Before);
1357 assert_eq!(far.compare(&a), IntervalOrdering::After);
1358 assert_eq!(a.try_compare(&far).unwrap(), Ordering::Less);
1359
1360 let x = Window::exact(at(7));
1362 assert_eq!(x.compare(&Window::exact(at(7))), IntervalOrdering::EqualExact);
1363 }
1364
1365 #[test]
1368 fn window_arithmetic_is_interval_arithmetic() {
1369 let w = Window::new(at(10), at(20)).unwrap();
1370 let s = w.checked_add(&d(5)).unwrap();
1371 assert_eq!((s.lo(), s.hi()), (&at(15), &at(25)));
1372 assert_eq!(s.width(), w.width());
1374
1375 assert_eq!(Window::new(at(20), at(10)).unwrap_err().code, Code::E0022);
1376
1377 let (wide, clipped) = w.widen(&d(5)).unwrap();
1378 assert_eq!((wide.lo(), wide.hi()), (&at(5), &at(25)));
1379 assert!(!clipped);
1380 let (wide2, clipped2) = w.widen(&d(50)).unwrap();
1382 assert_eq!(wide2.lo(), &I::zero());
1383 assert!(clipped2);
1384
1385 let other = Window::new(at(15), at(30)).unwrap();
1386 assert!(w.overlaps(&other));
1387 assert_eq!(w.hull(&other), Window::new(at(10), at(30)).unwrap());
1388 assert_eq!(w.intersect(&other), Some(Window::new(at(15), at(20)).unwrap()));
1389 assert_eq!(w.intersect(&Window::new(at(40), at(50)).unwrap()), None);
1390 }
1391
1392 #[test]
1393 fn signed_window_is_inert() {
1394 let claim = UC1::big_bang_claim();
1397 assert_eq!(claim.lo().sign(), Sign::Negative);
1398 assert_eq!(claim.hi().sign(), Sign::Positive);
1399 #[cfg(feature = "alloc")]
1401 assert!(claim.describe().contains("ticks"));
1402 }
1403
1404 #[test]
1407 fn canonical_binary_is_64_bytes_and_round_trips() {
1408 for n in [0u64, 1, 255, 256, 65_535, u64::MAX] {
1409 let v = at(n);
1410 let b = v.to_bytes();
1411 assert_eq!(b.len(), 64);
1412 assert_eq!(I::from_bytes(&b).unwrap(), v);
1413 }
1414 assert_eq!(I::zero().to_bytes(), [0u8; 64]);
1415 let max = I::from_ticks(<Ticks as TickInt>::domain_max()).unwrap();
1416 assert_eq!(max.to_bytes(), [0xffu8; 64]);
1417 }
1418
1419 #[test]
1420 fn byte_order_is_chronological_order() {
1421 let mut vals: Vec<I> = (0..64u64).map(|i| at(i * 2_654_435_761)).collect();
1424 vals.push(at(0));
1425 vals.push(I::from_ticks(<Ticks as TickInt>::domain_max()).unwrap());
1426 vals.sort();
1427 let mut bytes: Vec<[u8; 64]> = vals.iter().map(|v| v.to_bytes()).collect();
1428 let numeric = bytes.clone();
1429 bytes.sort();
1430 assert_eq!(bytes, numeric, "byte order diverges from numeric order");
1431 }
1432
1433 #[test]
1436 fn rebase_is_identity_within_a_profile() {
1437 let v = at(1_000_000);
1438 let (out, shift) = v.rebase::<UC1>().unwrap();
1439 assert_eq!(out, v);
1440 assert!(shift.is_zero());
1441 }
1442
1443 #[test]
1446 fn delta_arithmetic() {
1447 assert_eq!(d(3).checked_add(&d(4)).unwrap(), d(7));
1448 assert_eq!(d(3).checked_sub(&d(4)).unwrap_err().code, Code::E0020);
1449 assert_eq!(d(12).mul_u64(3).unwrap(), d(36));
1450 assert_eq!(d(12).div_u64(5).unwrap(), d(2));
1451 assert_eq!(d(12).divmod(&d(5)).unwrap(), (d(2), d(2)));
1452 assert_eq!(d(1).div_u64(0).unwrap_err().code, Code::E0021);
1453 assert!(Delta::zero().is_zero());
1454 assert_eq!(Delta::one_tick(), d(1));
1455 }
1456
1457 #[test]
1458 fn delta_tier_of_finds_the_largest_fitting_tier() {
1459 let beat = Delta::from_ticks(Tier::BEAT.ticks());
1461 assert_eq!(beat.tier_of(), Some(Tier::BEAT));
1462 let just_under = beat.checked_sub(&Delta::one_tick()).unwrap();
1464 assert_eq!(just_under.tier_of(), Some(Tier::new(-1).unwrap()));
1465 assert_eq!(Delta::one_tick().tier_of(), Some(Tier::TICK));
1467 assert_eq!(Delta::zero().tier_of(), None);
1468 let three_beats = Delta::from_tier(Tier::BEAT, 3).unwrap();
1470 assert_eq!(three_beats.in_tier(Tier::BEAT).0, <Ticks as TickInt>::from_u64(3));
1471 assert!(three_beats.in_tier(Tier::BEAT).1.is_zero_ticks());
1472 }
1473
1474 #[test]
1477 fn tier_values_are_base_5_groups() {
1478 let v = I::from_ticks(
1480 Tier::BEAT
1481 .ticks()
1482 .try_mul(&<Ticks as TickInt>::from_u64(2))
1483 .unwrap()
1484 .try_add(
1485 &Tier::ARC
1486 .ticks()
1487 .try_mul(&<Ticks as TickInt>::from_u64(3))
1488 .unwrap(),
1489 )
1490 .unwrap(),
1491 )
1492 .unwrap();
1493 assert_eq!(v.tier_value(Tier::BEAT), 2);
1494 assert_eq!(v.tier_value(Tier::ARC), 3);
1495 assert_eq!(v.tier_value(Tier::SWEEP), 0);
1496 for t in Tier::all_ascending() {
1498 assert!(v.tier_value(t) < GROUP_BASE);
1499 }
1500 }
1501
1502 #[cfg(feature = "alloc")]
1503 #[test]
1504 fn groups_descend_and_reassemble() {
1505 let v = at(987_654_321);
1506 let gs = v.groups(Tier::BEAT, Tier::TICK).unwrap();
1507 assert_eq!(gs.len(), 13); let mut acc = <Ticks as TickInt>::zero();
1510 let base = <Ticks as TickInt>::from_u64(GROUP_BASE as u64);
1511 for g in &gs {
1512 acc = acc
1513 .try_mul(&base)
1514 .unwrap()
1515 .try_add(&<Ticks as TickInt>::from_u64(*g as u64))
1516 .unwrap();
1517 }
1518 assert_eq!(&acc, v.ticks());
1519 assert_eq!(
1521 v.groups(Tier::TICK, Tier::BEAT).unwrap_err().code,
1522 Code::E0006
1523 );
1524 }
1525}
1526
1527#[cfg(test)]
1528mod rule_u_tests {
1529 use super::*;
1530 use crate::profile::UC1;
1531
1532 type I = Instant<UC1>;
1533
1534 fn at(n: u64) -> I {
1535 I::from_u64(n).unwrap()
1536 }
1537 fn d(n: u64) -> Delta {
1538 Delta::from_u64(n)
1539 }
1540 fn w(lo: u64, hi: u64) -> Window<UC1> {
1541 Window::new(at(lo), at(hi)).unwrap()
1542 }
1543 fn sp(lo: u64, hi: u64) -> Span {
1544 Span::new(d(lo), d(hi)).unwrap()
1545 }
1546
1547 #[test]
1550 fn span_is_an_interval_of_magnitudes() {
1551 let s = sp(10, 20);
1552 assert_eq!(s.uncertainty(), d(10));
1553 assert!(!s.is_exact());
1554 assert!(Span::exact(d(7)).is_exact());
1555 assert_eq!(Span::exact(d(7)).uncertainty(), Delta::zero());
1556 assert!(Span::zero().is_exact());
1557 assert_eq!(Span::new(d(20), d(10)).unwrap_err().code, Code::E0022);
1558 }
1559
1560 #[test]
1561 fn span_addition_is_interval_addition() {
1562 let a = sp(10, 20);
1564 let b = sp(1, 5);
1565 let s = a.checked_add(&b).unwrap();
1566 assert_eq!((s.lo(), s.hi()), (&d(11), &d(25)));
1567 assert_eq!(s.uncertainty(), d(14));
1569 assert!(s.uncertainty() >= a.uncertainty());
1570 assert!(s.uncertainty() >= b.uncertainty());
1571 }
1572
1573 #[test]
1574 fn span_subtraction_clamps_at_zero_and_says_so() {
1575 let (s, clamped) = sp(10, 20).checked_sub(&sp(1, 5)).unwrap();
1577 assert_eq!((s.lo(), s.hi()), (&d(5), &d(19)));
1578 assert!(!clamped);
1579 let (s, clamped) = sp(10, 20).checked_sub(&sp(15, 25)).unwrap();
1581 assert_eq!(s.lo(), &Delta::zero());
1582 assert_eq!(s.hi(), &d(5));
1583 assert!(clamped, "the clamp must be reported, not hidden");
1584 assert_eq!(
1586 sp(1, 2).checked_sub(&sp(10, 20)).unwrap_err().code,
1587 Code::E0020
1588 );
1589 }
1590
1591 #[test]
1592 fn span_midpoint_must_be_asked_for() {
1593 let s = sp(0, 3);
1594 assert_eq!(s.midpoint(Rounding::Trunc).unwrap(), d(1));
1595 assert_eq!(s.midpoint(Rounding::Ceil).unwrap(), d(2));
1596 assert_eq!(s.midpoint(Rounding::HalfUp).unwrap(), d(2));
1597 assert_eq!(sp(0, 4).midpoint(Rounding::Trunc).unwrap(), d(2));
1598 }
1599
1600 #[test]
1603 fn uncertain_instant_plus_uncertain_duration_widens() {
1604 let anchor = w(100, 110); let elapsed = sp(1000, 1005); let out = anchor.checked_add_span(&elapsed).unwrap();
1609 assert_eq!((out.lo(), out.hi()), (&at(1100), &at(1115)));
1610 assert_eq!(out.width(), d(15));
1612 assert!(out.width() >= anchor.width());
1613 assert!(out.width() >= elapsed.uncertainty());
1614 }
1615
1616 #[test]
1617 fn subtracting_a_span_is_outward_and_strict_at_the_datum() {
1618 let out = w(1000, 1010).checked_sub_span(&sp(100, 200)).unwrap();
1619 assert_eq!((out.lo(), out.hi()), (&at(800), &at(910)));
1621 assert!(out.width() > w(1000, 1010).width());
1622 assert_eq!(
1625 w(10, 20).checked_sub_span(&sp(0, 100)).unwrap_err().code,
1626 Code::E0020
1627 );
1628 }
1629
1630 #[test]
1631 fn elapsed_between_windows_is_a_span() {
1632 let earlier = w(100, 110);
1633 let later = w(1000, 1010);
1634 let (s, clamped) = later.since_window(&earlier).unwrap();
1635 assert_eq!((s.lo(), s.hi()), (&d(890), &d(910)));
1637 assert!(!clamped);
1638 let (s, clamped) = w(100, 200).since_window(&w(150, 250)).unwrap();
1640 assert_eq!(s.lo(), &Delta::zero());
1641 assert_eq!(s.hi(), &d(50));
1642 assert!(clamped);
1643 assert_eq!(
1645 w(10, 20).since_window(&w(100, 200)).unwrap_err().code,
1646 Code::E0020
1647 );
1648 }
1649
1650 #[test]
1651 fn round_trip_through_span_preserves_containment() {
1652 let start = w(10_000, 10_050);
1655 let s = sp(300, 320);
1656 let moved = start.checked_add_span(&s).unwrap();
1657 let back = moved.checked_sub_span(&s).unwrap();
1658 assert!(back.contains(start.lo()));
1659 assert!(back.contains(start.hi()));
1660 assert!(back.width() >= start.width());
1662 }
1663
1664 #[test]
1667 fn stated_comparison_uses_interval_semantics() {
1668 let a = Stated::new(at(10).floor_to(Tier::BEAT), Precision::Tier(Tier::BEAT));
1669 let b = Stated::new(at(20).floor_to(Tier::BEAT), Precision::Tier(Tier::BEAT));
1670 assert_eq!(a.compare(&b).unwrap(), IntervalOrdering::Indeterminate);
1672 assert_eq!(a.try_compare(&b).unwrap_err().code, Code::E0023);
1673
1674 let x = Stated::exact(at(7));
1676 assert_eq!(
1677 x.compare(&Stated::exact(at(7))).unwrap(),
1678 IntervalOrdering::EqualExact
1679 );
1680 assert_eq!(x.try_compare(&Stated::exact(at(8))).unwrap(), Ordering::Less);
1681 assert!(x.is_exact());
1682 }
1683
1684 #[test]
1685 fn stated_comparison_across_unequal_precision() {
1686 let coarse = Stated::new(at(0).floor_to(Tier::BEAT), Precision::Tier(Tier::BEAT));
1689 let fine = Stated::exact(at(5));
1690 assert_eq!(coarse.compare(&fine).unwrap(), IntervalOrdering::Indeterminate);
1691 assert_eq!(coarse.try_compare(&fine).unwrap_err().code, Code::E0023);
1692
1693 let far = Stated::exact(
1695 I::from_ticks(
1696 Tier::BEAT
1697 .ticks()
1698 .try_mul(&<Ticks as TickInt>::from_u64(3))
1699 .unwrap(),
1700 )
1701 .unwrap(),
1702 );
1703 assert_eq!(coarse.try_compare(&far).unwrap(), Ordering::Less);
1704 assert_eq!(far.try_compare(&coarse).unwrap(), Ordering::Greater);
1705 }
1706
1707 #[test]
1708 fn stated_cannot_be_refined_only_coarsened() {
1709 let s = Stated::new(at(1_000_000).floor_to(Tier::BEAT), Precision::Tier(Tier::BEAT));
1711 assert!(s.coarsen(Tier::ARC).is_ok());
1712 assert_eq!(s.coarsen(Tier::ARC).unwrap().precision(), Precision::Tier(Tier::ARC));
1713 assert_eq!(
1715 s.coarsen(Tier::new(-3).unwrap()).unwrap_err().code,
1716 Code::E0023
1717 );
1718 assert_eq!(s.coarsen(Tier::TICK).unwrap_err().code, Code::E0023);
1719 assert!(s.coarsen(Tier::ARC).unwrap().window().unwrap().width() > s.window().unwrap().width());
1721 }
1722
1723 #[test]
1724 fn stated_equality_is_about_the_statement() {
1725 let a = Stated::new(at(0), Precision::Tier(Tier::BEAT));
1728 let b = Stated::new(at(0), Precision::Tier(Tier::ARC));
1729 assert_ne!(a, b);
1730 assert_eq!(a, Stated::new(at(0), Precision::Tier(Tier::BEAT)));
1731 }
1732
1733 #[test]
1736 fn coarse_window_at_the_ceiling_is_clipped_to_the_domain() {
1737 let top = I::from_ticks(<Ticks as TickInt>::domain_max()).unwrap();
1741 let t32 = Tier::new(crate::tier::K_MAX).unwrap();
1742 let win = top.window_at(Precision::Tier(t32)).unwrap();
1743 assert!(win.contains(&top));
1744 assert_eq!(win.hi(), &top);
1745 assert_eq!(win.lo(), &top.floor_to(t32));
1746 assert!(!win.is_exact());
1747 }
1748}