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