Skip to main content

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