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