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);