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