Skip to main content

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(super) const 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 iter(self) -> core::ops::Range<i8> {
75            self.start..self.end_excl
76        }
77
78        #[inline]
79        pub const fn to_range(self) -> core::ops::Range<i8> {
80            self.start..self.end_excl
81        }
82
83        #[inline]
84        pub const fn intersects(self, other: Self) -> bool {
85            !(self.end_excl <= other.start || other.end_excl <= self.start)
86        }
87
88        #[inline]
89        pub const fn is_adjacent(self, other: Self) -> bool {
90            self.end_excl == other.start || other.end_excl == self.start
91        }
92
93        #[inline]
94        pub const fn is_contiguous_with(self, other: Self) -> bool {
95            self.intersects(other) || self.is_adjacent(other)
96        }
97    }
98}
99// ------------------------------------------------------------
100// interval algebra api: intersection / convex_hull / between / union / difference / symmetric_difference
101// ------------------------------------------------------------
102
103mod interval_algebra {
104
105    use super::*;
106
107    impl I8CO {
108        /// Returns the intersection of two intervals.
109        ///
110        /// If the intervals do not overlap, returns `None`.
111        #[inline]
112        pub const fn intersection(self, other: Self) -> Option<Self> {
113            let start = if self.start >= other.start {
114                self.start
115            } else {
116                other.start
117            };
118
119            let end_excl = if self.end_excl <= other.end_excl {
120                self.end_excl
121            } else {
122                other.end_excl
123            };
124
125            Self::try_new(start, end_excl)
126        }
127
128        /// Returns the convex hull (smallest interval containing both) of two intervals.
129        ///
130        /// Always returns a valid `I8CO`.
131        #[inline]
132        pub const fn convex_hull(self, other: Self) -> Self {
133            let start = if self.start <= other.start {
134                self.start
135            } else {
136                other.start
137            };
138
139            let end_excl = if self.end_excl >= other.end_excl {
140                self.end_excl
141            } else {
142                other.end_excl
143            };
144
145            Self { start, end_excl }
146        }
147
148        /// Returns the interval strictly between two intervals.
149        ///
150        /// If the intervals are contiguous or overlap, returns `None`.
151        #[inline]
152        pub const fn between(self, other: Self) -> Option<Self> {
153            let (left, right) = if self.start <= other.start {
154                (self, other)
155            } else {
156                (other, self)
157            };
158
159            Self::try_new(left.end_excl, right.start)
160        }
161
162        /// Returns the union of two intervals.
163        ///
164        /// - If intervals are contiguous or overlapping, returns `One` containing the merged interval.
165        /// - Otherwise, returns `Two` containing both intervals in ascending order.
166        #[inline]
167        pub const fn union(self, other: Self) -> OneTwo<Self> {
168            if self.is_contiguous_with(other) {
169                OneTwo::One(self.convex_hull(other))
170            } else if self.start <= other.start {
171                OneTwo::Two(self, other)
172            } else {
173                OneTwo::Two(other, self)
174            }
175        }
176
177        /// Returns the difference of two intervals (self - other).
178        ///
179        /// - If no overlap, returns `One(self)`.
180        /// - If partial overlap, returns `One` or `Two` depending on remaining segments.
181        /// - If fully contained, returns `Zero`.
182        #[inline]
183        pub const fn difference(self, other: Self) -> ZeroOneTwo<Self> {
184            match self.intersection(other) {
185                None => ZeroOneTwo::One(self),
186                Some(inter) => {
187                    let left = Self::try_new(self.start, inter.start);
188                    let right = Self::try_new(inter.end_excl, self.end_excl);
189
190                    match (left, right) {
191                        (None, None) => ZeroOneTwo::Zero,
192                        (Some(x), None) | (None, Some(x)) => ZeroOneTwo::One(x),
193                        (Some(x), Some(y)) => ZeroOneTwo::Two(x, y),
194                    }
195                }
196            }
197        }
198
199        /// Returns the symmetric difference of two intervals.
200        ///
201        /// Equivalent to `(self - other) ∪ (other - self)`.
202        /// - If intervals do not overlap, returns `Two(self, other)` in order.
203        /// - If intervals partially overlap, returns remaining non-overlapping segments as `One` or `Two`.
204        #[inline]
205        pub const fn symmetric_difference(self, other: Self) -> ZeroOneTwo<Self> {
206            match self.intersection(other) {
207                None => {
208                    if self.start <= other.start {
209                        ZeroOneTwo::Two(self, other)
210                    } else {
211                        ZeroOneTwo::Two(other, self)
212                    }
213                }
214                Some(inter) => {
215                    let hull = self.convex_hull(other);
216                    let left = Self::try_new(hull.start, inter.start);
217                    let right = Self::try_new(inter.end_excl, hull.end_excl);
218
219                    match (left, right) {
220                        (None, None) => ZeroOneTwo::Zero,
221                        (Some(x), None) | (None, Some(x)) => ZeroOneTwo::One(x),
222                        (Some(x), Some(y)) => ZeroOneTwo::Two(x, y),
223                    }
224                }
225            }
226        }
227    }
228}
229
230// ------------------------------------------------------------
231// Module: Minkowski arithmetic for I8CO
232// Provides checked and saturating Minkowski operations for intervals
233// ------------------------------------------------------------
234
235pub mod minkowski {
236    use super::*;
237
238    type Min = i8;
239    type Max = i8;
240
241    #[inline]
242    const fn min_max4(a: i8, b: i8, c: i8, d: i8) -> (Min, Max) {
243        let (min1, max1) = if a < b { (a, b) } else { (b, a) };
244        let (min2, max2) = if c < d { (c, d) } else { (d, c) };
245        let min = if min1 < min2 { min1 } else { min2 };
246        let max = if max1 > max2 { max1 } else { max2 };
247        (min, max)
248    }
249
250    #[inline]
251    const fn min_max2(a: i8, b: i8) -> (Min, Max) {
252        if a < b { (a, b) } else { (b, a) }
253    }
254
255    pub mod checked {
256        use super::*;
257
258        // --------------------------------------------------------
259        // Interval-to-interval
260        // --------------------------------------------------------
261        impl I8CO {
262            #[inline]
263            pub const fn checked_minkowski_add(self, other: Self) -> Option<Self> {
264                let Some(start) = self.start.checked_add(other.start) else {
265                    return None;
266                };
267                let Some(end_excl) = self.end_excl.checked_add(other.end_incl()) else {
268                    return None;
269                };
270                Some(Self::new_unchecked(start, end_excl))
271            }
272
273            #[inline]
274            pub const fn checked_minkowski_sub(self, other: Self) -> Option<Self> {
275                let Some(start) = self.start.checked_sub(other.end_incl()) else {
276                    return None;
277                };
278                let Some(end_excl) = self.end_excl.checked_sub(other.start) else {
279                    return None;
280                };
281                Some(Self::new_unchecked(start, end_excl))
282            }
283
284            #[inline]
285            pub const fn checked_minkowski_mul(self, other: Self) -> Option<Self> {
286                let a = self.start;
287                let b = self.end_incl();
288                let c = other.start;
289                let d = other.end_incl();
290
291                let Some(p1) = a.checked_mul(c) else {
292                    return None;
293                };
294                let Some(p2) = a.checked_mul(d) else {
295                    return None;
296                };
297                let Some(p3) = b.checked_mul(c) else {
298                    return None;
299                };
300                let Some(p4) = b.checked_mul(d) else {
301                    return None;
302                };
303
304                let (start, end_incl) = min_max4(p1, p2, p3, p4);
305
306                let Some(end_excl) = end_incl.checked_add(1) else {
307                    return None;
308                };
309
310                Some(Self::new_unchecked(start, end_excl))
311            }
312
313            #[inline]
314            pub const fn checked_minkowski_div(self, other: Self) -> Option<Self> {
315                if other.start <= 0 && other.end_incl() >= 0 {
316                    return None;
317                }
318
319                let a = self.start;
320                let b = self.end_incl();
321                let c = other.start;
322                let d = other.end_incl();
323
324                let Some(p1) = a.checked_div(c) else {
325                    return None;
326                };
327                let Some(p2) = a.checked_div(d) else {
328                    return None;
329                };
330                let Some(p3) = b.checked_div(c) else {
331                    return None;
332                };
333                let Some(p4) = b.checked_div(d) else {
334                    return None;
335                };
336
337                let (start, end_incl) = min_max4(p1, p2, p3, p4);
338
339                let Some(end_excl) = end_incl.checked_add(1) else {
340                    return None;
341                };
342
343                Some(Self::new_unchecked(start, end_excl))
344            }
345        }
346
347        // --------------------------------------------------------
348        // Scalar
349        // --------------------------------------------------------
350        impl I8CO {
351            #[inline]
352            pub const fn checked_minkowski_add_n(self, n: i8) -> Option<Self> {
353                let Some(start) = self.start.checked_add(n) else {
354                    return None;
355                };
356                let Some(end_excl) = self.end_excl.checked_add(n) else {
357                    return None;
358                };
359                Some(Self::new_unchecked(start, end_excl))
360            }
361
362            #[inline]
363            pub const fn checked_minkowski_sub_n(self, n: i8) -> Option<Self> {
364                let Some(start) = self.start.checked_sub(n) else {
365                    return None;
366                };
367                let Some(end_excl) = self.end_excl.checked_sub(n) else {
368                    return None;
369                };
370                Some(Self::new_unchecked(start, end_excl))
371            }
372
373            #[inline]
374            pub const fn checked_minkowski_mul_n(self, n: i8) -> Option<Self> {
375                let Some(a) = self.start.checked_mul(n) else {
376                    return None;
377                };
378                let Some(b) = self.end_incl().checked_mul(n) else {
379                    return None;
380                };
381
382                let (start, end_incl) = min_max2(a, b);
383
384                let Some(end_excl) = end_incl.checked_add(1) else {
385                    return None;
386                };
387                Some(Self::new_unchecked(start, end_excl))
388            }
389
390            #[inline]
391            pub const fn checked_minkowski_div_n(self, n: i8) -> Option<Self> {
392                if n == 0 {
393                    return None;
394                }
395                let Some(a) = self.start.checked_div(n) else {
396                    return None;
397                };
398                let Some(b) = self.end_incl().checked_div(n) else {
399                    return None;
400                };
401
402                let (start, end_incl) = min_max2(a, b);
403
404                let Some(end_excl) = end_incl.checked_add(1) else {
405                    return None;
406                };
407                Some(Self::new_unchecked(start, end_excl))
408            }
409        }
410    }
411
412    // ========================================================
413    // SATURATING
414    // ========================================================
415    pub mod saturating {
416        use super::*;
417
418        impl I8CO {
419            #[inline]
420            pub const fn saturating_minkowski_add(self, other: Self) -> Option<Self> {
421                let start = self.start.saturating_add(other.start);
422                let end_excl = self.end_excl.saturating_add(other.end_incl());
423                Self::try_new(start, end_excl)
424            }
425
426            #[inline]
427            pub const fn saturating_minkowski_sub(self, other: Self) -> Option<Self> {
428                let start = self.start.saturating_sub(other.end_incl());
429                let end_excl = self.end_excl.saturating_sub(other.start);
430                Self::try_new(start, end_excl)
431            }
432
433            #[inline]
434            pub const fn saturating_minkowski_mul(self, other: Self) -> Option<Self> {
435                let a = self.start.saturating_mul(other.start);
436                let b = self.start.saturating_mul(other.end_incl());
437                let c = self.end_incl().saturating_mul(other.start);
438                let d = self.end_incl().saturating_mul(other.end_incl());
439
440                let (start, end_incl) = min_max4(a, b, c, d);
441
442                let end_excl = end_incl.saturating_add(1);
443                Self::try_new(start, end_excl)
444            }
445
446            #[inline]
447            pub const fn saturating_minkowski_div(self, other: Self) -> Option<Self> {
448                if other.start <= 0 && other.end_incl() >= 0 {
449                    return None;
450                }
451
452                let a = self.start / other.start;
453                let b = self.start / other.end_incl();
454                let c = self.end_incl() / other.start;
455                let d = self.end_incl() / other.end_incl();
456
457                let (start, end_incl) = min_max4(a, b, c, d);
458
459                let end_excl = end_incl.saturating_add(1);
460                Self::try_new(start, end_excl)
461            }
462        }
463
464        impl I8CO {
465            #[inline]
466            pub const fn saturating_minkowski_add_n(self, n: i8) -> Option<Self> {
467                let start = self.start.saturating_add(n);
468                let end_excl = self.end_excl.saturating_add(n);
469                Self::try_new(start, end_excl)
470            }
471
472            #[inline]
473            pub const fn saturating_minkowski_sub_n(self, n: i8) -> Option<Self> {
474                let start = self.start.saturating_sub(n);
475                let end_excl = self.end_excl.saturating_sub(n);
476                Self::try_new(start, end_excl)
477            }
478
479            #[inline]
480            pub const fn saturating_minkowski_mul_n(self, n: i8) -> Option<Self> {
481                let a = self.start.saturating_mul(n);
482                let b = self.end_incl().saturating_mul(n);
483
484                let (start, end_incl) = min_max2(a, b);
485
486                let end_excl = end_incl.saturating_add(1);
487                Self::try_new(start, end_excl)
488            }
489
490            #[inline]
491            pub const fn saturating_minkowski_div_n(self, n: i8) -> Option<Self> {
492                if n == 0 {
493                    return None;
494                }
495
496                let a = self.start / n;
497                let b = self.end_incl() / n;
498
499                let (start, end_incl) = min_max2(a, b);
500
501                let end_excl = end_incl.saturating_add(1);
502                Self::try_new(start, end_excl)
503            }
504        }
505    }
506}