Skip to main content

int_interval/
i8.rs

1use crate::res::{OneTwo, ZeroOneTwo};
2
3
4#[cfg(test)]
5mod basic_tests;
6#[cfg(test)]
7mod between_tests;
8#[cfg(test)]
9mod checked_minkowski_tests;
10#[cfg(test)]
11mod convex_hull_tests;
12#[cfg(test)]
13mod difference_tests;
14#[cfg(test)]
15mod intersection_tests;
16#[cfg(test)]
17mod symmetric_difference_tests;
18#[cfg(test)]
19mod union_tests;
20
21#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
22pub struct I8CO {
23    start: i8,
24    end_excl: i8,
25}
26
27// ------------------------------------------------------------
28// low-level api: construction / accessors / predicates
29// ------------------------------------------------------------
30mod basic {
31
32    use super::*;
33
34    impl I8CO {
35        #[inline]
36        pub const fn try_new(start: i8, end_excl: i8) -> Option<Self> {
37            if start < end_excl {
38                Some(Self { start, end_excl })
39            } else {
40                None
41            }
42        }
43
44        #[inline]
45        pub const unsafe fn new_unchecked(start: i8, end_excl: i8) -> Self {
46            debug_assert!(start < end_excl);
47            Self { start, end_excl }
48        }
49
50        #[inline]
51        pub const fn start(self) -> i8 {
52            self.start
53        }
54
55        #[inline]
56        pub const fn end_excl(self) -> i8 {
57            self.end_excl
58        }
59
60        #[inline]
61        pub const fn end_incl(self) -> i8 {
62            // i8_low_bound =< start < end_excl
63            self.end_excl - 1
64        }
65
66        #[inline]
67        pub const fn len(self) -> u8 {
68            const SIGN_MASK: u8 = 1 << (i8::BITS - 1);
69            ((self.end_excl as u8) ^ SIGN_MASK) - ((self.start as u8) ^ SIGN_MASK)
70        }
71
72        #[inline]
73        pub const fn contains(self, x: i8) -> bool {
74            self.start <= x && x < self.end_excl
75        }
76
77        #[inline]
78        pub const fn contains_interval(self, other: Self) -> bool {
79            self.start <= other.start && other.end_excl <= self.end_excl
80        }
81
82        #[inline]
83        pub const fn iter(self) -> core::ops::Range<i8> {
84            self.start..self.end_excl
85        }
86
87        #[inline]
88        pub const fn to_range(self) -> core::ops::Range<i8> {
89            self.start..self.end_excl
90        }
91
92        #[inline]
93        pub const fn intersects(self, other: Self) -> bool {
94            !(self.end_excl <= other.start || other.end_excl <= self.start)
95        }
96
97        #[inline]
98        pub const fn is_adjacent(self, other: Self) -> bool {
99            self.end_excl == other.start || other.end_excl == self.start
100        }
101
102        #[inline]
103        pub const fn is_contiguous_with(self, other: Self) -> bool {
104            self.intersects(other) || self.is_adjacent(other)
105        }
106    }
107}
108
109// ------------------------------------------------------------
110// interval algebra api: intersection / convex_hull / between / union / difference / symmetric_difference
111// ------------------------------------------------------------
112
113mod interval_algebra {
114
115    use super::*;
116
117    impl I8CO {
118        /// Returns the intersection of two intervals.
119        ///
120        /// If the intervals do not overlap, returns `None`.
121        #[inline]
122        pub const fn intersection(self, other: Self) -> Option<Self> {
123            let start = if self.start >= other.start {
124                self.start
125            } else {
126                other.start
127            };
128
129            let end_excl = if self.end_excl <= other.end_excl {
130                self.end_excl
131            } else {
132                other.end_excl
133            };
134
135            Self::try_new(start, end_excl)
136        }
137
138        /// Returns the convex hull (smallest interval containing both) of two intervals.
139        ///
140        /// Always returns a valid `I8CO`.
141        #[inline]
142        pub const fn convex_hull(self, other: Self) -> Self {
143            let start = if self.start <= other.start {
144                self.start
145            } else {
146                other.start
147            };
148
149            let end_excl = if self.end_excl >= other.end_excl {
150                self.end_excl
151            } else {
152                other.end_excl
153            };
154
155            Self { start, end_excl }
156        }
157
158        /// Returns the interval strictly between two intervals.
159        ///
160        /// If the intervals are contiguous or overlap, returns `None`.
161        #[inline]
162        pub const fn between(self, other: Self) -> Option<Self> {
163            let (left, right) = if self.start <= other.start {
164                (self, other)
165            } else {
166                (other, self)
167            };
168
169            Self::try_new(left.end_excl, right.start)
170        }
171
172        /// Returns the union of two intervals.
173        ///
174        /// - If intervals are contiguous or overlapping, returns `One` containing the merged interval.
175        /// - Otherwise, returns `Two` containing both intervals in ascending order.
176        #[inline]
177        pub const fn union(self, other: Self) -> OneTwo<Self> {
178            if self.is_contiguous_with(other) {
179                OneTwo::One(self.convex_hull(other))
180            } else if self.start <= other.start {
181                OneTwo::Two(self, other)
182            } else {
183                OneTwo::Two(other, self)
184            }
185        }
186
187        /// Returns the difference of two intervals (self - other).
188        ///
189        /// - If no overlap, returns `One(self)`.
190        /// - If partial overlap, returns `One` or `Two` depending on remaining segments.
191        /// - If fully contained, returns `Zero`.
192        #[inline]
193        pub const fn difference(self, other: Self) -> ZeroOneTwo<Self> {
194            match self.intersection(other) {
195                None => ZeroOneTwo::One(self),
196                Some(inter) => {
197                    let left = Self::try_new(self.start, inter.start);
198                    let right = Self::try_new(inter.end_excl, self.end_excl);
199
200                    match (left, right) {
201                        (None, None) => ZeroOneTwo::Zero,
202                        (Some(x), None) | (None, Some(x)) => ZeroOneTwo::One(x),
203                        (Some(x), Some(y)) => ZeroOneTwo::Two(x, y),
204                    }
205                }
206            }
207        }
208
209        /// Returns the symmetric difference of two intervals.
210        ///
211        /// Equivalent to `(self - other) ∪ (other - self)`.
212        /// - If intervals do not overlap, returns `Two(self, other)` in order.
213        /// - If intervals partially overlap, returns remaining non-overlapping segments as `One` or `Two`.
214        #[inline]
215        pub const fn symmetric_difference(self, other: Self) -> ZeroOneTwo<Self> {
216            match self.intersection(other) {
217                None => {
218                    if self.start <= other.start {
219                        ZeroOneTwo::Two(self, other)
220                    } else {
221                        ZeroOneTwo::Two(other, self)
222                    }
223                }
224                Some(inter) => {
225                    let hull = self.convex_hull(other);
226                    let left = Self::try_new(hull.start, inter.start);
227                    let right = Self::try_new(inter.end_excl, hull.end_excl);
228
229                    match (left, right) {
230                        (None, None) => ZeroOneTwo::Zero,
231                        (Some(x), None) | (None, Some(x)) => ZeroOneTwo::One(x),
232                        (Some(x), Some(y)) => ZeroOneTwo::Two(x, y),
233                    }
234                }
235            }
236        }
237    }
238}
239
240// ------------------------------------------------------------
241// Module: Minkowski arithmetic for I8CO
242// Provides checked and saturating Minkowski operations for intervals
243// ------------------------------------------------------------
244
245pub mod minkowski {
246    use super::*;
247
248    type Min = i8;
249    type Max = i8;
250
251    #[inline]
252    const fn min_max4(a: i8, b: i8, c: i8, d: i8) -> (Min, Max) {
253        let (min1, max1) = if a < b { (a, b) } else { (b, a) };
254        let (min2, max2) = if c < d { (c, d) } else { (d, c) };
255        let min = if min1 < min2 { min1 } else { min2 };
256        let max = if max1 > max2 { max1 } else { max2 };
257        (min, max)
258    }
259
260    #[inline]
261    const fn min_max2(a: i8, b: i8) -> (Min, Max) {
262        if a < b { (a, b) } else { (b, a) }
263    }
264
265    pub mod checked {
266        use super::*;
267
268        // --------------------------------------------------------
269        // Interval-to-interval
270        // --------------------------------------------------------
271        impl I8CO {
272            #[inline]
273            pub const fn checked_minkowski_add(self, other: Self) -> Option<Self> {
274                let Some(start) = self.start.checked_add(other.start) else {
275                    return None;
276                };
277                let Some(end_excl) = self.end_excl.checked_add(other.end_incl()) else {
278                    return None;
279                };
280
281                // SAFETY:
282                // `checked_add` guarantees both endpoint computations succeed without overflow.
283                // For half-open intervals, let `a_last = self.end_incl()` and `b_last = other.end_incl()`.
284                // Since `self.start <= a_last` and `other.start <= b_last`, we have
285                // `self.start + other.start <= a_last + b_last < self.end_excl + other.end_incl()`,
286                // hence the computed bounds satisfy `start < end_excl`.
287                Some(unsafe { Self::new_unchecked(start, end_excl) })
288            }
289
290            #[inline]
291            pub const fn checked_minkowski_sub(self, other: Self) -> Option<Self> {
292                let Some(start) = self.start.checked_sub(other.end_incl()) else {
293                    return None;
294                };
295                let Some(end_excl) = self.end_excl.checked_sub(other.start) else {
296                    return None;
297                };
298
299                // SAFETY:
300                // `checked_sub` guarantees both endpoint computations succeed without overflow.
301                // For interval subtraction, the minimum is attained at `self.start - other.end_incl()`
302                // and the exclusive upper bound is `self.end_excl - other.start`.
303                // Because `other.start <= other.end_incl()`, we get
304                // `self.start - other.end_incl() < self.end_excl - other.start`,
305                // so the resulting half-open interval is valid.
306                Some(unsafe { Self::new_unchecked(start, end_excl) })
307            }
308
309            #[inline]
310            pub const fn checked_minkowski_mul(self, other: Self) -> Option<Self> {
311                let a = self.start;
312                let b = self.end_incl();
313                let c = other.start;
314                let d = other.end_incl();
315
316                let Some(p1) = a.checked_mul(c) else {
317                    return None;
318                };
319                let Some(p2) = a.checked_mul(d) else {
320                    return None;
321                };
322                let Some(p3) = b.checked_mul(c) else {
323                    return None;
324                };
325                let Some(p4) = b.checked_mul(d) else {
326                    return None;
327                };
328
329                let (start, end_incl) = min_max4(p1, p2, p3, p4);
330
331                let Some(end_excl) = end_incl.checked_add(1) else {
332                    return None;
333                };
334
335                // SAFETY:
336                // All four corner products are computed with `checked_mul`, so no intermediate
337                // multiplication overflows. For multiplication over a closed integer rectangle
338                // `[a, b] × [c, d]`, every attainable extremum occurs at a corner, so
339                // `min_max4(p1, p2, p3, p4)` yields the true inclusive lower/upper bounds.
340                // Therefore `start <= end_incl` holds by construction.
341                // `checked_add(1)` then safely converts the inclusive upper bound to the exclusive
342                // upper bound, and implies `end_excl = end_incl + 1`, hence `start < end_excl`.
343                Some(unsafe { Self::new_unchecked(start, end_excl) })
344            }
345
346            #[inline]
347            pub const fn checked_minkowski_div(self, other: Self) -> Option<Self> {
348                if other.start <= 0 && other.end_incl() >= 0 {
349                    return None;
350                }
351
352                let a = self.start;
353                let b = self.end_incl();
354                let c = other.start;
355                let d = other.end_incl();
356
357                let Some(p1) = a.checked_div(c) else {
358                    return None;
359                };
360                let Some(p2) = a.checked_div(d) else {
361                    return None;
362                };
363                let Some(p3) = b.checked_div(c) else {
364                    return None;
365                };
366                let Some(p4) = b.checked_div(d) else {
367                    return None;
368                };
369
370                let (start, end_incl) = min_max4(p1, p2, p3, p4);
371
372                let Some(end_excl) = end_incl.checked_add(1) else {
373                    return None;
374                };
375
376                // SAFETY:
377                // The guard `other.start <= 0 && other.end_incl() >= 0` rejects any divisor interval
378                // that contains zero, so division by zero cannot occur anywhere in the divisor set.
379                // Each corner quotient is computed with `checked_div`, so exceptional signed cases
380                // such as `MIN / -1` are also rejected.
381                // On each connected component of the divisor domain that excludes zero, integer division
382                // is monotone with respect to the rectangle corners relevant to the extremum search;
383                // thus the global inclusive bounds over the interval pair are captured by the four
384                // corner quotients and recovered by `min_max4`, giving `start <= end_incl`.
385                // `checked_add(1)` safely converts the inclusive upper bound to half-open form, so
386                // the final bounds satisfy `start < end_excl`.
387                Some(unsafe { Self::new_unchecked(start, end_excl) })
388            }
389        }
390
391        // --------------------------------------------------------
392        // Scalar
393        // --------------------------------------------------------
394        impl I8CO {
395            #[inline]
396            pub const fn checked_minkowski_add_n(self, n: i8) -> 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` guarantees both translated bounds are computed without overflow.
406                // Adding the same scalar to both endpoints preserves the interval width exactly,
407                // so a valid half-open interval remains valid and still satisfies `start < end_excl`.
408                Some(unsafe { Self::new_unchecked(start, end_excl) })
409            }
410
411            #[inline]
412            pub const fn checked_minkowski_sub_n(self, n: i8) -> 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` guarantees both translated bounds are computed without overflow.
422                // Subtracting the same scalar from both endpoints preserves the interval width exactly,
423                // so the strict half-open ordering is unchanged and `start < end_excl` still holds.
424                Some(unsafe { Self::new_unchecked(start, end_excl) })
425            }
426
427            #[inline]
428            pub const fn checked_minkowski_mul_n(self, n: i8) -> Option<Self> {
429                let Some(a) = self.start.checked_mul(n) else {
430                    return None;
431                };
432                let Some(b) = self.end_incl().checked_mul(n) else {
433                    return None;
434                };
435
436                let (start, end_incl) = min_max2(a, b);
437
438                let Some(end_excl) = end_incl.checked_add(1) else {
439                    return None;
440                };
441
442                // SAFETY:
443                // Both endpoint products are computed with `checked_mul`, so no signed overflow occurs.
444                // Multiplication by a scalar maps the closed source interval endpoints to the two extreme
445                // candidates; `min_max2` therefore recovers the true inclusive lower/upper bounds whether
446                // `n` is positive, zero, or negative, giving `start <= end_incl`.
447                // `checked_add(1)` safely converts the inclusive upper bound into the exclusive upper bound,
448                // which guarantees the final half-open interval satisfies `start < end_excl`.
449                Some(unsafe { Self::new_unchecked(start, end_excl) })
450            }
451
452            #[inline]
453            pub const fn checked_minkowski_div_n(self, n: i8) -> Option<Self> {
454                if n == 0 {
455                    return None;
456                }
457
458                let Some(a) = self.start.checked_div(n) else {
459                    return None;
460                };
461                let Some(b) = self.end_incl().checked_div(n) else {
462                    return None;
463                };
464
465                let (start, end_incl) = min_max2(a, b);
466
467                let Some(end_excl) = end_incl.checked_add(1) else {
468                    return None;
469                };
470
471                // SAFETY:
472                // The guard `n != 0` excludes division by zero, and `checked_div` additionally rejects
473                // the only overflowing signed case (`MIN / -1`).
474                // Division by a fixed nonzero scalar sends the source closed interval endpoints to the
475                // two extreme candidates for the image interval, so `min_max2` yields the true inclusive
476                // lower/upper bounds and ensures `start <= end_incl`.
477                // `checked_add(1)` safely restores half-open representation, therefore the constructed
478                // interval satisfies `start < end_excl`.
479                Some(unsafe { Self::new_unchecked(start, end_excl) })
480            }
481        }
482    }
483
484    // ========================================================
485    // SATURATING
486    // ========================================================
487    pub mod saturating {
488        use super::*;
489
490        impl I8CO {
491            #[inline]
492            pub const fn saturating_minkowski_add(self, other: Self) -> Option<Self> {
493                let start = self.start.saturating_add(other.start);
494                let end_excl = self.end_excl.saturating_add(other.end_incl());
495                Self::try_new(start, end_excl)
496            }
497
498            #[inline]
499            pub const fn saturating_minkowski_sub(self, other: Self) -> Option<Self> {
500                let start = self.start.saturating_sub(other.end_incl());
501                let end_excl = self.end_excl.saturating_sub(other.start);
502                Self::try_new(start, end_excl)
503            }
504
505            #[inline]
506            pub const fn saturating_minkowski_mul(self, other: Self) -> Option<Self> {
507                let a = self.start.saturating_mul(other.start);
508                let b = self.start.saturating_mul(other.end_incl());
509                let c = self.end_incl().saturating_mul(other.start);
510                let d = self.end_incl().saturating_mul(other.end_incl());
511
512                let (start, end_incl) = min_max4(a, b, c, d);
513
514                let end_excl = end_incl.saturating_add(1);
515                Self::try_new(start, end_excl)
516            }
517
518            #[inline]
519            pub const fn saturating_minkowski_div(self, other: Self) -> Option<Self> {
520                if other.start <= 0 && other.end_incl() >= 0 {
521                    return None;
522                }
523
524                let a = self.start / other.start;
525                let b = self.start / other.end_incl();
526                let c = self.end_incl() / other.start;
527                let d = self.end_incl() / other.end_incl();
528
529                let (start, end_incl) = min_max4(a, b, c, d);
530
531                let end_excl = end_incl.saturating_add(1);
532                Self::try_new(start, end_excl)
533            }
534        }
535
536        impl I8CO {
537            #[inline]
538            pub const fn saturating_minkowski_add_n(self, n: i8) -> Option<Self> {
539                let start = self.start.saturating_add(n);
540                let end_excl = self.end_excl.saturating_add(n);
541                Self::try_new(start, end_excl)
542            }
543
544            #[inline]
545            pub const fn saturating_minkowski_sub_n(self, n: i8) -> Option<Self> {
546                let start = self.start.saturating_sub(n);
547                let end_excl = self.end_excl.saturating_sub(n);
548                Self::try_new(start, end_excl)
549            }
550
551            #[inline]
552            pub const fn saturating_minkowski_mul_n(self, n: i8) -> Option<Self> {
553                let a = self.start.saturating_mul(n);
554                let b = self.end_incl().saturating_mul(n);
555
556                let (start, end_incl) = min_max2(a, b);
557
558                let end_excl = end_incl.saturating_add(1);
559                Self::try_new(start, end_excl)
560            }
561
562            #[inline]
563            pub const fn saturating_minkowski_div_n(self, n: i8) -> Option<Self> {
564                if n == 0 {
565                    return None;
566                }
567
568                let a = self.start / n;
569                let b = self.end_incl() / n;
570
571                let (start, end_incl) = min_max2(a, b);
572
573                let end_excl = end_incl.saturating_add(1);
574                Self::try_new(start, end_excl)
575            }
576        }
577    }
578}