1pub const BLOCK: i64 = 1 << 12;
5
6pub fn block_end(n: i64) -> i64 {
7 (n.div_euclid(BLOCK) + 1).saturating_mul(BLOCK)
8}
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
12pub struct Extent {
13 pub start: i64,
14 pub end: i64,
15}
16
17impl Extent {
18 pub const EVERYWHERE: Extent = Extent {
20 start: i64::MIN,
21 end: i64::MAX,
22 };
23
24 pub const NOWHERE: Extent = Extent { start: 0, end: 0 };
25
26 pub fn new(start: i64, end: i64) -> Extent {
27 assert!(start <= end, "an extent [{start}, {end}) runs backwards");
28 Extent { start, end }
29 }
30
31 pub fn from(start: i64) -> Extent {
32 Extent::new(start, i64::MAX)
33 }
34
35 pub fn is_bounded(&self) -> bool {
36 self.start != i64::MIN && self.end != i64::MAX
37 }
38
39 pub fn contains(&self, n: i64) -> bool {
40 self.start <= n && n < self.end
41 }
42
43 pub fn intersect(self, other: Extent) -> Extent {
44 let start = self.start.max(other.start);
45 let end = self.end.min(other.end);
46 match start < end {
47 true => Extent { start, end },
48 false => Extent::NOWHERE,
49 }
50 }
51
52 pub fn hull(self, other: Extent) -> Extent {
53 match (self.is_empty(), other.is_empty()) {
54 (true, _) => other,
55 (_, true) => self,
56 _ => Extent {
57 start: self.start.min(other.start),
58 end: self.end.max(other.end),
59 },
60 }
61 }
62
63 pub fn shifted(self, by: i64) -> Extent {
65 if self.is_empty() {
66 return self;
67 }
68 let edge = |n: i64| match n {
69 i64::MIN | i64::MAX => n,
70 n => n.saturating_add(by).clamp(i64::MIN + 1, i64::MAX - 1),
71 };
72 Extent {
73 start: edge(self.start),
74 end: edge(self.end),
75 }
76 }
77
78 pub fn secs(rate: u32, start_secs: f64, end_secs: f64) -> Extent {
79 let at = |secs: f64| (secs * f64::from(rate)).round() as i64;
80 Extent::new(at(start_secs), at(end_secs))
81 }
82
83 pub fn len(&self) -> usize {
84 debug_assert!(self.is_bounded(), "an unbounded extent has no length");
85 (self.end - self.start) as usize
86 }
87
88 pub fn is_empty(&self) -> bool {
89 self.end == self.start
90 }
91
92 pub fn start_secs(&self, rate: u32) -> f64 {
93 self.start as f64 / f64::from(rate)
94 }
95
96 pub fn span_secs(&self, rate: u32) -> f64 {
97 self.len() as f64 / f64::from(rate)
98 }
99}
100
101#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
104pub struct Grid {
105 pub rate: u32,
106 pub a: i128,
107 pub d: i128,
108}
109
110impl Grid {
111 pub const fn of(rate: u32) -> Grid {
112 Grid { rate, a: 1, d: 1 }
113 }
114
115 pub fn finer(rate: u32, scale: usize) -> Grid {
116 Grid {
117 rate,
118 a: 1,
119 d: scale as i128,
120 }
121 }
122
123 pub fn is_rate(&self) -> bool {
124 (self.a, self.d) == (1, 1)
125 }
126
127 pub(crate) fn exact(&self, n: i64) -> Option<(i128, i128)> {
128 let num = self.a.checked_mul(i128::from(n))?;
129 Some((num, self.d.checked_mul(i128::from(self.rate))?))
130 }
131
132 pub fn instant(&self, n: i64) -> f64 {
135 if self.is_rate() {
136 return n as f64 / f64::from(self.rate);
137 }
138 let num = self.a.saturating_mul(i128::from(n));
139 num as f64 / self.d.saturating_mul(i128::from(self.rate)) as f64
140 }
141
142 pub fn first_at(&self, edge: f64) -> i64 {
145 let beyond = if edge < 0.0 { i64::MIN } else { i64::MAX };
146 if !edge.is_finite() {
147 return beyond;
148 }
149 match self.decimal_steps(edge) {
150 Some(n) => i64::try_from(n).unwrap_or(beyond),
151 None => self.step_at(edge, Round::Ceil).unwrap_or(beyond),
152 }
153 }
154
155 pub fn edge_at(&self, n: i64) -> Option<f64> {
157 let t = self.instant(n);
158 [t, t.next_down(), t.next_up()]
159 .into_iter()
160 .find(|edge| self.first_at(*edge) == n)
161 }
162
163 fn decimal_steps(&self, edge: f64) -> Option<i128> {
165 const LIMIT: i128 = 1 << 120;
166 let text = format!("{edge:e}");
167 let (mantissa, exp) = text.split_once('e')?;
168 let (whole, frac) = mantissa.split_once('.').unwrap_or((mantissa, ""));
169 let digits: i128 = format!("{whole}{frac}").parse().ok()?;
170 let shift = exp.parse::<i32>().ok()? - frac.len() as i32;
171 let ten = 10i128
172 .checked_pow(shift.unsigned_abs())
173 .filter(|p| *p < LIMIT)?;
174 let (num, den) = match shift >= 0 {
175 true => (digits.checked_mul(ten)?, 1),
176 false => (digits, ten),
177 };
178 let top = num.checked_mul(self.d.checked_mul(i128::from(self.rate))?)?;
179 let bottom = den.checked_mul(self.a)?;
180 Some(top.div_euclid(bottom) + i128::from(top.rem_euclid(bottom) != 0))
181 }
182
183 pub(crate) fn inside(&self, n: i64, l: f64, r: f64) -> bool {
185 let t = self.instant(n);
186 let reached = |edge: f64| match t == edge {
187 true => n >= self.first_at(edge),
188 false => t > edge,
189 };
190 reached(l) && !reached(r)
191 }
192
193 pub fn position(&self, n: i64) -> f64 {
194 self.a.saturating_mul(i128::from(n)) as f64 / self.d as f64
195 }
196
197 pub fn sr(&self) -> f64 {
198 f64::from(self.rate) * self.d as f64 / self.a as f64
199 }
200
201 pub fn count(&self, t: f64) -> f64 {
202 t * f64::from(self.rate) * self.d as f64 / self.a as f64
203 }
204
205 pub fn step_at(&self, t: f64, round: Round) -> Option<i64> {
208 match self.is_rate() {
209 true => rated(t, self.rate, round).or_else(|| self.wide(t, round)),
210 false => self.wide(t, round),
211 }
212 }
213
214 fn wide(&self, t: f64, round: Round) -> Option<i64> {
215 #[cfg(test)]
216 WIDE.with(|w| w.set(w.get() + 1));
217 if !t.is_finite() {
218 return None;
219 }
220 let bits = t.abs().to_bits();
221 let (exp, frac) = ((bits >> 52) as i32, (bits & ((1 << 52) - 1)) as i128);
222 let (mantissa, shift) = match exp {
223 0 => (frac, 1074),
224 e => (frac | (1 << 52), 1075 - e),
225 };
226 let zeros = mantissa.trailing_zeros().min(127) as i32;
227 let (mantissa, shift) = match mantissa {
228 0 => (0, 0),
229 m => (m >> zeros, shift - zeros),
230 };
231 let mantissa = if t < 0.0 { -mantissa } else { mantissa };
232 let (num, den) = match shift {
233 s if s <= 0 => (mantissa.checked_mul(1i128.checked_shl((-s) as u32)?)?, 1),
234 s if s < 127 => (mantissa, 1i128 << s),
235 _ => return None,
236 };
237 let top = num.checked_mul(self.d.checked_mul(i128::from(self.rate))?)?;
238 let bottom = self.a.checked_mul(den)?;
239 let (floor, rem) = (top.div_euclid(bottom), top.rem_euclid(bottom));
240 let k = match round {
241 Round::Floor => floor,
242 Round::Ceil => floor + i128::from(rem != 0),
243 Round::Even => match (2 * rem).cmp(&bottom) {
244 std::cmp::Ordering::Less => floor,
245 std::cmp::Ordering::Greater => floor + 1,
246 std::cmp::Ordering::Equal => floor + floor.rem_euclid(2),
247 },
248 };
249 i64::try_from(k).ok()
250 }
251}
252
253fn rated(t: f64, rate: u32, round: Round) -> Option<i64> {
257 let held = t == 0.0 || (2f64.powi(-74)..2f64.powi(51)).contains(&t.abs());
258 if !held || rate >= 1 << 26 {
259 return None;
260 }
261 let r = f64::from(rate);
262 let split = 134_217_729.0 * t;
263 let hi = split - (split - t);
264 let (x, y) = (hi * r, (t - hi) * r);
265 let p = x + y;
266 let back = p - x;
267 let e = (x - (p - back)) + (y - back);
268 if p.abs() >= 2f64.powi(51) {
269 return None;
270 }
271 let f = p.floor();
272 let k = match round {
273 Round::Floor if p == f && e < 0.0 => f - 1.0,
274 Round::Floor => f,
275 Round::Ceil if p == p.ceil() && e > 0.0 => p + 1.0,
276 Round::Ceil => p.ceil(),
277 Round::Even => match (p - f).total_cmp(&0.5) {
278 std::cmp::Ordering::Less => f,
279 std::cmp::Ordering::Greater => f + 1.0,
280 std::cmp::Ordering::Equal if e > 0.0 => f + 1.0,
281 std::cmp::Ordering::Equal if e < 0.0 => f,
282 std::cmp::Ordering::Equal => f + f.rem_euclid(2.0),
283 },
284 };
285 Some(k as i64)
286}
287
288#[cfg(test)]
289thread_local! {
290 pub(crate) static WIDE: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
291}
292
293#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
294pub enum Round {
295 Even,
296 Floor,
297 Ceil,
298}
299
300#[cfg(test)]
301mod tests {
302 use super::{Grid, Round, rated};
303
304 #[test]
306 fn an_edge_in_seconds_tying_an_instant_is_decided_by_its_decimal() {
307 let cd = Grid::of(44_100);
308 assert_eq!(cd.first_at(0.1), 4410);
309 assert!(cd.inside(4410, 0.1, 1.0));
310 let grid = Grid::of(48_000);
311 let edge = 60.0 / 72.0;
312 assert_eq!(grid.instant(40_000), edge);
313 assert_eq!(grid.first_at(edge), 40_001);
314 assert!(!grid.inside(40_000, edge, 2.0));
315 assert!(grid.inside(40_000, 0.0, edge));
316 }
317
318 fn instants(rate: u32) -> Vec<f64> {
319 let r = f64::from(rate);
320 let mut out = vec![0.0, -0.0, 1e-300, -1e-300, 1e300, f64::MIN_POSITIVE];
321 let mut seed = 0x9e37_79b9_7f4a_7c15u64;
322 for k in -2_000i64..2_000 {
323 for half in [0.0, 0.5] {
324 let at = (k as f64 + half) / r;
325 let mut near = at;
326 for _ in 0..3 {
327 near = near.next_up();
328 out.push(near);
329 }
330 near = at;
331 for _ in 0..3 {
332 near = near.next_down();
333 out.push(near);
334 }
335 out.push(at);
336 out.push(at * 1e9);
337 }
338 seed = seed.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1);
339 out.push(f64::from_bits(seed >> 2) * if seed & 1 == 0 { 1.0 } else { -1.0 });
340 }
341 out
342 }
343
344 #[test]
345 fn a_step_rounds_alike_in_doubles_and_in_wide_integers() {
346 for rate in [1, 8_000, 44_100, 48_000, 88_200, 96_000, 192_000] {
347 let grid = Grid::of(rate);
348 for t in instants(rate) {
349 for round in [Round::Floor, Round::Ceil, Round::Even] {
350 if let Some(k) = rated(t, rate, round) {
351 assert_eq!(Some(k), grid.wide(t, round), "{t:e} at {rate} {round:?}");
352 }
353 }
354 }
355 }
356 }
357}