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