int_interval/u8.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 saturating_minkowski_tests;
15#[cfg(test)]
16mod symmetric_difference_tests;
17#[cfg(test)]
18mod union_tests;
19
20#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
21pub struct U8CO {
22 start: u8,
23 end_excl: u8,
24}
25
26// ------------------------------------------------------------
27// low-level api: construction / accessors / predicates
28// ------------------------------------------------------------
29
30mod construction_accessors_predicates {
31
32 use super::*;
33
34 impl U8CO {
35 #[inline]
36 pub const fn try_new(start: u8, end_excl: u8) -> 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: u8, end_excl: u8) -> Self {
46 debug_assert!(start < end_excl);
47 Self { start, end_excl }
48 }
49
50 #[inline]
51 pub const fn start(self) -> u8 {
52 self.start
53 }
54
55 #[inline]
56 pub const fn end_excl(self) -> u8 {
57 self.end_excl
58 }
59
60 #[inline]
61 pub const fn end_incl(self) -> u8 {
62 // u8_low_bound =< start < end_excl
63 self.end_excl - 1
64 }
65
66 #[inline]
67 pub const fn len(self) -> u8 {
68 self.end_excl - self.start
69 }
70
71 #[inline]
72 pub const fn contains(self, x: u8) -> bool {
73 self.start <= x && x < self.end_excl
74 }
75
76 #[inline]
77 pub const fn iter(self) -> core::ops::Range<u8> {
78 self.start..self.end_excl
79 }
80
81 #[inline]
82 pub const fn to_range(self) -> core::ops::Range<u8> {
83 self.start..self.end_excl
84 }
85
86 #[inline]
87 pub const fn intersects(self, other: Self) -> bool {
88 !(self.end_excl <= other.start || other.end_excl <= self.start)
89 }
90
91 #[inline]
92 pub const fn is_adjacent(self, other: Self) -> bool {
93 self.end_excl == other.start || other.end_excl == self.start
94 }
95
96 #[inline]
97 pub const fn is_contiguous_with(self, other: Self) -> bool {
98 self.intersects(other) || self.is_adjacent(other)
99 }
100 }
101}
102
103// ------------------------------------------------------------
104// interval algebra api: intersection / convex_hull / between / union / difference / symmetric_difference
105// ------------------------------------------------------------
106
107mod interval_algebra {
108
109 use super::*;
110
111 impl U8CO {
112 /// Returns the intersection of two intervals.
113 ///
114 /// If the intervals do not overlap, returns `None`.
115 #[inline]
116 pub const fn intersection(self, other: Self) -> Option<Self> {
117 let start = if self.start >= other.start {
118 self.start
119 } else {
120 other.start
121 };
122
123 let end_excl = if self.end_excl <= other.end_excl {
124 self.end_excl
125 } else {
126 other.end_excl
127 };
128
129 Self::try_new(start, end_excl)
130 }
131
132 /// Returns the convex hull (smallest interval containing both) of two intervals.
133 ///
134 /// Always returns a valid `U8CO`.
135 #[inline]
136 pub const fn convex_hull(self, other: Self) -> Self {
137 let start = if self.start <= other.start {
138 self.start
139 } else {
140 other.start
141 };
142
143 let end_excl = if self.end_excl >= other.end_excl {
144 self.end_excl
145 } else {
146 other.end_excl
147 };
148
149 Self { start, end_excl }
150 }
151
152 /// Returns the interval strictly between two intervals.
153 ///
154 /// If the intervals are contiguous or overlap, returns `None`.
155 #[inline]
156 pub const fn between(self, other: Self) -> Option<Self> {
157 let (left, right) = if self.start <= other.start {
158 (self, other)
159 } else {
160 (other, self)
161 };
162
163 Self::try_new(left.end_excl, right.start)
164 }
165
166 /// Returns the union of two intervals.
167 ///
168 /// - If intervals are contiguous or overlapping, returns `One` containing the merged interval.
169 /// - Otherwise, returns `Two` containing both intervals in ascending order.
170 #[inline]
171 pub const fn union(self, other: Self) -> OneTwo<Self> {
172 if self.is_contiguous_with(other) {
173 OneTwo::One(self.convex_hull(other))
174 } else if self.start <= other.start {
175 OneTwo::Two(self, other)
176 } else {
177 OneTwo::Two(other, self)
178 }
179 }
180
181 /// Returns the difference of two intervals (self - other).
182 ///
183 /// - If no overlap, returns `One(self)`.
184 /// - If partial overlap, returns `One` or `Two` depending on remaining segments.
185 /// - If fully contained, returns `Zero`.
186 #[inline]
187 pub const fn difference(self, other: Self) -> ZeroOneTwo<Self> {
188 match self.intersection(other) {
189 None => ZeroOneTwo::One(self),
190 Some(inter) => {
191 let left = Self::try_new(self.start, inter.start);
192 let right = Self::try_new(inter.end_excl, self.end_excl);
193
194 match (left, right) {
195 (None, None) => ZeroOneTwo::Zero,
196 (Some(x), None) | (None, Some(x)) => ZeroOneTwo::One(x),
197 (Some(x), Some(y)) => ZeroOneTwo::Two(x, y),
198 }
199 }
200 }
201 }
202
203 /// Returns the symmetric difference of two intervals.
204 ///
205 /// Equivalent to `(self - other) ∪ (other - self)`.
206 /// - If intervals do not overlap, returns `Two(self, other)` in order.
207 /// - If intervals partially overlap, returns remaining non-overlapping segments as `One` or `Two`.
208 #[inline]
209 pub const fn symmetric_difference(self, other: Self) -> ZeroOneTwo<Self> {
210 match self.intersection(other) {
211 None => {
212 if self.start <= other.start {
213 ZeroOneTwo::Two(self, other)
214 } else {
215 ZeroOneTwo::Two(other, self)
216 }
217 }
218 Some(inter) => {
219 let hull = self.convex_hull(other);
220 let left = Self::try_new(hull.start, inter.start);
221 let right = Self::try_new(inter.end_excl, hull.end_excl);
222
223 match (left, right) {
224 (None, None) => ZeroOneTwo::Zero,
225 (Some(x), None) | (None, Some(x)) => ZeroOneTwo::One(x),
226 (Some(x), Some(y)) => ZeroOneTwo::Two(x, y),
227 }
228 }
229 }
230 }
231 }
232}
233
234// ------------------------------------------------------------
235// Module: Minkowski arithmetic for U8CO
236// Provides checked and saturating Minkowski operations
237// ------------------------------------------------------------
238
239pub mod minkowski {
240 use super::*;
241
242 pub mod checked {
243 use super::*;
244
245 // --------------------------------------------------------
246 // Interval-to-interval Minkowski operations
247 // --------------------------------------------------------
248 impl U8CO {
249 /// Minkowski addition: [a_start, a_end) + [b_start, b_end)
250 #[inline]
251 pub const fn checked_minkowski_add(self, other: Self) -> Option<Self> {
252 let Some(start) = self.start.checked_add(other.start) else {
253 return None;
254 };
255 let Some(end_excl) = self.end_excl.checked_add(other.end_incl()) else {
256 return None;
257 };
258
259 // SAFETY:
260 // `checked_add` guarantees both bounds are computed without overflow.
261 // For half-open intervals, addition preserves ordering:
262 // self.start <= self.end_excl - 1 and other.start <= other.end_incl(),
263 // therefore `start < end_excl` still holds for the resulting interval.
264 Some(unsafe { Self::new_unchecked(start, end_excl) })
265 }
266
267 /// Minkowski subtraction: [a_start, a_end) - [b_start, b_end)
268 #[inline]
269 pub const fn checked_minkowski_sub(self, other: Self) -> Option<Self> {
270 let Some(start) = self.start.checked_sub(other.end_incl()) else {
271 return None;
272 };
273 let Some(end_excl) = self.end_excl.checked_sub(other.start) else {
274 return None;
275 };
276
277 // SAFETY:
278 // `checked_sub` guarantees both bounds are computed without underflow.
279 // Since `self.start <= self.end_excl - 1` and `other.start <= other.end_incl()`,
280 // we have `self.start - other.end_incl() < self.end_excl - other.start`,
281 // so the resulting half-open interval remains valid.
282 Some(unsafe { Self::new_unchecked(start, end_excl) })
283 }
284
285 /// Minkowski multiplication: [a_start, a_end) * [b_start, b_end)
286 #[inline]
287 pub const fn checked_minkowski_mul(self, other: Self) -> Option<Self> {
288 let Some(start) = self.start.checked_mul(other.start) else {
289 return None;
290 };
291 let Some(end_incl) = self.end_incl().checked_mul(other.end_incl()) else {
292 return None;
293 };
294 let Some(end_excl) = end_incl.checked_add(1) else {
295 return None;
296 };
297
298 // SAFETY:
299 // For `U8CO`, all values are non-negative, so endpoint-wise multiplication
300 // is monotone. `start` is the minimum product and `end_incl` is the maximum.
301 // `checked_add(1)` converts the inclusive upper bound back to half-open form,
302 // and overflow has already been excluded.
303 Some(unsafe { Self::new_unchecked(start, end_excl) })
304 }
305
306 /// Minkowski division: [a_start, a_end) / [b_start, b_end)
307 #[inline]
308 pub const fn checked_minkowski_div(self, other: Self) -> Option<Self> {
309 if other.start == 0 {
310 return None;
311 }
312
313 let Some(start) = self.start.checked_div(other.end_incl()) else {
314 return None;
315 };
316 let Some(end_incl) = self.end_incl().checked_div(other.start) else {
317 return None;
318 };
319 let Some(end_excl) = end_incl.checked_add(1) else {
320 return None;
321 };
322
323 // SAFETY:
324 // `other.start != 0` excludes division by zero, and all operands are unsigned.
325 // Over positive integers, division is monotone with respect to the dividend and
326 // anti-monotone with respect to the divisor, so:
327 // min = self.start / other.end_incl()
328 // max = self.end_incl() / other.start
329 // Thus `start <= end_incl`, and `end_excl = end_incl + 1` is checked.
330 Some(unsafe { Self::new_unchecked(start, end_excl) })
331 }
332 }
333
334 // --------------------------------------------------------
335 // Interval-to-scalar Minkowski operations
336 // --------------------------------------------------------
337 impl U8CO {
338 /// Add a scalar to an interval: [start, end) + n
339 #[inline]
340 pub const fn checked_minkowski_add_n(self, n: u8) -> Option<Self> {
341 let Some(start) = self.start.checked_add(n) else {
342 return None;
343 };
344 let Some(end_excl) = self.end_excl.checked_add(n) else {
345 return None;
346 };
347
348 // SAFETY:
349 // `checked_add` excludes overflow, and adding the same scalar to both bounds
350 // preserves the half-open interval ordering.
351 Some(unsafe { Self::new_unchecked(start, end_excl) })
352 }
353
354 /// Subtract a scalar from an interval: [start, end) - n
355 #[inline]
356 pub const fn checked_minkowski_sub_n(self, n: u8) -> Option<Self> {
357 let Some(start) = self.start.checked_sub(n) else {
358 return None;
359 };
360 let Some(end_excl) = self.end_excl.checked_sub(n) else {
361 return None;
362 };
363
364 // SAFETY:
365 // `checked_sub` excludes underflow, and subtracting the same scalar from both
366 // bounds preserves the half-open interval ordering.
367 Some(unsafe { Self::new_unchecked(start, end_excl) })
368 }
369
370 /// Multiply an interval by a scalar: [start, end) * n
371 #[inline]
372 pub const fn checked_minkowski_mul_n(self, n: u8) -> Option<Self> {
373 let Some(start) = self.start.checked_mul(n) else {
374 return None;
375 };
376 let Some(end_incl) = self.end_incl().checked_mul(n) else {
377 return None;
378 };
379 let Some(end_excl) = end_incl.checked_add(1) else {
380 return None;
381 };
382
383 // SAFETY:
384 // For unsigned integers, multiplication by a scalar is monotone.
385 // Therefore `start * n` is the lower bound and `end_incl * n` is the upper bound.
386 // `checked_*` operations exclude overflow, and `end_excl` restores half-open form.
387 Some(unsafe { Self::new_unchecked(start, end_excl) })
388 }
389
390 /// Divide an interval by a scalar: [start, end) / n
391 #[inline]
392 pub const fn checked_minkowski_div_n(self, n: u8) -> Option<Self> {
393 if n == 0 {
394 return None;
395 }
396
397 let start = self.start / n;
398 let end_incl = self.end_incl() / n;
399 let Some(end_excl) = end_incl.checked_add(1) else {
400 return None;
401 };
402
403 // SAFETY:
404 // `n != 0` excludes division by zero. For unsigned integers and positive scalar `n`,
405 // division is monotone, so `self.start / n <= self.end_incl() / n`.
406 // `checked_add(1)` safely converts the inclusive upper bound to half-open form.
407 Some(unsafe { Self::new_unchecked(start, end_excl) })
408 }
409 }
410 }
411
412 pub mod saturating {
413 use super::*;
414
415 // --------------------------------------------------------
416 // Interval-to-interval Minkowski operations
417 // --------------------------------------------------------
418 impl U8CO {
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 start = self.start.saturating_mul(other.start);
436 let end_incl = self.end_incl().saturating_mul(other.end_incl());
437 let end_excl = end_incl.saturating_add(1);
438 Self::try_new(start, end_excl)
439 }
440
441 #[inline]
442 pub const fn saturating_minkowski_div(self, other: Self) -> Option<Self> {
443 if other.start == 0 {
444 return None;
445 }
446
447 let start = self.start / other.end_incl();
448 let end_incl = self.end_incl() / other.start;
449 let end_excl = end_incl.saturating_add(1);
450 Self::try_new(start, end_excl)
451 }
452 }
453
454 // --------------------------------------------------------
455 // Interval-to-scalar Minkowski operations
456 // --------------------------------------------------------
457 impl U8CO {
458 /// Saturating add scalar: [start, end) + n
459 #[inline]
460 pub const fn saturating_minkowski_add_n(self, n: u8) -> Option<Self> {
461 let start = self.start.saturating_add(n);
462 let end_excl = self.end_excl.saturating_add(n);
463 Self::try_new(start, end_excl)
464 }
465
466 /// Saturating sub scalar: [start, end) - n
467 #[inline]
468 pub const fn saturating_minkowski_sub_n(self, n: u8) -> Option<Self> {
469 let start = self.start.saturating_sub(n);
470 let end_excl = self.end_excl.saturating_sub(n);
471 Self::try_new(start, end_excl)
472 }
473
474 /// Saturating mul scalar: [start, end) * n
475 #[inline]
476 pub const fn saturating_minkowski_mul_n(self, n: u8) -> Option<Self> {
477 let start = self.start.saturating_mul(n);
478 let end_incl = self.end_incl().saturating_mul(n);
479 let end_excl = end_incl.saturating_add(1);
480 Self::try_new(start, end_excl)
481 }
482
483 /// Saturating div scalar: [start, end) / n
484 #[inline]
485 pub const fn saturating_minkowski_div_n(self, n: u8) -> Option<Self> {
486 if n == 0 {
487 return None;
488 }
489
490 let start = self.start / n;
491 let end_incl = self.end_incl() / n;
492 let end_excl = end_incl.saturating_add(1);
493 Self::try_new(start, end_excl)
494 }
495 }
496 }
497}