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