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 impl I8CO {
121 #[inline]
122 pub const fn start(self) -> i8 {
123 self.start
124 }
125
126 #[inline]
127 pub const fn end_excl(self) -> i8 {
128 self.end_excl
129 }
130
131 #[inline]
132 pub const fn end_incl(self) -> i8 {
133 // i8_low_bound =< start < end_excl
134 self.end_excl - 1
135 }
136
137 #[inline]
138 pub const fn len(self) -> u8 {
139 const SIGN_MASK: u8 = 1 << (i8::BITS - 1);
140 ((self.end_excl as u8) ^ SIGN_MASK) - ((self.start as u8) ^ SIGN_MASK)
141 }
142
143 /// Returns the midpoint of the interval `[start, end_excl)`,
144 /// using floor division if the length is even.
145 ///
146 /// # Guarantees
147 /// - `midpoint()` ∈ `[self.start, self.end_excl - 1]`
148 /// - Works for intervals with maximum length (entire `i8` range)
149 #[inline]
150 pub const fn midpoint(self) -> i8 {
151 self.start + (self.len() / 2) as i8
152 }
153
154 #[inline]
155 pub const fn contains(self, x: i8) -> bool {
156 self.start <= x && x < self.end_excl
157 }
158
159 #[inline]
160 pub const fn contains_interval(self, other: Self) -> bool {
161 self.start <= other.start && other.end_excl <= self.end_excl
162 }
163
164 #[inline]
165 pub const fn iter(self) -> core::ops::Range<i8> {
166 self.start..self.end_excl
167 }
168
169 #[inline]
170 pub const fn to_range(self) -> core::ops::Range<i8> {
171 self.start..self.end_excl
172 }
173
174 #[inline]
175 pub const fn intersects(self, other: Self) -> bool {
176 !(self.end_excl <= other.start || other.end_excl <= self.start)
177 }
178
179 #[inline]
180 pub const fn is_adjacent(self, other: Self) -> bool {
181 self.end_excl == other.start || other.end_excl == self.start
182 }
183
184 #[inline]
185 pub const fn is_contiguous_with(self, other: Self) -> bool {
186 self.intersects(other) || self.is_adjacent(other)
187 }
188 }
189}
190
191// ------------------------------------------------------------
192// interval algebra api: intersection / convex_hull / between / union / difference / symmetric_difference
193// ------------------------------------------------------------
194
195mod interval_algebra {
196
197 use super::*;
198
199 impl I8CO {
200 /// Returns the intersection of two intervals.
201 ///
202 /// If the intervals do not overlap, returns `None`.
203 #[inline]
204 pub const fn intersection(self, other: Self) -> Option<Self> {
205 let start = if self.start >= other.start {
206 self.start
207 } else {
208 other.start
209 };
210
211 let end_excl = if self.end_excl <= other.end_excl {
212 self.end_excl
213 } else {
214 other.end_excl
215 };
216
217 Self::try_new(start, end_excl)
218 }
219
220 /// Returns the convex hull (smallest interval containing both) of two intervals.
221 ///
222 /// Always returns a valid `I8CO`.
223 #[inline]
224 pub const fn convex_hull(self, other: Self) -> Self {
225 let start = if self.start <= other.start {
226 self.start
227 } else {
228 other.start
229 };
230
231 let end_excl = if self.end_excl >= other.end_excl {
232 self.end_excl
233 } else {
234 other.end_excl
235 };
236
237 Self { start, end_excl }
238 }
239
240 /// Returns the interval strictly between two intervals.
241 ///
242 /// If the intervals are contiguous or overlap, returns `None`.
243 #[inline]
244 pub const fn between(self, other: Self) -> Option<Self> {
245 let (left, right) = if self.start <= other.start {
246 (self, other)
247 } else {
248 (other, self)
249 };
250
251 Self::try_new(left.end_excl, right.start)
252 }
253
254 /// Returns the union of two intervals.
255 ///
256 /// - If intervals are contiguous or overlapping, returns `One` containing the merged interval.
257 /// - Otherwise, returns `Two` containing both intervals in ascending order.
258 #[inline]
259 pub const fn union(self, other: Self) -> OneTwo<Self> {
260 if self.is_contiguous_with(other) {
261 OneTwo::One(self.convex_hull(other))
262 } else if self.start <= other.start {
263 OneTwo::Two(self, other)
264 } else {
265 OneTwo::Two(other, self)
266 }
267 }
268
269 /// Returns the difference of two intervals (self - other).
270 ///
271 /// - If no overlap, returns `One(self)`.
272 /// - If partial overlap, returns `One` or `Two` depending on remaining segments.
273 /// - If fully contained, returns `Zero`.
274 #[inline]
275 pub const fn difference(self, other: Self) -> ZeroOneTwo<Self> {
276 match self.intersection(other) {
277 None => ZeroOneTwo::One(self),
278 Some(inter) => {
279 let left = Self::try_new(self.start, inter.start);
280 let right = Self::try_new(inter.end_excl, self.end_excl);
281
282 match (left, right) {
283 (None, None) => ZeroOneTwo::Zero,
284 (Some(x), None) | (None, Some(x)) => ZeroOneTwo::One(x),
285 (Some(x), Some(y)) => ZeroOneTwo::Two(x, y),
286 }
287 }
288 }
289 }
290
291 /// Returns the symmetric difference of two intervals.
292 ///
293 /// Equivalent to `(self - other) ∪ (other - self)`.
294 /// - If intervals do not overlap, returns `Two(self, other)` in order.
295 /// - If intervals partially overlap, returns remaining non-overlapping segments as `One` or `Two`.
296 #[inline]
297 pub const fn symmetric_difference(self, other: Self) -> ZeroOneTwo<Self> {
298 match self.intersection(other) {
299 None => {
300 if self.start <= other.start {
301 ZeroOneTwo::Two(self, other)
302 } else {
303 ZeroOneTwo::Two(other, self)
304 }
305 }
306 Some(inter) => {
307 let hull = self.convex_hull(other);
308 let left = Self::try_new(hull.start, inter.start);
309 let right = Self::try_new(inter.end_excl, hull.end_excl);
310
311 match (left, right) {
312 (None, None) => ZeroOneTwo::Zero,
313 (Some(x), None) | (None, Some(x)) => ZeroOneTwo::One(x),
314 (Some(x), Some(y)) => ZeroOneTwo::Two(x, y),
315 }
316 }
317 }
318 }
319 }
320}
321
322// ------------------------------------------------------------
323// Module: Minkowski arithmetic for I8CO
324// Provides checked and saturating Minkowski operations for intervals
325// ------------------------------------------------------------
326
327pub mod minkowski {
328 use super::*;
329
330 type Min = i8;
331 type Max = i8;
332
333 #[inline]
334 const fn min_max4(a: i8, b: i8, c: i8, d: i8) -> (Min, Max) {
335 let (min1, max1) = if a < b { (a, b) } else { (b, a) };
336 let (min2, max2) = if c < d { (c, d) } else { (d, c) };
337 let min = if min1 < min2 { min1 } else { min2 };
338 let max = if max1 > max2 { max1 } else { max2 };
339 (min, max)
340 }
341
342 #[inline]
343 const fn min_max2(a: i8, b: i8) -> (Min, Max) {
344 if a < b { (a, b) } else { (b, a) }
345 }
346
347 pub mod checked {
348 use super::*;
349
350 // --------------------------------------------------------
351 // Interval-to-interval
352 // --------------------------------------------------------
353 impl I8CO {
354 #[inline]
355 pub const fn checked_minkowski_add(self, other: Self) -> Option<Self> {
356 let Some(start) = self.start.checked_add(other.start) else {
357 return None;
358 };
359 let Some(end_excl) = self.end_excl.checked_add(other.end_incl()) else {
360 return None;
361 };
362
363 // SAFETY:
364 // `checked_add` guarantees both endpoint computations succeed without overflow.
365 // For half-open intervals, let `a_last = self.end_incl()` and `b_last = other.end_incl()`.
366 // Since `self.start <= a_last` and `other.start <= b_last`, we have
367 // `self.start + other.start <= a_last + b_last < self.end_excl + other.end_incl()`,
368 // hence the computed bounds satisfy `start < end_excl`.
369 Some(unsafe { Self::new_unchecked(start, end_excl) })
370 }
371
372 #[inline]
373 pub const fn checked_minkowski_sub(self, other: Self) -> Option<Self> {
374 let Some(start) = self.start.checked_sub(other.end_incl()) else {
375 return None;
376 };
377 let Some(end_excl) = self.end_excl.checked_sub(other.start) else {
378 return None;
379 };
380
381 // SAFETY:
382 // `checked_sub` guarantees both endpoint computations succeed without overflow.
383 // For interval subtraction, the minimum is attained at `self.start - other.end_incl()`
384 // and the exclusive upper bound is `self.end_excl - other.start`.
385 // Because `other.start <= other.end_incl()`, we get
386 // `self.start - other.end_incl() < self.end_excl - other.start`,
387 // so the resulting half-open interval is valid.
388 Some(unsafe { Self::new_unchecked(start, end_excl) })
389 }
390
391 #[inline]
392 pub const fn checked_minkowski_mul_hull(self, other: Self) -> Option<Self> {
393 let a = self.start;
394 let b = self.end_incl();
395 let c = other.start;
396 let d = other.end_incl();
397
398 let Some(p1) = a.checked_mul(c) else {
399 return None;
400 };
401 let Some(p2) = a.checked_mul(d) else {
402 return None;
403 };
404 let Some(p3) = b.checked_mul(c) else {
405 return None;
406 };
407 let Some(p4) = b.checked_mul(d) else {
408 return None;
409 };
410
411 let (start, end_incl) = min_max4(p1, p2, p3, p4);
412
413 let Some(end_excl) = end_incl.checked_add(1) else {
414 return None;
415 };
416
417 // SAFETY:
418 // All four corner products are computed with `checked_mul`, so no intermediate
419 // multiplication overflows. For multiplication over a closed integer rectangle
420 // `[a, b] × [c, d]`, every attainable extremum occurs at a corner, so
421 // `min_max4(p1, p2, p3, p4)` yields the true inclusive lower/upper bounds.
422 // Therefore `start <= end_incl` holds by construction.
423 // `checked_add(1)` then safely converts the inclusive upper bound to the exclusive
424 // upper bound, and implies `end_excl = end_incl + 1`, hence `start < end_excl`.
425 Some(unsafe { Self::new_unchecked(start, end_excl) })
426 }
427
428 #[inline]
429 pub const fn checked_minkowski_div_hull(self, other: Self) -> Option<Self> {
430 if other.start <= 0 && other.end_incl() >= 0 {
431 return None;
432 }
433
434 let a = self.start;
435 let b = self.end_incl();
436 let c = other.start;
437 let d = other.end_incl();
438
439 let Some(p1) = a.checked_div(c) else {
440 return None;
441 };
442 let Some(p2) = a.checked_div(d) else {
443 return None;
444 };
445 let Some(p3) = b.checked_div(c) else {
446 return None;
447 };
448 let Some(p4) = b.checked_div(d) else {
449 return None;
450 };
451
452 let (start, end_incl) = min_max4(p1, p2, p3, p4);
453
454 let Some(end_excl) = end_incl.checked_add(1) else {
455 return None;
456 };
457
458 // SAFETY:
459 // The guard `other.start <= 0 && other.end_incl() >= 0` rejects any divisor interval
460 // that contains zero, so division by zero cannot occur anywhere in the divisor set.
461 // Each corner quotient is computed with `checked_div`, so exceptional signed cases
462 // such as `MIN / -1` are also rejected.
463 // On each connected component of the divisor domain that excludes zero, integer division
464 // is monotone with respect to the rectangle corners relevant to the extremum search;
465 // thus the global inclusive bounds over the interval pair are captured by the four
466 // corner quotients and recovered by `min_max4`, giving `start <= end_incl`.
467 // `checked_add(1)` safely converts the inclusive upper bound to half-open form, so
468 // the final bounds satisfy `start < end_excl`.
469 Some(unsafe { Self::new_unchecked(start, end_excl) })
470 }
471 }
472
473 // --------------------------------------------------------
474 // Scalar
475 // --------------------------------------------------------
476 impl I8CO {
477 #[inline]
478 pub const fn checked_minkowski_add_scalar(self, n: i8) -> Option<Self> {
479 let Some(start) = self.start.checked_add(n) else {
480 return None;
481 };
482 let Some(end_excl) = self.end_excl.checked_add(n) else {
483 return None;
484 };
485
486 // SAFETY:
487 // `checked_add` guarantees both translated bounds are computed without overflow.
488 // Adding the same scalar to both endpoints preserves the interval width exactly,
489 // so a valid half-open interval remains valid and still satisfies `start < end_excl`.
490 Some(unsafe { Self::new_unchecked(start, end_excl) })
491 }
492
493 #[inline]
494 pub const fn checked_minkowski_sub_scalar(self, n: i8) -> Option<Self> {
495 let Some(start) = self.start.checked_sub(n) else {
496 return None;
497 };
498 let Some(end_excl) = self.end_excl.checked_sub(n) else {
499 return None;
500 };
501
502 // SAFETY:
503 // `checked_sub` guarantees both translated bounds are computed without overflow.
504 // Subtracting the same scalar from both endpoints preserves the interval width exactly,
505 // so the strict half-open ordering is unchanged and `start < end_excl` still holds.
506 Some(unsafe { Self::new_unchecked(start, end_excl) })
507 }
508
509 #[inline]
510 pub const fn checked_minkowski_mul_scalar_hull(self, n: i8) -> Option<Self> {
511 let Some(a) = self.start.checked_mul(n) else {
512 return None;
513 };
514 let Some(b) = self.end_incl().checked_mul(n) else {
515 return None;
516 };
517
518 let (start, end_incl) = min_max2(a, b);
519
520 let Some(end_excl) = end_incl.checked_add(1) else {
521 return None;
522 };
523
524 // SAFETY:
525 // Both endpoint products are computed with `checked_mul`, so no signed overflow occurs.
526 // Multiplication by a scalar maps the closed source interval endpoints to the two extreme
527 // candidates; `min_max2` therefore recovers the true inclusive lower/upper bounds whether
528 // `n` is positive, zero, or negative, giving `start <= end_incl`.
529 // `checked_add(1)` safely converts the inclusive upper bound into the exclusive upper bound,
530 // which guarantees the final half-open interval satisfies `start < end_excl`.
531 Some(unsafe { Self::new_unchecked(start, end_excl) })
532 }
533
534 #[inline]
535 pub const fn checked_minkowski_div_scalar_hull(self, n: i8) -> Option<Self> {
536 if n == 0 {
537 return None;
538 }
539
540 let Some(a) = self.start.checked_div(n) else {
541 return None;
542 };
543 let Some(b) = self.end_incl().checked_div(n) else {
544 return None;
545 };
546
547 let (start, end_incl) = min_max2(a, b);
548
549 let Some(end_excl) = end_incl.checked_add(1) else {
550 return None;
551 };
552
553 // SAFETY:
554 // The guard `n != 0` excludes division by zero, and `checked_div` additionally rejects
555 // the only overflowing signed case (`MIN / -1`).
556 // Division by a fixed nonzero scalar sends the source closed interval endpoints to the
557 // two extreme candidates for the image interval, so `min_max2` yields the true inclusive
558 // lower/upper bounds and ensures `start <= end_incl`.
559 // `checked_add(1)` safely restores half-open representation, therefore the constructed
560 // interval satisfies `start < end_excl`.
561 Some(unsafe { Self::new_unchecked(start, end_excl) })
562 }
563 }
564 }
565
566 // ========================================================
567 // SATURATING
568 // ========================================================
569 pub mod saturating {
570 use super::*;
571
572 impl I8CO {
573 #[inline]
574 pub const fn saturating_minkowski_add(self, other: Self) -> Option<Self> {
575 let start = self.start.saturating_add(other.start);
576 let end_excl = self.end_excl.saturating_add(other.end_incl());
577 Self::try_new(start, end_excl)
578 }
579
580 #[inline]
581 pub const fn saturating_minkowski_sub(self, other: Self) -> Option<Self> {
582 let start = self.start.saturating_sub(other.end_incl());
583 let end_excl = self.end_excl.saturating_sub(other.start);
584 Self::try_new(start, end_excl)
585 }
586
587 #[inline]
588 pub const fn saturating_minkowski_mul_hull(self, other: Self) -> Option<Self> {
589 let a = self.start.saturating_mul(other.start);
590 let b = self.start.saturating_mul(other.end_incl());
591 let c = self.end_incl().saturating_mul(other.start);
592 let d = self.end_incl().saturating_mul(other.end_incl());
593
594 let (start, end_incl) = min_max4(a, b, c, d);
595
596 let end_excl = end_incl.saturating_add(1);
597 Self::try_new(start, end_excl)
598 }
599
600 #[inline]
601 pub const fn saturating_minkowski_div_hull(self, other: Self) -> Option<Self> {
602 if other.start <= 0 && other.end_incl() >= 0 {
603 return None;
604 }
605
606 let a = self.start / other.start;
607 let b = self.start / other.end_incl();
608 let c = self.end_incl() / other.start;
609 let d = self.end_incl() / other.end_incl();
610
611 let (start, end_incl) = min_max4(a, b, c, d);
612
613 let end_excl = end_incl.saturating_add(1);
614 Self::try_new(start, end_excl)
615 }
616 }
617
618 impl I8CO {
619 #[inline]
620 pub const fn saturating_minkowski_add_scalar(self, n: i8) -> Option<Self> {
621 let start = self.start.saturating_add(n);
622 let end_excl = self.end_excl.saturating_add(n);
623 Self::try_new(start, end_excl)
624 }
625
626 #[inline]
627 pub const fn saturating_minkowski_sub_scalar(self, n: i8) -> Option<Self> {
628 let start = self.start.saturating_sub(n);
629 let end_excl = self.end_excl.saturating_sub(n);
630 Self::try_new(start, end_excl)
631 }
632
633 #[inline]
634 pub const fn saturating_minkowski_mul_scalar_hull(self, n: i8) -> Option<Self> {
635 let a = self.start.saturating_mul(n);
636 let b = self.end_incl().saturating_mul(n);
637
638 let (start, end_incl) = min_max2(a, b);
639
640 let end_excl = end_incl.saturating_add(1);
641 Self::try_new(start, end_excl)
642 }
643
644 #[inline]
645 pub const fn saturating_minkowski_div_scalar_hull(self, n: i8) -> Option<Self> {
646 if n == 0 {
647 return None;
648 }
649
650 let a = self.start / n;
651 let b = self.end_incl() / n;
652
653 let (start, end_incl) = min_max2(a, b);
654
655 let end_excl = end_incl.saturating_add(1);
656 Self::try_new(start, end_excl)
657 }
658 }
659 }
660}
661
662crate::traits::impl_co_forwarding!(I8CO, i8, u8);