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}