int_interval/u8.rs
1use crate::res::{OneTwo, ZeroOneTwo};
2
3#[cfg(test)]
4mod basic_tests;
5#[cfg(test)]
6mod between_tests;
7#[cfg(test)]
8mod checked_minkowski_tests;
9#[cfg(test)]
10mod convex_hull_tests;
11#[cfg(test)]
12mod difference_tests;
13#[cfg(test)]
14mod intersection_tests;
15#[cfg(test)]
16mod saturating_minkowski_tests;
17#[cfg(test)]
18mod symmetric_difference_tests;
19#[cfg(test)]
20mod union_tests;
21
22#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
23pub struct U8CO {
24 start: u8,
25 end_excl: u8,
26}
27
28// ------------------------------------------------------------
29// low-level api: construction / accessors / predicates
30// ------------------------------------------------------------
31
32mod basic {
33
34 use super::*;
35
36 impl U8CO {
37 #[inline]
38 pub const fn try_new(start: u8, end_excl: u8) -> Option<Self> {
39 if start < end_excl {
40 Some(Self { start, end_excl })
41 } else {
42 None
43 }
44 }
45
46 #[inline]
47 pub const unsafe fn new_unchecked(start: u8, end_excl: u8) -> Self {
48 debug_assert!(start < end_excl);
49 Self { start, end_excl }
50 }
51
52 #[inline]
53 pub const fn start(self) -> u8 {
54 self.start
55 }
56
57 #[inline]
58 pub const fn end_excl(self) -> u8 {
59 self.end_excl
60 }
61
62 #[inline]
63 pub const fn end_incl(self) -> u8 {
64 // u8_low_bound =< start < end_excl
65 self.end_excl - 1
66 }
67
68 #[inline]
69 pub const fn len(self) -> u8 {
70 self.end_excl - self.start
71 }
72
73 /// Returns the midpoint of the interval (floor division).
74 #[inline]
75 pub const fn midpoint(self) -> u8 {
76 self.start + (self.len() / 2)
77 }
78
79 /// Construct a `U8CO` from a midpoint and a length.
80 ///
81 /// Returns `None` if the computed interval is invalid or overflows u8.
82 #[inline]
83 pub const fn checked_from_midpoint_len(mid: u8, len: u8) -> Option<Self> {
84 if len == 0 {
85 return None;
86 }
87 // Compute start = mid - floor(len/2)
88 let Some(start) = mid.checked_sub(len / 2) else {
89 return None;
90 };
91 // Compute end_excl = start + len, return None if overflow
92 let Some(end_excl) = start.checked_add(len) else {
93 return None;
94 };
95
96 // # Safety
97 // This function uses `unsafe { Self::new_unchecked(start, end_excl) }` internally.
98 // The safety is guaranteed by the following checks:
99 // 1. `mid.checked_sub(len / 2)` ensures `start` does not underflow `u8`.
100 // 2. `start.checked_add(len)` ensures `end_excl` does not overflow `u8`.
101 // 3. Because `len > 0`, we have `start < end_excl`.
102 // 4. Therefore, the half-open interval invariant `[start, end_excl)` is preserved.
103 Some(unsafe { Self::new_unchecked(start, end_excl) })
104 }
105
106 /// Saturating version: from midpoint + length, keeps start < end_excl
107 #[inline]
108 pub const fn saturating_from_midpoint_len(mid: u8, len: u8) -> Option<Self> {
109 if len == 0 {
110 return None;
111 }
112
113 let start = mid.saturating_sub(len / 2);
114 let end_excl = start.saturating_add(len);
115
116 // Use try_new to enforce start < end_excl invariant
117 Self::try_new(start, end_excl)
118 }
119
120 #[inline]
121 pub const fn contains(self, x: u8) -> bool {
122 self.start <= x && x < self.end_excl
123 }
124
125 #[inline]
126 pub const fn contains_interval(self, other: Self) -> bool {
127 self.start <= other.start && other.end_excl <= self.end_excl
128 }
129
130 #[inline]
131 pub const fn iter(self) -> core::ops::Range<u8> {
132 self.start..self.end_excl
133 }
134
135 #[inline]
136 pub const fn to_range(self) -> core::ops::Range<u8> {
137 self.start..self.end_excl
138 }
139
140 #[inline]
141 pub const fn intersects(self, other: Self) -> bool {
142 !(self.end_excl <= other.start || other.end_excl <= self.start)
143 }
144
145 #[inline]
146 pub const fn is_adjacent(self, other: Self) -> bool {
147 self.end_excl == other.start || other.end_excl == self.start
148 }
149
150 #[inline]
151 pub const fn is_contiguous_with(self, other: Self) -> bool {
152 self.intersects(other) || self.is_adjacent(other)
153 }
154 }
155}
156
157// ------------------------------------------------------------
158// interval algebra api: intersection / convex_hull / between / union / difference / symmetric_difference
159// ------------------------------------------------------------
160
161mod interval_algebra {
162
163 use super::*;
164
165 impl U8CO {
166 /// Returns the intersection of two intervals.
167 ///
168 /// If the intervals do not overlap, returns `None`.
169 #[inline]
170 pub const fn intersection(self, other: Self) -> Option<Self> {
171 let start = if self.start >= other.start {
172 self.start
173 } else {
174 other.start
175 };
176
177 let end_excl = if self.end_excl <= other.end_excl {
178 self.end_excl
179 } else {
180 other.end_excl
181 };
182
183 Self::try_new(start, end_excl)
184 }
185
186 /// Returns the convex hull (smallest interval containing both) of two intervals.
187 ///
188 /// Always returns a valid `U8CO`.
189 #[inline]
190 pub const fn convex_hull(self, other: Self) -> Self {
191 let start = if self.start <= other.start {
192 self.start
193 } else {
194 other.start
195 };
196
197 let end_excl = if self.end_excl >= other.end_excl {
198 self.end_excl
199 } else {
200 other.end_excl
201 };
202
203 Self { start, end_excl }
204 }
205
206 /// Returns the interval strictly between two intervals.
207 ///
208 /// If the intervals are contiguous or overlap, returns `None`.
209 #[inline]
210 pub const fn between(self, other: Self) -> Option<Self> {
211 let (left, right) = if self.start <= other.start {
212 (self, other)
213 } else {
214 (other, self)
215 };
216
217 Self::try_new(left.end_excl, right.start)
218 }
219
220 /// Returns the union of two intervals.
221 ///
222 /// - If intervals are contiguous or overlapping, returns `One` containing the merged interval.
223 /// - Otherwise, returns `Two` containing both intervals in ascending order.
224 #[inline]
225 pub const fn union(self, other: Self) -> OneTwo<Self> {
226 if self.is_contiguous_with(other) {
227 OneTwo::One(self.convex_hull(other))
228 } else if self.start <= other.start {
229 OneTwo::Two(self, other)
230 } else {
231 OneTwo::Two(other, self)
232 }
233 }
234
235 /// Returns the difference of two intervals (self - other).
236 ///
237 /// - If no overlap, returns `One(self)`.
238 /// - If partial overlap, returns `One` or `Two` depending on remaining segments.
239 /// - If fully contained, returns `Zero`.
240 #[inline]
241 pub const fn difference(self, other: Self) -> ZeroOneTwo<Self> {
242 match self.intersection(other) {
243 None => ZeroOneTwo::One(self),
244 Some(inter) => {
245 let left = Self::try_new(self.start, inter.start);
246 let right = Self::try_new(inter.end_excl, self.end_excl);
247
248 match (left, right) {
249 (None, None) => ZeroOneTwo::Zero,
250 (Some(x), None) | (None, Some(x)) => ZeroOneTwo::One(x),
251 (Some(x), Some(y)) => ZeroOneTwo::Two(x, y),
252 }
253 }
254 }
255 }
256
257 /// Returns the symmetric difference of two intervals.
258 ///
259 /// Equivalent to `(self - other) ∪ (other - self)`.
260 /// - If intervals do not overlap, returns `Two(self, other)` in order.
261 /// - If intervals partially overlap, returns remaining non-overlapping segments as `One` or `Two`.
262 #[inline]
263 pub const fn symmetric_difference(self, other: Self) -> ZeroOneTwo<Self> {
264 match self.intersection(other) {
265 None => {
266 if self.start <= other.start {
267 ZeroOneTwo::Two(self, other)
268 } else {
269 ZeroOneTwo::Two(other, self)
270 }
271 }
272 Some(inter) => {
273 let hull = self.convex_hull(other);
274 let left = Self::try_new(hull.start, inter.start);
275 let right = Self::try_new(inter.end_excl, hull.end_excl);
276
277 match (left, right) {
278 (None, None) => ZeroOneTwo::Zero,
279 (Some(x), None) | (None, Some(x)) => ZeroOneTwo::One(x),
280 (Some(x), Some(y)) => ZeroOneTwo::Two(x, y),
281 }
282 }
283 }
284 }
285 }
286}
287
288// ------------------------------------------------------------
289// Module: Minkowski arithmetic for U8CO
290// Provides checked and saturating Minkowski operations
291// ------------------------------------------------------------
292
293pub mod minkowski {
294 use super::*;
295
296 pub mod checked {
297 use super::*;
298
299 // --------------------------------------------------------
300 // Interval-to-interval Minkowski operations
301 // --------------------------------------------------------
302 impl U8CO {
303 /// Minkowski addition: [a_start, a_end) + [b_start, b_end)
304 #[inline]
305 pub const fn checked_minkowski_add(self, other: Self) -> Option<Self> {
306 let Some(start) = self.start.checked_add(other.start) else {
307 return None;
308 };
309 let Some(end_excl) = self.end_excl.checked_add(other.end_incl()) else {
310 return None;
311 };
312
313 // SAFETY:
314 // `checked_add` guarantees both bounds are computed without overflow.
315 // For half-open intervals, addition preserves ordering:
316 // self.start <= self.end_excl - 1 and other.start <= other.end_incl(),
317 // therefore `start < end_excl` still holds for the resulting interval.
318 Some(unsafe { Self::new_unchecked(start, end_excl) })
319 }
320
321 /// Minkowski subtraction: [a_start, a_end) - [b_start, b_end)
322 #[inline]
323 pub const fn checked_minkowski_sub(self, other: Self) -> Option<Self> {
324 let Some(start) = self.start.checked_sub(other.end_incl()) else {
325 return None;
326 };
327 let Some(end_excl) = self.end_excl.checked_sub(other.start) else {
328 return None;
329 };
330
331 // SAFETY:
332 // `checked_sub` guarantees both bounds are computed without underflow.
333 // Since `self.start <= self.end_excl - 1` and `other.start <= other.end_incl()`,
334 // we have `self.start - other.end_incl() < self.end_excl - other.start`,
335 // so the resulting half-open interval remains valid.
336 Some(unsafe { Self::new_unchecked(start, end_excl) })
337 }
338
339 /// Minkowski multiplication: [a_start, a_end) * [b_start, b_end)
340 #[inline]
341 pub const fn checked_minkowski_mul(self, other: Self) -> Option<Self> {
342 let Some(start) = self.start.checked_mul(other.start) else {
343 return None;
344 };
345 let Some(end_incl) = self.end_incl().checked_mul(other.end_incl()) else {
346 return None;
347 };
348 let Some(end_excl) = end_incl.checked_add(1) else {
349 return None;
350 };
351
352 // SAFETY:
353 // For `U8CO`, all values are non-negative, so endpoint-wise multiplication
354 // is monotone. `start` is the minimum product and `end_incl` is the maximum.
355 // `checked_add(1)` converts the inclusive upper bound back to half-open form,
356 // and overflow has already been excluded.
357 Some(unsafe { Self::new_unchecked(start, end_excl) })
358 }
359
360 /// Minkowski division: [a_start, a_end) / [b_start, b_end)
361 #[inline]
362 pub const fn checked_minkowski_div(self, other: Self) -> Option<Self> {
363 if other.start == 0 {
364 return None;
365 }
366
367 let Some(start) = self.start.checked_div(other.end_incl()) else {
368 return None;
369 };
370 let Some(end_incl) = self.end_incl().checked_div(other.start) else {
371 return None;
372 };
373 let Some(end_excl) = end_incl.checked_add(1) else {
374 return None;
375 };
376
377 // SAFETY:
378 // `other.start != 0` excludes division by zero, and all operands are unsigned.
379 // Over positive integers, division is monotone with respect to the dividend and
380 // anti-monotone with respect to the divisor, so:
381 // min = self.start / other.end_incl()
382 // max = self.end_incl() / other.start
383 // Thus `start <= end_incl`, and `end_excl = end_incl + 1` is checked.
384 Some(unsafe { Self::new_unchecked(start, end_excl) })
385 }
386 }
387
388 // --------------------------------------------------------
389 // Interval-to-scalar Minkowski operations
390 // --------------------------------------------------------
391 impl U8CO {
392 /// Add a scalar to an interval: [start, end) + n
393 #[inline]
394 pub const fn checked_minkowski_add_n(self, n: u8) -> Option<Self> {
395 let Some(start) = self.start.checked_add(n) else {
396 return None;
397 };
398 let Some(end_excl) = self.end_excl.checked_add(n) else {
399 return None;
400 };
401
402 // SAFETY:
403 // `checked_add` excludes overflow, and adding the same scalar to both bounds
404 // preserves the half-open interval ordering.
405 Some(unsafe { Self::new_unchecked(start, end_excl) })
406 }
407
408 /// Subtract a scalar from an interval: [start, end) - n
409 #[inline]
410 pub const fn checked_minkowski_sub_n(self, n: u8) -> Option<Self> {
411 let Some(start) = self.start.checked_sub(n) else {
412 return None;
413 };
414 let Some(end_excl) = self.end_excl.checked_sub(n) else {
415 return None;
416 };
417
418 // SAFETY:
419 // `checked_sub` excludes underflow, and subtracting the same scalar from both
420 // bounds preserves the half-open interval ordering.
421 Some(unsafe { Self::new_unchecked(start, end_excl) })
422 }
423
424 /// Multiply an interval by a scalar: [start, end) * n
425 #[inline]
426 pub const fn checked_minkowski_mul_n(self, n: u8) -> Option<Self> {
427 let Some(start) = self.start.checked_mul(n) else {
428 return None;
429 };
430 let Some(end_incl) = self.end_incl().checked_mul(n) else {
431 return None;
432 };
433 let Some(end_excl) = end_incl.checked_add(1) else {
434 return None;
435 };
436
437 // SAFETY:
438 // For unsigned integers, multiplication by a scalar is monotone.
439 // Therefore `start * n` is the lower bound and `end_incl * n` is the upper bound.
440 // `checked_*` operations exclude overflow, and `end_excl` restores half-open form.
441 Some(unsafe { Self::new_unchecked(start, end_excl) })
442 }
443
444 /// Divide an interval by a scalar: [start, end) / n
445 #[inline]
446 pub const fn checked_minkowski_div_n(self, n: u8) -> Option<Self> {
447 if n == 0 {
448 return None;
449 }
450
451 let start = self.start / n;
452 let end_incl = self.end_incl() / n;
453 let Some(end_excl) = end_incl.checked_add(1) else {
454 return None;
455 };
456
457 // SAFETY:
458 // `n != 0` excludes division by zero. For unsigned integers and positive scalar `n`,
459 // division is monotone, so `self.start / n <= self.end_incl() / n`.
460 // `checked_add(1)` safely converts the inclusive upper bound to half-open form.
461 Some(unsafe { Self::new_unchecked(start, end_excl) })
462 }
463 }
464 }
465
466 pub mod saturating {
467 use super::*;
468
469 // --------------------------------------------------------
470 // Interval-to-interval Minkowski operations
471 // --------------------------------------------------------
472 impl U8CO {
473 #[inline]
474 pub const fn saturating_minkowski_add(self, other: Self) -> Option<Self> {
475 let start = self.start.saturating_add(other.start);
476 let end_excl = self.end_excl.saturating_add(other.end_incl());
477 Self::try_new(start, end_excl)
478 }
479
480 #[inline]
481 pub const fn saturating_minkowski_sub(self, other: Self) -> Option<Self> {
482 let start = self.start.saturating_sub(other.end_incl());
483 let end_excl = self.end_excl.saturating_sub(other.start);
484 Self::try_new(start, end_excl)
485 }
486
487 #[inline]
488 pub const fn saturating_minkowski_mul(self, other: Self) -> Option<Self> {
489 let start = self.start.saturating_mul(other.start);
490 let end_incl = self.end_incl().saturating_mul(other.end_incl());
491 let end_excl = end_incl.saturating_add(1);
492 Self::try_new(start, end_excl)
493 }
494
495 #[inline]
496 pub const fn saturating_minkowski_div(self, other: Self) -> Option<Self> {
497 if other.start == 0 {
498 return None;
499 }
500
501 let start = self.start / other.end_incl();
502 let end_incl = self.end_incl() / other.start;
503 let end_excl = end_incl.saturating_add(1);
504 Self::try_new(start, end_excl)
505 }
506 }
507
508 // --------------------------------------------------------
509 // Interval-to-scalar Minkowski operations
510 // --------------------------------------------------------
511 impl U8CO {
512 /// Saturating add scalar: [start, end) + n
513 #[inline]
514 pub const fn saturating_minkowski_add_n(self, n: u8) -> Option<Self> {
515 let start = self.start.saturating_add(n);
516 let end_excl = self.end_excl.saturating_add(n);
517 Self::try_new(start, end_excl)
518 }
519
520 /// Saturating sub scalar: [start, end) - n
521 #[inline]
522 pub const fn saturating_minkowski_sub_n(self, n: u8) -> Option<Self> {
523 let start = self.start.saturating_sub(n);
524 let end_excl = self.end_excl.saturating_sub(n);
525 Self::try_new(start, end_excl)
526 }
527
528 /// Saturating mul scalar: [start, end) * n
529 #[inline]
530 pub const fn saturating_minkowski_mul_n(self, n: u8) -> Option<Self> {
531 let start = self.start.saturating_mul(n);
532 let end_incl = self.end_incl().saturating_mul(n);
533 let end_excl = end_incl.saturating_add(1);
534 Self::try_new(start, end_excl)
535 }
536
537 /// Saturating div scalar: [start, end) / n
538 #[inline]
539 pub const fn saturating_minkowski_div_n(self, n: u8) -> Option<Self> {
540 if n == 0 {
541 return None;
542 }
543
544 let start = self.start / n;
545 let end_incl = self.end_incl() / n;
546 let end_excl = end_incl.saturating_add(1);
547 Self::try_new(start, end_excl)
548 }
549 }
550 }
551}