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