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