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