Skip to main content

int_intervals/interval/
u8.rs

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