Skip to main content

int_interval/
u8.rs

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