int_interval/u8.rs
1use crate::res::{OneTwo, ZeroOneTwo};
2
3#[cfg(test)]
4mod tests_for_basic;
5#[cfg(test)]
6mod tests_for_between;
7#[cfg(test)]
8mod tests_for_checked_minkowski;
9#[cfg(test)]
10mod tests_for_convex_hull;
11#[cfg(test)]
12mod tests_for_difference;
13#[cfg(test)]
14mod tests_for_intersection;
15#[cfg(test)]
16mod tests_for_saturating_minkowski;
17#[cfg(test)]
18mod tests_for_symmetric_difference;
19#[cfg(test)]
20mod tests_for_union;
21
22#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
23pub struct U8CO {
24 start: u8,
25 end_excl: u8,
26}
27
28// ------------------------------------------------------------
29// low-level api: construction / accessors / predicates
30// ------------------------------------------------------------
31
32mod basic {
33
34 use super::*;
35
36 impl U8CO {
37 #[inline]
38 pub const fn try_new(start: u8, end_excl: u8) -> Option<Self> {
39 if start < end_excl {
40 Some(Self { start, end_excl })
41 } else {
42 None
43 }
44 }
45
46 #[inline]
47 pub const unsafe fn new_unchecked(start: u8, end_excl: u8) -> Self {
48 debug_assert!(start < end_excl);
49 Self { start, end_excl }
50 }
51
52 /// Construct a `U8CO` from a midpoint and a length.
53 ///
54 /// Returns `None` if the computed interval is invalid or overflows u8.
55 #[inline]
56 pub const fn checked_from_midpoint_len(mid: u8, len: u8) -> Option<Self> {
57 if len == 0 {
58 return None;
59 }
60 // Compute start = mid - floor(len/2)
61 let Some(start) = mid.checked_sub(len / 2) else {
62 return None;
63 };
64 // Compute end_excl = start + len, return None if overflow
65 let Some(end_excl) = start.checked_add(len) else {
66 return None;
67 };
68
69 // # Safety
70 // This function uses `unsafe { Self::new_unchecked(start, end_excl) }` internally.
71 // The safety is guaranteed by the following checks:
72 // 1. `mid.checked_sub(len / 2)` ensures `start` does not underflow `u8`.
73 // 2. `start.checked_add(len)` ensures `end_excl` does not overflow `u8`.
74 // 3. Because `len > 0`, we have `start < end_excl`.
75 // 4. Therefore, the half-open interval invariant `[start, end_excl)` is preserved.
76 Some(unsafe { Self::new_unchecked(start, end_excl) })
77 }
78
79 /// Saturating version: from midpoint + length, keeps start < end_excl
80 #[inline]
81 pub const fn saturating_from_midpoint_len(mid: u8, len: u8) -> Option<Self> {
82 if len == 0 {
83 return None;
84 }
85
86 let start = mid.saturating_sub(len / 2);
87 let end_excl = start.saturating_add(len);
88
89 // Use try_new to enforce start < end_excl invariant
90 Self::try_new(start, end_excl)
91 }
92 }
93
94 impl U8CO {
95 #[inline]
96 pub const fn start(self) -> u8 {
97 self.start
98 }
99
100 #[inline]
101 pub const fn end_excl(self) -> u8 {
102 self.end_excl
103 }
104
105 #[inline]
106 pub const fn end_incl(self) -> u8 {
107 // u8_low_bound =< start < end_excl
108 self.end_excl - 1
109 }
110 }
111
112 impl U8CO {
113 #[inline]
114 pub const fn len(self) -> u8 {
115 self.end_excl - self.start
116 }
117 }
118
119 impl U8CO {
120 /// Returns the midpoint of the interval (floor division).
121 #[inline]
122 pub const fn midpoint(self) -> u8 {
123 self.start + (self.len() / 2)
124 }
125 }
126
127 impl U8CO {
128 #[inline]
129 pub const fn contains(self, x: u8) -> bool {
130 self.start <= x && x < self.end_excl
131 }
132
133 #[inline]
134 pub const fn contains_interval(self, other: Self) -> bool {
135 self.start <= other.start && other.end_excl <= self.end_excl
136 }
137
138 #[inline]
139 pub const fn intersects(self, other: Self) -> bool {
140 !(self.end_excl <= other.start || other.end_excl <= self.start)
141 }
142
143 #[inline]
144 pub const fn is_adjacent(self, other: Self) -> bool {
145 self.end_excl == other.start || other.end_excl == self.start
146 }
147
148 #[inline]
149 pub const fn is_contiguous_with(self, other: Self) -> bool {
150 self.intersects(other) || self.is_adjacent(other)
151 }
152 }
153
154 impl U8CO {
155 #[inline]
156 pub const fn iter(self) -> core::ops::Range<u8> {
157 self.start..self.end_excl
158 }
159
160 #[inline]
161 pub const fn to_range(self) -> core::ops::Range<u8> {
162 self.start..self.end_excl
163 }
164 }
165}
166
167// ------------------------------------------------------------
168// interval algebra api: intersection / convex_hull / between / union / difference / symmetric_difference
169// ------------------------------------------------------------
170
171mod interval_algebra {
172
173 use super::*;
174
175 impl U8CO {
176 /// Returns the intersection of two intervals.
177 ///
178 /// If the intervals do not overlap, returns `None`.
179 #[inline]
180 pub const fn intersection(self, other: Self) -> Option<Self> {
181 let start = if self.start >= other.start {
182 self.start
183 } else {
184 other.start
185 };
186
187 let end_excl = if self.end_excl <= other.end_excl {
188 self.end_excl
189 } else {
190 other.end_excl
191 };
192
193 Self::try_new(start, end_excl)
194 }
195
196 /// Returns the convex hull (smallest interval containing both) of two intervals.
197 ///
198 /// Always returns a valid `U8CO`.
199 #[inline]
200 pub const fn convex_hull(self, other: Self) -> Self {
201 let start = if self.start <= other.start {
202 self.start
203 } else {
204 other.start
205 };
206
207 let end_excl = if self.end_excl >= other.end_excl {
208 self.end_excl
209 } else {
210 other.end_excl
211 };
212
213 Self { start, end_excl }
214 }
215
216 /// Returns the interval strictly between two intervals.
217 ///
218 /// If the intervals are contiguous or overlap, returns `None`.
219 #[inline]
220 pub const fn between(self, other: Self) -> Option<Self> {
221 let (left, right) = if self.start <= other.start {
222 (self, other)
223 } else {
224 (other, self)
225 };
226
227 Self::try_new(left.end_excl, right.start)
228 }
229
230 /// Returns the union of two intervals.
231 ///
232 /// - If intervals are contiguous or overlapping, returns `One` containing the merged interval.
233 /// - Otherwise, returns `Two` containing both intervals in ascending order.
234 #[inline]
235 pub const fn union(self, other: Self) -> OneTwo<Self> {
236 if self.is_contiguous_with(other) {
237 OneTwo::One(self.convex_hull(other))
238 } else if self.start <= other.start {
239 OneTwo::Two(self, other)
240 } else {
241 OneTwo::Two(other, self)
242 }
243 }
244
245 /// Returns the difference of two intervals (self - other).
246 ///
247 /// - If no overlap, returns `One(self)`.
248 /// - If partial overlap, returns `One` or `Two` depending on remaining segments.
249 /// - If fully contained, returns `Zero`.
250 #[inline]
251 pub const fn difference(self, other: Self) -> ZeroOneTwo<Self> {
252 match self.intersection(other) {
253 None => ZeroOneTwo::One(self),
254 Some(inter) => {
255 let left = Self::try_new(self.start, inter.start);
256 let right = Self::try_new(inter.end_excl, self.end_excl);
257
258 match (left, right) {
259 (None, None) => ZeroOneTwo::Zero,
260 (Some(x), None) | (None, Some(x)) => ZeroOneTwo::One(x),
261 (Some(x), Some(y)) => ZeroOneTwo::Two(x, y),
262 }
263 }
264 }
265 }
266
267 /// Returns the symmetric difference of two intervals.
268 ///
269 /// Equivalent to `(self - other) ∪ (other - self)`.
270 /// - If intervals do not overlap, returns `Two(self, other)` in order.
271 /// - If intervals partially overlap, returns remaining non-overlapping segments as `One` or `Two`.
272 #[inline]
273 pub const fn symmetric_difference(self, other: Self) -> ZeroOneTwo<Self> {
274 match self.intersection(other) {
275 None => {
276 if self.start <= other.start {
277 ZeroOneTwo::Two(self, other)
278 } else {
279 ZeroOneTwo::Two(other, self)
280 }
281 }
282 Some(inter) => {
283 let hull = self.convex_hull(other);
284 let left = Self::try_new(hull.start, inter.start);
285 let right = Self::try_new(inter.end_excl, hull.end_excl);
286
287 match (left, right) {
288 (None, None) => ZeroOneTwo::Zero,
289 (Some(x), None) | (None, Some(x)) => ZeroOneTwo::One(x),
290 (Some(x), Some(y)) => ZeroOneTwo::Two(x, y),
291 }
292 }
293 }
294 }
295 }
296}
297
298// ------------------------------------------------------------
299// Module: Minkowski arithmetic for U8CO
300// Provides checked and saturating Minkowski operations
301// ------------------------------------------------------------
302
303pub mod minkowski {
304 use super::*;
305
306 pub mod checked {
307 use super::*;
308
309 // --------------------------------------------------------
310 // Interval-to-interval Minkowski operations
311 // --------------------------------------------------------
312 impl U8CO {
313 /// Minkowski addition: [a_start, a_end) + [b_start, b_end)
314 #[inline]
315 pub const fn checked_minkowski_add(self, other: Self) -> Option<Self> {
316 let Some(start) = self.start.checked_add(other.start) else {
317 return None;
318 };
319 let Some(end_excl) = self.end_excl.checked_add(other.end_incl()) else {
320 return None;
321 };
322
323 // SAFETY:
324 // `checked_add` guarantees both bounds are computed without overflow.
325 // For half-open intervals, addition preserves ordering:
326 // self.start <= self.end_excl - 1 and other.start <= other.end_incl(),
327 // therefore `start < end_excl` still holds for the resulting interval.
328 Some(unsafe { Self::new_unchecked(start, end_excl) })
329 }
330
331 /// Minkowski subtraction: [a_start, a_end) - [b_start, b_end)
332 #[inline]
333 pub const fn checked_minkowski_sub(self, other: Self) -> Option<Self> {
334 let Some(start) = self.start.checked_sub(other.end_incl()) else {
335 return None;
336 };
337 let Some(end_excl) = self.end_excl.checked_sub(other.start) else {
338 return None;
339 };
340
341 // SAFETY:
342 // `checked_sub` guarantees both bounds are computed without underflow.
343 // Since `self.start <= self.end_excl - 1` and `other.start <= other.end_incl()`,
344 // we have `self.start - other.end_incl() < self.end_excl - other.start`,
345 // so the resulting half-open interval remains valid.
346 Some(unsafe { Self::new_unchecked(start, end_excl) })
347 }
348
349 /// Minkowski multiplication: [a_start, a_end) * [b_start, b_end)
350 #[inline]
351 pub const fn checked_minkowski_mul_hull(self, other: Self) -> Option<Self> {
352 let Some(start) = self.start.checked_mul(other.start) else {
353 return None;
354 };
355 let Some(end_incl) = self.end_incl().checked_mul(other.end_incl()) else {
356 return None;
357 };
358 let Some(end_excl) = end_incl.checked_add(1) else {
359 return None;
360 };
361
362 // SAFETY:
363 // For `U8CO`, all values are non-negative, so endpoint-wise multiplication
364 // is monotone. `start` is the minimum product and `end_incl` is the maximum.
365 // `checked_add(1)` converts the inclusive upper bound back to half-open form,
366 // and overflow has already been excluded.
367 Some(unsafe { Self::new_unchecked(start, end_excl) })
368 }
369
370 /// Minkowski division: [a_start, a_end) / [b_start, b_end)
371 #[inline]
372 pub const fn checked_minkowski_div_hull(self, other: Self) -> Option<Self> {
373 if other.start == 0 {
374 return None;
375 }
376
377 let Some(start) = self.start.checked_div(other.end_incl()) else {
378 return None;
379 };
380 let Some(end_incl) = self.end_incl().checked_div(other.start) else {
381 return None;
382 };
383 let Some(end_excl) = end_incl.checked_add(1) else {
384 return None;
385 };
386
387 // SAFETY:
388 // `other.start != 0` excludes division by zero, and all operands are unsigned.
389 // Over positive integers, division is monotone with respect to the dividend and
390 // anti-monotone with respect to the divisor, so:
391 // min = self.start / other.end_incl()
392 // max = self.end_incl() / other.start
393 // Thus `start <= end_incl`, and `end_excl = end_incl + 1` is checked.
394 Some(unsafe { Self::new_unchecked(start, end_excl) })
395 }
396 }
397
398 // --------------------------------------------------------
399 // Interval-to-scalar Minkowski operations
400 // --------------------------------------------------------
401 impl U8CO {
402 /// Add a scalar to an interval: [start, end) + n
403 #[inline]
404 pub const fn checked_minkowski_add_scalar(self, n: u8) -> Option<Self> {
405 let Some(start) = self.start.checked_add(n) else {
406 return None;
407 };
408 let Some(end_excl) = self.end_excl.checked_add(n) else {
409 return None;
410 };
411
412 // SAFETY:
413 // `checked_add` excludes overflow, and adding the same scalar to both bounds
414 // preserves the half-open interval ordering.
415 Some(unsafe { Self::new_unchecked(start, end_excl) })
416 }
417
418 /// Subtract a scalar from an interval: [start, end) - n
419 #[inline]
420 pub const fn checked_minkowski_sub_scalar(self, n: u8) -> Option<Self> {
421 let Some(start) = self.start.checked_sub(n) else {
422 return None;
423 };
424 let Some(end_excl) = self.end_excl.checked_sub(n) else {
425 return None;
426 };
427
428 // SAFETY:
429 // `checked_sub` excludes underflow, and subtracting the same scalar from both
430 // bounds preserves the half-open interval ordering.
431 Some(unsafe { Self::new_unchecked(start, end_excl) })
432 }
433
434 /// Multiply an interval by a scalar: [start, end) * n
435 #[inline]
436 pub const fn checked_minkowski_mul_scalar_hull(self, n: u8) -> Option<Self> {
437 let Some(start) = self.start.checked_mul(n) else {
438 return None;
439 };
440 let Some(end_incl) = self.end_incl().checked_mul(n) else {
441 return None;
442 };
443 let Some(end_excl) = end_incl.checked_add(1) else {
444 return None;
445 };
446
447 // SAFETY:
448 // For unsigned integers, multiplication by a scalar is monotone.
449 // Therefore `start * n` is the lower bound and `end_incl * n` is the upper bound.
450 // `checked_*` operations exclude overflow, and `end_excl` restores half-open form.
451 Some(unsafe { Self::new_unchecked(start, end_excl) })
452 }
453
454 /// Divide an interval by a scalar: [start, end) / n
455 #[inline]
456 pub const fn checked_minkowski_div_scalar_hull(self, n: u8) -> Option<Self> {
457 if n == 0 {
458 return None;
459 }
460
461 let start = self.start / n;
462 let end_incl = self.end_incl() / n;
463 let Some(end_excl) = end_incl.checked_add(1) else {
464 return None;
465 };
466
467 // SAFETY:
468 // `n != 0` excludes division by zero. For unsigned integers and positive scalar `n`,
469 // division is monotone, so `self.start / n <= self.end_incl() / n`.
470 // `checked_add(1)` safely converts the inclusive upper bound to half-open form.
471 Some(unsafe { Self::new_unchecked(start, end_excl) })
472 }
473 }
474 }
475
476 pub mod saturating {
477 use super::*;
478
479 // --------------------------------------------------------
480 // Interval-to-interval Minkowski operations
481 // --------------------------------------------------------
482 impl U8CO {
483 #[inline]
484 pub const fn saturating_minkowski_add(self, other: Self) -> Option<Self> {
485 let start = self.start.saturating_add(other.start);
486 let end_excl = self.end_excl.saturating_add(other.end_incl());
487 Self::try_new(start, end_excl)
488 }
489
490 #[inline]
491 pub const fn saturating_minkowski_sub(self, other: Self) -> Option<Self> {
492 let start = self.start.saturating_sub(other.end_incl());
493 let end_excl = self.end_excl.saturating_sub(other.start);
494 Self::try_new(start, end_excl)
495 }
496
497 #[inline]
498 pub const fn saturating_minkowski_mul_hull(self, other: Self) -> Option<Self> {
499 let start = self.start.saturating_mul(other.start);
500 let end_incl = self.end_incl().saturating_mul(other.end_incl());
501 let end_excl = end_incl.saturating_add(1);
502 Self::try_new(start, end_excl)
503 }
504
505 #[inline]
506 pub const fn saturating_minkowski_div_hull(self, other: Self) -> Option<Self> {
507 if other.start == 0 {
508 return None;
509 }
510
511 let start = self.start / other.end_incl();
512 let end_incl = self.end_incl() / other.start;
513 let end_excl = end_incl.saturating_add(1);
514 Self::try_new(start, end_excl)
515 }
516 }
517
518 // --------------------------------------------------------
519 // Interval-to-scalar Minkowski operations
520 // --------------------------------------------------------
521 impl U8CO {
522 /// Saturating add scalar: [start, end) + n
523 #[inline]
524 pub const fn saturating_minkowski_add_scalar(self, n: u8) -> Option<Self> {
525 let start = self.start.saturating_add(n);
526 let end_excl = self.end_excl.saturating_add(n);
527 Self::try_new(start, end_excl)
528 }
529
530 /// Saturating sub scalar: [start, end) - n
531 #[inline]
532 pub const fn saturating_minkowski_sub_scalar(self, n: u8) -> Option<Self> {
533 let start = self.start.saturating_sub(n);
534 let end_excl = self.end_excl.saturating_sub(n);
535 Self::try_new(start, end_excl)
536 }
537
538 /// Saturating mul scalar: [start, end) * n
539 #[inline]
540 pub const fn saturating_minkowski_mul_scalar_hull(self, n: u8) -> Option<Self> {
541 let start = self.start.saturating_mul(n);
542 let end_incl = self.end_incl().saturating_mul(n);
543 let end_excl = end_incl.saturating_add(1);
544 Self::try_new(start, end_excl)
545 }
546
547 /// Saturating div scalar: [start, end) / n
548 #[inline]
549 pub const fn saturating_minkowski_div_scalar_hull(self, n: u8) -> Option<Self> {
550 if n == 0 {
551 return None;
552 }
553
554 let start = self.start / n;
555 let end_incl = self.end_incl() / n;
556 let end_excl = end_incl.saturating_add(1);
557 Self::try_new(start, end_excl)
558 }
559 }
560 }
561}
562
563crate::traits::impl_co_forwarding!(U8CO, u8, u8);