Skip to main content

int_interval/
i8.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_symmetric_difference;
17#[cfg(test)]
18mod tests_for_union;
19
20#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
21pub struct I8CO {
22    start: i8,
23    end_excl: i8,
24}
25
26// ------------------------------------------------------------
27// low-level api: construction / accessors / predicates
28// ------------------------------------------------------------
29mod basic {
30
31    use super::*;
32
33    impl I8CO {
34        #[inline]
35        pub const fn try_new(start: i8, end_excl: i8) -> Option<Self> {
36            if start < end_excl {
37                Some(Self { start, end_excl })
38            } else {
39                None
40            }
41        }
42
43        #[inline]
44        pub const unsafe fn new_unchecked(start: i8, end_excl: i8) -> Self {
45            debug_assert!(start < end_excl);
46            Self { start, end_excl }
47        }
48
49        /// Constructs an `I8CO` interval from a midpoint and length (`u8`).
50        ///
51        /// # Parameters
52        /// - `mid`: the desired midpoint of the interval
53        /// - `len`: the desired length of the interval in units, must be `1..=u8::MAX`
54        ///
55        /// # Returns
56        /// - `Some(I8CO)` if the interval `[start, end_excl)` can be represented in `i8`
57        /// - `None` if `len = 0` or the computed `start` / `end_excl` would overflow `i8`
58        ///
59        /// # Guarantees
60        /// - Returned interval satisfies `start < end_excl`
61        /// - Maximum accepted input length is `u8::MAX`
62        #[inline]
63        pub const fn checked_from_midpoint_len(mid: i8, len: u8) -> Option<Self> {
64            if len == 0 {
65                return None;
66            }
67
68            let half = (len / 2) as i8;
69
70            let Some(start) = mid.checked_sub(half) else {
71                return None;
72            };
73            let Some(end_incl) = mid.checked_add(half) else {
74                return None;
75            };
76            let Some(end_excl) = end_incl.checked_add((len % 2) as i8) else {
77                return None;
78            };
79
80            // # Safety
81            // This function uses `unsafe { Self::new_unchecked(start, end_excl) }` internally.
82            // The safety is guaranteed by the following checks:
83            // 1. `mid.checked_sub(half)` ensures `start` does not underflow `i8`.
84            // 2. `mid.checked_add(the_other_half)` ensures `end_excl` does not overflow `i8`.
85            // 3. Because `half >= 0` and `the_other_half > 0`, we have `start < end_excl`.
86            // 4. Therefore, the half-open interval invariant `[start, end_excl)` is preserved.
87            Some(unsafe { Self::new_unchecked(start, end_excl) })
88        }
89
90        /// Constructs an `I8CO` interval from a midpoint and length (`u8`) with saturating semantics.
91        ///
92        /// # Parameters
93        /// - `mid`: the desired midpoint of the interval
94        /// - `len`: the desired length of the interval in units, must be `1..=u8::MAX`
95        ///
96        /// # Behavior
97        /// - Values are saturated at `i8::MIN` / `i8::MAX` to prevent overflow.
98        /// - If `len = 0`, returns `None`.
99        ///
100        /// # Guarantees
101        /// - Returned interval satisfies `start < end_excl`
102        /// - Maximum accepted input length is `u8::MAX`
103        /// - Fully compatible with codegen for other signed integer interval types
104        #[inline]
105        pub const fn saturating_from_midpoint_len(mid: i8, len: u8) -> Option<Self> {
106            if len == 0 {
107                return None;
108            }
109
110            let half = (len / 2) as i8;
111
112            let start = mid.saturating_sub(half);
113            let end_incl = mid.saturating_add(half);
114            let end_excl = end_incl.saturating_add((len % 2) as i8);
115
116            Self::try_new(start, end_excl)
117        }
118    }
119
120    #[inline]
121    const fn checked_end_excl_from_start_len(start: i8, len: u8) -> Option<i8> {
122        end_excl_from_start_len(start, len, false)
123    }
124
125    #[inline]
126    const fn saturating_end_excl_from_start_len(start: i8, len: u8) -> Option<i8> {
127        end_excl_from_start_len(start, len, true)
128    }
129
130    /// Computes the exclusive end bound for a signed start plus an unsigned length.
131    ///
132    /// This helper exists because the length type may represent values that do not
133    /// fit in the signed coordinate type. Therefore, directly casting `len` into
134    /// the coordinate type is not a valid general implementation.
135    ///
136    /// The computation is split around zero:
137    ///
138    /// - if `start < 0`, first consume the distance from `start` to zero;
139    /// - then place any remaining length on the non-negative side;
140    /// - if `start >= 0`, use the remaining representable room up to the signed
141    ///   coordinate maximum.
142    ///
143    /// No widening integer type is used. This is intentional, so the same control
144    /// flow can be emitted by codegen for other signed interval types.
145    ///
146    /// # Parameters
147    /// - `start`: inclusive start bound
148    /// - `len`: requested interval length
149    /// - `saturating`: whether overflow should clamp to the signed coordinate maximum
150    ///
151    /// # Returns
152    /// - `None` if `len == 0`;
153    /// - `None` if `saturating == false` and `start + len` would exceed the signed
154    ///   coordinate maximum;
155    /// - `Some(end_excl)` otherwise;
156    /// - when `saturating == true`, overflow is represented as the signed coordinate
157    ///   maximum.
158    ///
159    /// # Guarantees
160    /// - Never wraps signed or unsigned arithmetic.
161    /// - Never relies on a lossy unsigned-to-signed cast unless the value is known
162    ///   to fit in the signed coordinate type.
163    /// - Correctly handles intervals that cross zero.
164    /// - Correctly handles the signed coordinate minimum without evaluating its
165    ///   negation.
166    ///
167    /// # Non-guarantees
168    /// - This does not construct an interval value.
169    /// - This does not always guarantee `start < end_excl`.
170    ///   In saturating mode, a maximum start bound may return the same maximum as
171    ///   `end_excl`, which must later be rejected by the interval constructor.
172    /// - This does not guarantee the requested length is preserved in saturating
173    ///   mode; the result may be shorter.
174    /// - This does not provide a directly computable signed length for all
175    ///   successful intervals. Some valid logical lengths may exceed what the
176    ///   signed coordinate type can represent.
177    #[inline]
178    const fn end_excl_from_start_len(start: i8, len: u8, saturating: bool) -> Option<i8> {
179        if len == 0 {
180            return None;
181        }
182
183        if start < 0 {
184            let to_zero = if start == i8::MIN {
185                (i8::MAX as u8) + 1
186            } else {
187                (-start) as u8
188            };
189
190            if len < to_zero {
191                let Some(end_excl) = start.checked_add(len as i8) else {
192                    return None;
193                };
194                Some(end_excl)
195            } else if len == to_zero {
196                Some(0)
197            } else {
198                let rem = len - to_zero;
199
200                if rem > i8::MAX as u8 {
201                    if saturating { Some(i8::MAX) } else { None }
202                } else {
203                    Some(rem as i8)
204                }
205            }
206        } else {
207            let room = (i8::MAX - start) as u8;
208
209            if len > room {
210                if saturating { Some(i8::MAX) } else { None }
211            } else {
212                let Some(end_excl) = start.checked_add(len as i8) else {
213                    return None;
214                };
215                Some(end_excl)
216            }
217        }
218    }
219
220    impl I8CO {
221        /// Constructs an `I8CO` interval from a start position and length.
222        ///
223        /// The resulting interval is `[start, start + len)`.
224        ///
225        /// Handles signed cross-zero intervals without widening arithmetic.
226        /// For example, `start = -3, len = 5` produces `[-3, 2)`.
227        ///
228        /// Returns `None` if:
229        /// - `len == 0`;
230        /// - `start + len` would exceed `i8::MAX`.
231        #[inline]
232        pub const fn checked_from_start_len(start: i8, len: u8) -> Option<Self> {
233            let Some(end_excl) = checked_end_excl_from_start_len(start, len) else {
234                return None;
235            };
236
237            Some(unsafe { Self::new_unchecked(start, end_excl) })
238        }
239
240        /// Constructs an `I8CO` interval from a start position and length,
241        /// saturating the exclusive end bound at `i8::MAX`.
242        ///
243        /// The resulting interval is `[start, saturated_end_excl)`.
244        ///
245        /// Returns `None` if:
246        /// - `len == 0`;
247        /// - saturation still produces an empty interval, e.g. `start == i8::MAX`.
248        #[inline]
249        pub const fn saturating_from_start_len(start: i8, len: u8) -> Option<Self> {
250            let Some(end_excl) = saturating_end_excl_from_start_len(start, len) else {
251                return None;
252            };
253
254            Self::try_new(start, end_excl)
255        }
256    }
257
258    impl I8CO {
259        #[inline]
260        pub const fn start(self) -> i8 {
261            self.start
262        }
263
264        #[inline]
265        pub const fn end_excl(self) -> i8 {
266            self.end_excl
267        }
268
269        #[inline]
270        pub const fn end_incl(self) -> i8 {
271            // i8_low_bound =< start < end_excl
272            self.end_excl - 1
273        }
274
275        #[inline]
276        pub const fn len(self) -> u8 {
277            const SIGN_MASK: u8 = 1 << (i8::BITS - 1);
278            ((self.end_excl as u8) ^ SIGN_MASK) - ((self.start as u8) ^ SIGN_MASK)
279        }
280
281        /// Returns the midpoint of the interval `[start, end_excl)`,
282        /// using floor division if the length is even.
283        ///
284        /// # Guarantees
285        /// - `midpoint()` ∈ `[self.start, self.end_excl - 1]`
286        /// - Works for intervals with maximum length (entire `i8` range)
287        #[inline]
288        pub const fn midpoint(self) -> i8 {
289            self.start + (self.len() / 2) as i8
290        }
291
292        #[inline]
293        pub const fn contains(self, x: i8) -> bool {
294            self.start <= x && x < self.end_excl
295        }
296
297        #[inline]
298        pub const fn contains_interval(self, other: Self) -> bool {
299            self.start <= other.start && other.end_excl <= self.end_excl
300        }
301
302        #[inline]
303        pub const fn iter(self) -> core::ops::Range<i8> {
304            self.start..self.end_excl
305        }
306
307        #[inline]
308        pub const fn to_range(self) -> core::ops::Range<i8> {
309            self.start..self.end_excl
310        }
311
312        #[inline]
313        pub const fn intersects(self, other: Self) -> bool {
314            !(self.end_excl <= other.start || other.end_excl <= self.start)
315        }
316
317        #[inline]
318        pub const fn is_adjacent(self, other: Self) -> bool {
319            self.end_excl == other.start || other.end_excl == self.start
320        }
321
322        #[inline]
323        pub const fn is_contiguous_with(self, other: Self) -> bool {
324            self.intersects(other) || self.is_adjacent(other)
325        }
326    }
327}
328
329// ------------------------------------------------------------
330// interval algebra api: intersection / convex_hull / between / union / difference / symmetric_difference
331// ------------------------------------------------------------
332
333mod interval_algebra {
334
335    use super::*;
336
337    impl I8CO {
338        /// Returns the intersection of two intervals.
339        ///
340        /// If the intervals do not overlap, returns `None`.
341        #[inline]
342        pub const fn intersection(self, other: Self) -> Option<Self> {
343            let start = if self.start >= other.start {
344                self.start
345            } else {
346                other.start
347            };
348
349            let end_excl = if self.end_excl <= other.end_excl {
350                self.end_excl
351            } else {
352                other.end_excl
353            };
354
355            Self::try_new(start, end_excl)
356        }
357
358        /// Returns the convex hull (smallest interval containing both) of two intervals.
359        ///
360        /// Always returns a valid `I8CO`.
361        #[inline]
362        pub const fn convex_hull(self, other: Self) -> Self {
363            let start = if self.start <= other.start {
364                self.start
365            } else {
366                other.start
367            };
368
369            let end_excl = if self.end_excl >= other.end_excl {
370                self.end_excl
371            } else {
372                other.end_excl
373            };
374
375            Self { start, end_excl }
376        }
377
378        /// Returns the interval strictly between two intervals.
379        ///
380        /// If the intervals are contiguous or overlap, returns `None`.
381        #[inline]
382        pub const fn between(self, other: Self) -> Option<Self> {
383            let (left, right) = if self.start <= other.start {
384                (self, other)
385            } else {
386                (other, self)
387            };
388
389            Self::try_new(left.end_excl, right.start)
390        }
391
392        /// Returns the union of two intervals.
393        ///
394        /// - If intervals are contiguous or overlapping, returns `One` containing the merged interval.
395        /// - Otherwise, returns `Two` containing both intervals in ascending order.
396        #[inline]
397        pub const fn union(self, other: Self) -> OneTwo<Self> {
398            if self.is_contiguous_with(other) {
399                OneTwo::One(self.convex_hull(other))
400            } else if self.start <= other.start {
401                OneTwo::Two(self, other)
402            } else {
403                OneTwo::Two(other, self)
404            }
405        }
406
407        /// Returns the difference of two intervals (self - other).
408        ///
409        /// - If no overlap, returns `One(self)`.
410        /// - If partial overlap, returns `One` or `Two` depending on remaining segments.
411        /// - If fully contained, returns `Zero`.
412        #[inline]
413        pub const fn difference(self, other: Self) -> ZeroOneTwo<Self> {
414            match self.intersection(other) {
415                None => ZeroOneTwo::One(self),
416                Some(inter) => {
417                    let left = Self::try_new(self.start, inter.start);
418                    let right = Self::try_new(inter.end_excl, self.end_excl);
419
420                    match (left, right) {
421                        (None, None) => ZeroOneTwo::Zero,
422                        (Some(x), None) | (None, Some(x)) => ZeroOneTwo::One(x),
423                        (Some(x), Some(y)) => ZeroOneTwo::Two(x, y),
424                    }
425                }
426            }
427        }
428
429        /// Returns the symmetric difference of two intervals.
430        ///
431        /// Equivalent to `(self - other) ∪ (other - self)`.
432        /// - If intervals do not overlap, returns `Two(self, other)` in order.
433        /// - If intervals partially overlap, returns remaining non-overlapping segments as `One` or `Two`.
434        #[inline]
435        pub const fn symmetric_difference(self, other: Self) -> ZeroOneTwo<Self> {
436            match self.intersection(other) {
437                None => {
438                    if self.start <= other.start {
439                        ZeroOneTwo::Two(self, other)
440                    } else {
441                        ZeroOneTwo::Two(other, self)
442                    }
443                }
444                Some(inter) => {
445                    let hull = self.convex_hull(other);
446                    let left = Self::try_new(hull.start, inter.start);
447                    let right = Self::try_new(inter.end_excl, hull.end_excl);
448
449                    match (left, right) {
450                        (None, None) => ZeroOneTwo::Zero,
451                        (Some(x), None) | (None, Some(x)) => ZeroOneTwo::One(x),
452                        (Some(x), Some(y)) => ZeroOneTwo::Two(x, y),
453                    }
454                }
455            }
456        }
457    }
458}
459
460// ------------------------------------------------------------
461// Module: Minkowski arithmetic for I8CO
462// Provides checked and saturating Minkowski operations for intervals
463// ------------------------------------------------------------
464
465pub mod minkowski {
466    use super::*;
467
468    type Min = i8;
469    type Max = i8;
470
471    #[inline]
472    const fn min_max4(a: i8, b: i8, c: i8, d: i8) -> (Min, Max) {
473        let (min1, max1) = if a < b { (a, b) } else { (b, a) };
474        let (min2, max2) = if c < d { (c, d) } else { (d, c) };
475        let min = if min1 < min2 { min1 } else { min2 };
476        let max = if max1 > max2 { max1 } else { max2 };
477        (min, max)
478    }
479
480    #[inline]
481    const fn min_max2(a: i8, b: i8) -> (Min, Max) {
482        if a < b { (a, b) } else { (b, a) }
483    }
484
485    pub mod checked {
486        use super::*;
487
488        // --------------------------------------------------------
489        // Interval-to-interval
490        // --------------------------------------------------------
491        impl I8CO {
492            #[inline]
493            pub const fn checked_minkowski_add(self, other: Self) -> Option<Self> {
494                let Some(start) = self.start.checked_add(other.start) else {
495                    return None;
496                };
497                let Some(end_excl) = self.end_excl.checked_add(other.end_incl()) else {
498                    return None;
499                };
500
501                // SAFETY:
502                // `checked_add` guarantees both endpoint computations succeed without overflow.
503                // For half-open intervals, let `a_last = self.end_incl()` and `b_last = other.end_incl()`.
504                // Since `self.start <= a_last` and `other.start <= b_last`, we have
505                // `self.start + other.start <= a_last + b_last < self.end_excl + other.end_incl()`,
506                // hence the computed bounds satisfy `start < end_excl`.
507                Some(unsafe { Self::new_unchecked(start, end_excl) })
508            }
509
510            #[inline]
511            pub const fn checked_minkowski_sub(self, other: Self) -> Option<Self> {
512                let Some(start) = self.start.checked_sub(other.end_incl()) else {
513                    return None;
514                };
515                let Some(end_excl) = self.end_excl.checked_sub(other.start) else {
516                    return None;
517                };
518
519                // SAFETY:
520                // `checked_sub` guarantees both endpoint computations succeed without overflow.
521                // For interval subtraction, the minimum is attained at `self.start - other.end_incl()`
522                // and the exclusive upper bound is `self.end_excl - other.start`.
523                // Because `other.start <= other.end_incl()`, we get
524                // `self.start - other.end_incl() < self.end_excl - other.start`,
525                // so the resulting half-open interval is valid.
526                Some(unsafe { Self::new_unchecked(start, end_excl) })
527            }
528
529            #[inline]
530            pub const fn checked_minkowski_mul_hull(self, other: Self) -> Option<Self> {
531                let a = self.start;
532                let b = self.end_incl();
533                let c = other.start;
534                let d = other.end_incl();
535
536                let Some(p1) = a.checked_mul(c) else {
537                    return None;
538                };
539                let Some(p2) = a.checked_mul(d) else {
540                    return None;
541                };
542                let Some(p3) = b.checked_mul(c) else {
543                    return None;
544                };
545                let Some(p4) = b.checked_mul(d) else {
546                    return None;
547                };
548
549                let (start, end_incl) = min_max4(p1, p2, p3, p4);
550
551                let Some(end_excl) = end_incl.checked_add(1) else {
552                    return None;
553                };
554
555                // SAFETY:
556                // All four corner products are computed with `checked_mul`, so no intermediate
557                // multiplication overflows. For multiplication over a closed integer rectangle
558                // `[a, b] × [c, d]`, every attainable extremum occurs at a corner, so
559                // `min_max4(p1, p2, p3, p4)` yields the true inclusive lower/upper bounds.
560                // Therefore `start <= end_incl` holds by construction.
561                // `checked_add(1)` then safely converts the inclusive upper bound to the exclusive
562                // upper bound, and implies `end_excl = end_incl + 1`, hence `start < end_excl`.
563                Some(unsafe { Self::new_unchecked(start, end_excl) })
564            }
565
566            #[inline]
567            pub const fn checked_minkowski_div_hull(self, other: Self) -> Option<Self> {
568                if other.start <= 0 && other.end_incl() >= 0 {
569                    return None;
570                }
571
572                let a = self.start;
573                let b = self.end_incl();
574                let c = other.start;
575                let d = other.end_incl();
576
577                let Some(p1) = a.checked_div(c) else {
578                    return None;
579                };
580                let Some(p2) = a.checked_div(d) else {
581                    return None;
582                };
583                let Some(p3) = b.checked_div(c) else {
584                    return None;
585                };
586                let Some(p4) = b.checked_div(d) else {
587                    return None;
588                };
589
590                let (start, end_incl) = min_max4(p1, p2, p3, p4);
591
592                let Some(end_excl) = end_incl.checked_add(1) else {
593                    return None;
594                };
595
596                // SAFETY:
597                // The guard `other.start <= 0 && other.end_incl() >= 0` rejects any divisor interval
598                // that contains zero, so division by zero cannot occur anywhere in the divisor set.
599                // Each corner quotient is computed with `checked_div`, so exceptional signed cases
600                // such as `MIN / -1` are also rejected.
601                // On each connected component of the divisor domain that excludes zero, integer division
602                // is monotone with respect to the rectangle corners relevant to the extremum search;
603                // thus the global inclusive bounds over the interval pair are captured by the four
604                // corner quotients and recovered by `min_max4`, giving `start <= end_incl`.
605                // `checked_add(1)` safely converts the inclusive upper bound to half-open form, so
606                // the final bounds satisfy `start < end_excl`.
607                Some(unsafe { Self::new_unchecked(start, end_excl) })
608            }
609        }
610
611        // --------------------------------------------------------
612        // Scalar
613        // --------------------------------------------------------
614        impl I8CO {
615            #[inline]
616            pub const fn checked_minkowski_add_scalar(self, n: i8) -> Option<Self> {
617                let Some(start) = self.start.checked_add(n) else {
618                    return None;
619                };
620                let Some(end_excl) = self.end_excl.checked_add(n) else {
621                    return None;
622                };
623
624                // SAFETY:
625                // `checked_add` guarantees both translated bounds are computed without overflow.
626                // Adding the same scalar to both endpoints preserves the interval width exactly,
627                // so a valid half-open interval remains valid and still satisfies `start < end_excl`.
628                Some(unsafe { Self::new_unchecked(start, end_excl) })
629            }
630
631            #[inline]
632            pub const fn checked_minkowski_sub_scalar(self, n: i8) -> Option<Self> {
633                let Some(start) = self.start.checked_sub(n) else {
634                    return None;
635                };
636                let Some(end_excl) = self.end_excl.checked_sub(n) else {
637                    return None;
638                };
639
640                // SAFETY:
641                // `checked_sub` guarantees both translated bounds are computed without overflow.
642                // Subtracting the same scalar from both endpoints preserves the interval width exactly,
643                // so the strict half-open ordering is unchanged and `start < end_excl` still holds.
644                Some(unsafe { Self::new_unchecked(start, end_excl) })
645            }
646
647            #[inline]
648            pub const fn checked_minkowski_mul_scalar_hull(self, n: i8) -> Option<Self> {
649                let Some(a) = self.start.checked_mul(n) else {
650                    return None;
651                };
652                let Some(b) = self.end_incl().checked_mul(n) else {
653                    return None;
654                };
655
656                let (start, end_incl) = min_max2(a, b);
657
658                let Some(end_excl) = end_incl.checked_add(1) else {
659                    return None;
660                };
661
662                // SAFETY:
663                // Both endpoint products are computed with `checked_mul`, so no signed overflow occurs.
664                // Multiplication by a scalar maps the closed source interval endpoints to the two extreme
665                // candidates; `min_max2` therefore recovers the true inclusive lower/upper bounds whether
666                // `n` is positive, zero, or negative, giving `start <= end_incl`.
667                // `checked_add(1)` safely converts the inclusive upper bound into the exclusive upper bound,
668                // which guarantees the final half-open interval satisfies `start < end_excl`.
669                Some(unsafe { Self::new_unchecked(start, end_excl) })
670            }
671
672            #[inline]
673            pub const fn checked_minkowski_div_scalar_hull(self, n: i8) -> Option<Self> {
674                if n == 0 {
675                    return None;
676                }
677
678                let Some(a) = self.start.checked_div(n) else {
679                    return None;
680                };
681                let Some(b) = self.end_incl().checked_div(n) else {
682                    return None;
683                };
684
685                let (start, end_incl) = min_max2(a, b);
686
687                let Some(end_excl) = end_incl.checked_add(1) else {
688                    return None;
689                };
690
691                // SAFETY:
692                // The guard `n != 0` excludes division by zero, and `checked_div` additionally rejects
693                // the only overflowing signed case (`MIN / -1`).
694                // Division by a fixed nonzero scalar sends the source closed interval endpoints to the
695                // two extreme candidates for the image interval, so `min_max2` yields the true inclusive
696                // lower/upper bounds and ensures `start <= end_incl`.
697                // `checked_add(1)` safely restores half-open representation, therefore the constructed
698                // interval satisfies `start < end_excl`.
699                Some(unsafe { Self::new_unchecked(start, end_excl) })
700            }
701        }
702    }
703
704    // ========================================================
705    // SATURATING
706    // ========================================================
707    pub mod saturating {
708        use super::*;
709
710        impl I8CO {
711            #[inline]
712            pub const fn saturating_minkowski_add(self, other: Self) -> Option<Self> {
713                let start = self.start.saturating_add(other.start);
714                let end_excl = self.end_excl.saturating_add(other.end_incl());
715                Self::try_new(start, end_excl)
716            }
717
718            #[inline]
719            pub const fn saturating_minkowski_sub(self, other: Self) -> Option<Self> {
720                let start = self.start.saturating_sub(other.end_incl());
721                let end_excl = self.end_excl.saturating_sub(other.start);
722                Self::try_new(start, end_excl)
723            }
724
725            #[inline]
726            pub const fn saturating_minkowski_mul_hull(self, other: Self) -> Option<Self> {
727                let a = self.start.saturating_mul(other.start);
728                let b = self.start.saturating_mul(other.end_incl());
729                let c = self.end_incl().saturating_mul(other.start);
730                let d = self.end_incl().saturating_mul(other.end_incl());
731
732                let (start, end_incl) = min_max4(a, b, c, d);
733
734                let end_excl = end_incl.saturating_add(1);
735                Self::try_new(start, end_excl)
736            }
737
738            #[inline]
739            pub const fn saturating_minkowski_div_hull(self, other: Self) -> Option<Self> {
740                if other.start <= 0 && other.end_incl() >= 0 {
741                    return None;
742                }
743
744                let a = self.start / other.start;
745                let b = self.start / other.end_incl();
746                let c = self.end_incl() / other.start;
747                let d = self.end_incl() / other.end_incl();
748
749                let (start, end_incl) = min_max4(a, b, c, d);
750
751                let end_excl = end_incl.saturating_add(1);
752                Self::try_new(start, end_excl)
753            }
754        }
755
756        impl I8CO {
757            #[inline]
758            pub const fn saturating_minkowski_add_scalar(self, n: i8) -> Option<Self> {
759                let start = self.start.saturating_add(n);
760                let end_excl = self.end_excl.saturating_add(n);
761                Self::try_new(start, end_excl)
762            }
763
764            #[inline]
765            pub const fn saturating_minkowski_sub_scalar(self, n: i8) -> Option<Self> {
766                let start = self.start.saturating_sub(n);
767                let end_excl = self.end_excl.saturating_sub(n);
768                Self::try_new(start, end_excl)
769            }
770
771            #[inline]
772            pub const fn saturating_minkowski_mul_scalar_hull(self, n: i8) -> Option<Self> {
773                let a = self.start.saturating_mul(n);
774                let b = self.end_incl().saturating_mul(n);
775
776                let (start, end_incl) = min_max2(a, b);
777
778                let end_excl = end_incl.saturating_add(1);
779                Self::try_new(start, end_excl)
780            }
781
782            #[inline]
783            pub const fn saturating_minkowski_div_scalar_hull(self, n: i8) -> Option<Self> {
784                if n == 0 {
785                    return None;
786                }
787
788                let a = self.start / n;
789                let b = self.end_incl() / n;
790
791                let (start, end_incl) = min_max2(a, b);
792
793                let end_excl = end_incl.saturating_add(1);
794                Self::try_new(start, end_excl)
795            }
796        }
797    }
798}
799
800crate::traits::impl_co_forwarding!(I8CO, i8, u8);