Skip to main content

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}