Skip to main content

arithmetic_nonmax/
lib.rs

1//! `NonMax` provides integer types that cannot be the maximum value of their underlying primitive type.
2//!
3//! # Memory Optimization
4//!
5//! The main benefit of `NonMax<T>` is that `Option<NonMax<T>>` has the same size as `T`.
6//! This is achieved through Rust's "niche optimization", where the bit pattern of the
7//! maximum value is used to represent `None`.
8//!
9//! | Primitive | `size_of::<T>()` | `size_of::<Option<T>>()` | `size_of::<Option<NonMax<T>>>()` |
10//! |-----------|------------------|--------------------------|---------------------------------|
11//! | `u32`     | 4                | 8                        | **4**                           |
12//! | `i32`     | 4                | 8                        | **4**                           |
13//! | `u8`      | 1                | 2                        | **1**                           |
14
15#![no_std]
16
17use core::convert::TryFrom;
18use core::fmt::{self, Binary, Display, LowerHex, Octal, UpperHex};
19use core::hash::Hash;
20use core::marker::PhantomData;
21use core::num::NonZero;
22use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, RemAssign, Sub, SubAssign};
23
24/// Error type returned when a value is the maximum for its type.
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub struct MaxValueError;
27
28impl Display for MaxValueError {
29    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30        write!(f, "provided value is the maximum value for this type")
31    }
32}
33
34impl core::error::Error for MaxValueError {}
35
36/// A wrapper type for an integer that cannot be its maximum value.
37///
38/// This type leverages Rust's `NonZero` optimization by mapping the maximum value to zero internally.
39/// As a result, `Option<NonMax<T>>` has the same size as the underlying primitive type `T`.
40///
41/// # Examples
42/// ```
43/// # use arithmetic_nonmax::NonMaxU32;
44/// # use core::mem::size_of;
45/// assert_eq!(size_of::<NonMaxU32>(), 4);
46/// assert_eq!(size_of::<Option<NonMaxU32>>(), 4);
47/// ```
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
49pub struct NonMax<T: NonMaxItem>(T::NonZero);
50
51impl<T: NonMaxItem + Copy> NonMax<T> {
52    /// Creates a new `NonMax` if the given value is not the maximum value.
53    ///
54    /// # Examples
55    /// ```
56    /// # use arithmetic_nonmax::NonMaxU8;
57    /// assert!(NonMaxU8::new(254).is_some());
58    /// assert!(NonMaxU8::new(255).is_none());
59    /// ```
60    pub fn new(value: T) -> Option<Self> {
61        Value::new(value).to_inner_repr().to_nonmax()
62    }
63
64    fn to_real_repr(self) -> Value<T, Real> {
65        T::from_nonzero(self.0).to_real_repr()
66    }
67
68    /// Returns the underlying primitive value.
69    ///
70    /// # Examples
71    /// ```
72    /// # use arithmetic_nonmax::NonMaxU32;
73    /// let x = NonMaxU32::new(123).unwrap();
74    /// assert_eq!(x.get(), 123);
75    /// ```
76    pub fn get(&self) -> T {
77        self.to_real_repr().value()
78    }
79}
80
81impl<T: NonMaxItem + Copy + PartialEq> NonMax<T> {
82    /// Returns `true` if this is the minimum value.
83    pub fn is_min(&self) -> bool {
84        self.get() == T::MIN_VALUE
85    }
86
87    /// Returns `true` if this is the maximum possible value for this type.
88    pub fn is_max(&self) -> bool {
89        self.get() == T::MAX_SAFE
90    }
91
92    /// Returns `true` if the value is zero.
93    pub fn is_zero(&self) -> bool {
94        self.get() == T::ZERO_VALUE
95    }
96}
97
98impl<T: NonMaxItem + Copy> NonMax<T> {
99    /// Checked integer addition. Computes `self + rhs`, returning `None` if overflow occurred
100    /// or if the result is the maximum value.
101    pub fn checked_add(self, rhs: Self) -> Option<Self> {
102        self.get().checked_add(rhs.get()).and_then(Self::new)
103    }
104
105    /// Checked integer subtraction. Computes `self - rhs`, returning `None` if overflow occurred
106    /// or if the result is the maximum value.
107    pub fn checked_sub(self, rhs: Self) -> Option<Self> {
108        self.get().checked_sub(rhs.get()).and_then(Self::new)
109    }
110
111    /// Checked integer multiplication. Computes `self * rhs`, returning `None` if overflow occurred
112    /// or if the result is the maximum value.
113    pub fn checked_mul(self, rhs: Self) -> Option<Self> {
114        self.get().checked_mul(rhs.get()).and_then(Self::new)
115    }
116
117    /// Checked integer division. Computes `self / rhs`, returning `None` if the divisor is zero
118    /// or if the result is the maximum value.
119    pub fn checked_div(self, rhs: Self) -> Option<Self> {
120        self.get().checked_div(rhs.get()).and_then(Self::new)
121    }
122
123    /// Checked integer remainder. Computes `self % rhs`, returning `None` if the divisor is zero
124    /// or if the result is the maximum value.
125    pub fn checked_rem(self, rhs: Self) -> Option<Self> {
126        self.get().checked_rem(rhs.get()).and_then(Self::new)
127    }
128
129    /// Checked addition with a primitive value.
130    ///
131    /// # Examples
132    /// ```
133    /// # use arithmetic_nonmax::NonMaxU8;
134    /// let x = NonMaxU8::new(100).unwrap();
135    /// assert_eq!(x.checked_add_val(50).unwrap().get(), 150);
136    /// assert!(x.checked_add_val(155).is_none()); // 255 is MAX
137    /// ```
138    pub fn checked_add_val(self, rhs: T) -> Option<Self> {
139        self.get().checked_add(rhs).and_then(Self::new)
140    }
141
142    /// Checked subtraction with a primitive value.
143    pub fn checked_sub_val(self, rhs: T) -> Option<Self> {
144        self.get().checked_sub(rhs).and_then(Self::new)
145    }
146
147    /// Checked multiplication with a primitive value.
148    pub fn checked_mul_val(self, rhs: T) -> Option<Self> {
149        self.get().checked_mul(rhs).and_then(Self::new)
150    }
151
152    /// Checked division with a primitive value.
153    pub fn checked_div_val(self, rhs: T) -> Option<Self> {
154        self.get().checked_div(rhs).and_then(Self::new)
155    }
156
157    /// Checked remainder with a primitive value.
158    pub fn checked_rem_val(self, rhs: T) -> Option<Self> {
159        self.get().checked_rem(rhs).and_then(Self::new)
160    }
161}
162
163impl<T: NonMaxItem + Copy + Add<Output = T>> Add for NonMax<T> {
164    type Output = Self;
165    fn add(self, rhs: Self) -> Self::Output {
166        self.checked_add(rhs)
167            .expect("attempt to add with overflow or to maximum value")
168    }
169}
170
171impl<T: NonMaxItem + Copy + Add<Output = T>> Add<T> for NonMax<T> {
172    type Output = Self;
173    fn add(self, rhs: T) -> Self::Output {
174        self.checked_add_val(rhs)
175            .expect("attempt to add with overflow or to maximum value")
176    }
177}
178
179impl<T: NonMaxItem + Copy + Add<Output = T>> AddAssign for NonMax<T> {
180    fn add_assign(&mut self, rhs: Self) {
181        *self = *self + rhs;
182    }
183}
184
185impl<T: NonMaxItem + Copy + Add<Output = T>> AddAssign<T> for NonMax<T> {
186    fn add_assign(&mut self, rhs: T) {
187        *self = *self + rhs;
188    }
189}
190
191impl<T: NonMaxItem + Copy + Sub<Output = T>> Sub for NonMax<T> {
192    type Output = Self;
193    fn sub(self, rhs: Self) -> Self::Output {
194        self.checked_sub(rhs)
195            .expect("attempt to subtract with overflow or to maximum value")
196    }
197}
198
199impl<T: NonMaxItem + Copy + Sub<Output = T>> Sub<T> for NonMax<T> {
200    type Output = Self;
201    fn sub(self, rhs: T) -> Self::Output {
202        self.checked_sub_val(rhs)
203            .expect("attempt to subtract with overflow or to maximum value")
204    }
205}
206
207impl<T: NonMaxItem + Copy + Sub<Output = T>> SubAssign for NonMax<T> {
208    fn sub_assign(&mut self, rhs: Self) {
209        *self = *self - rhs;
210    }
211}
212
213impl<T: NonMaxItem + Copy + Sub<Output = T>> SubAssign<T> for NonMax<T> {
214    fn sub_assign(&mut self, rhs: T) {
215        *self = *self - rhs;
216    }
217}
218
219impl<T: NonMaxItem + Copy + Mul<Output = T>> Mul for NonMax<T> {
220    type Output = Self;
221    fn mul(self, rhs: Self) -> Self::Output {
222        self.checked_mul(rhs)
223            .expect("attempt to multiply with overflow or to maximum value")
224    }
225}
226
227impl<T: NonMaxItem + Copy + Mul<Output = T>> Mul<T> for NonMax<T> {
228    type Output = Self;
229    fn mul(self, rhs: T) -> Self::Output {
230        self.checked_mul_val(rhs)
231            .expect("attempt to multiply with overflow or to maximum value")
232    }
233}
234
235impl<T: NonMaxItem + Copy + Mul<Output = T>> MulAssign for NonMax<T> {
236    fn mul_assign(&mut self, rhs: Self) {
237        *self = *self * rhs;
238    }
239}
240
241impl<T: NonMaxItem + Copy + Mul<Output = T>> MulAssign<T> for NonMax<T> {
242    fn mul_assign(&mut self, rhs: T) {
243        *self = *self * rhs;
244    }
245}
246
247impl<T: NonMaxItem + Copy + Div<Output = T>> Div for NonMax<T> {
248    type Output = Self;
249    fn div(self, rhs: Self) -> Self::Output {
250        self.checked_div(rhs)
251            .expect("attempt to divide by zero or to maximum value")
252    }
253}
254
255impl<T: NonMaxItem + Copy + Div<T, Output = T>> Div<T> for NonMax<T> {
256    type Output = Self;
257    fn div(self, rhs: T) -> Self::Output {
258        self.checked_div_val(rhs)
259            .expect("attempt to divide by zero or to maximum value")
260    }
261}
262
263impl<T: NonMaxItem + Copy + Div<Output = T>> DivAssign for NonMax<T> {
264    fn div_assign(&mut self, rhs: Self) {
265        *self = *self / rhs;
266    }
267}
268
269impl<T: NonMaxItem + Copy + Div<T, Output = T>> DivAssign<T> for NonMax<T> {
270    fn div_assign(&mut self, rhs: T) {
271        *self = *self / rhs;
272    }
273}
274
275impl<T: NonMaxItem + Copy + Rem<Output = T>> Rem for NonMax<T> {
276    type Output = Self;
277    fn rem(self, rhs: Self) -> Self::Output {
278        self.checked_rem(rhs)
279            .expect("attempt to calculate remainder by zero or to maximum value")
280    }
281}
282
283impl<T: NonMaxItem + Copy + Rem<T, Output = T>> Rem<T> for NonMax<T> {
284    type Output = Self;
285    fn rem(self, rhs: T) -> Self::Output {
286        self.checked_rem_val(rhs)
287            .expect("attempt to calculate remainder by zero or to maximum value")
288    }
289}
290
291impl<T: NonMaxItem + Copy + Rem<Output = T>> RemAssign for NonMax<T> {
292    fn rem_assign(&mut self, rhs: Self) {
293        *self = *self % rhs;
294    }
295}
296
297impl<T: NonMaxItem + Copy + Rem<T, Output = T>> RemAssign<T> for NonMax<T> {
298    fn rem_assign(&mut self, rhs: T) {
299        *self = *self % rhs;
300    }
301}
302
303impl<T: NonMaxItem + Copy + PartialOrd> PartialOrd for NonMax<T> {
304    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
305        self.to_real_repr().partial_cmp(&other.to_real_repr())
306    }
307}
308
309impl<T: NonMaxItem + Copy + Ord> Ord for NonMax<T> {
310    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
311        self.to_real_repr().cmp(&other.to_real_repr())
312    }
313}
314
315impl<T: NonMaxItem + Copy + Display> Display for NonMax<T> {
316    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
317        Display::fmt(&self.get(), f)
318    }
319}
320
321impl<T: NonMaxItem + Copy + Binary> Binary for NonMax<T> {
322    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
323        Binary::fmt(&self.get(), f)
324    }
325}
326
327impl<T: NonMaxItem + Copy + Octal> Octal for NonMax<T> {
328    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
329        Octal::fmt(&self.get(), f)
330    }
331}
332
333impl<T: NonMaxItem + Copy + LowerHex> LowerHex for NonMax<T> {
334    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
335        LowerHex::fmt(&self.get(), f)
336    }
337}
338
339impl<T: NonMaxItem + Copy + UpperHex> UpperHex for NonMax<T> {
340    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
341        UpperHex::fmt(&self.get(), f)
342    }
343}
344
345impl<T: NonMaxItem + Copy> Default for NonMax<T> {
346    fn default() -> Self {
347        Self::new(T::ZERO_VALUE).unwrap()
348    }
349}
350
351#[doc(hidden)]
352pub trait NonMaxItem: Sized {
353    type NonZero: Copy + PartialEq + Eq + PartialOrd + Ord + Hash;
354    const MIN_VALUE: Self;
355    const MAX_SAFE: Self;
356    const ZERO_VALUE: Self;
357    fn transform(self) -> Self;
358    fn to_nonzero(value: Value<Self, Inner>) -> Option<Self::NonZero>;
359    unsafe fn to_nonzero_unchecked(value: Value<Self, Inner>) -> Self::NonZero;
360    fn from_nonzero(value: Self::NonZero) -> Value<Self, Inner>;
361
362    fn checked_add(self, rhs: Self) -> Option<Self>;
363    fn checked_sub(self, rhs: Self) -> Option<Self>;
364    fn checked_mul(self, rhs: Self) -> Option<Self>;
365    fn checked_div(self, rhs: Self) -> Option<Self>;
366    fn checked_rem(self, rhs: Self) -> Option<Self>;
367}
368
369macro_rules! impl_non_max_item {
370    ($($t:ty, $name:ident, $doc:expr),*) => {
371        $(
372            impl NonMaxItem for $t {
373                type NonZero = NonZero<$t>;
374                const MIN_VALUE: Self = <$t>::MIN;
375                const MAX_SAFE: Self = <$t>::MAX - 1;
376                const ZERO_VALUE: Self = 0;
377                fn transform(self) -> Self {
378                    self ^ <$t>::MAX
379                }
380                fn to_nonzero(value: Value<Self, Inner>) -> Option<Self::NonZero> {
381                    Self::NonZero::new(value.value())
382                }
383                unsafe fn to_nonzero_unchecked(value: Value<Self, Inner>) -> Self::NonZero {
384                    unsafe { Self::NonZero::new_unchecked(value.value()) }
385                }
386                fn from_nonzero(value: Self::NonZero) -> Value<Self, Inner> {
387                    Value::new(value.get())
388                }
389
390                fn checked_add(self, rhs: Self) -> Option<Self> { self.checked_add(rhs) }
391                fn checked_sub(self, rhs: Self) -> Option<Self> { self.checked_sub(rhs) }
392                fn checked_mul(self, rhs: Self) -> Option<Self> { self.checked_mul(rhs) }
393                fn checked_div(self, rhs: Self) -> Option<Self> { self.checked_div(rhs) }
394                fn checked_rem(self, rhs: Self) -> Option<Self> { self.checked_rem(rhs) }
395            }
396
397            impl From<NonMax<$t>> for $t {
398                fn from(value: NonMax<$t>) -> Self {
399                    value.get()
400                }
401            }
402
403            impl TryFrom<$t> for NonMax<$t> {
404                type Error = MaxValueError;
405
406                fn try_from(value: $t) -> Result<Self, Self::Error> {
407                    Self::new(value).ok_or(MaxValueError)
408                }
409            }
410
411            #[doc = $doc]
412            pub type $name = NonMax<$t>;
413
414            impl $name {
415                /// The minimum value for this type.
416                pub const MIN: Self = unsafe { Self(NonZero::new_unchecked(<$t>::MIN ^ <$t>::MAX)) };
417                /// The maximum value for this type.
418                pub const MAX: Self = unsafe { Self(NonZero::new_unchecked((<$t>::MAX - 1) ^ <$t>::MAX)) };
419                /// The zero value for this type.
420                pub const ZERO: Self = unsafe { Self(NonZero::new_unchecked(0 ^ <$t>::MAX)) };
421
422                /// Creates a new `NonMax` without checking the value.
423                ///
424                /// # Safety
425                /// The value must not be the maximum value of the underlying type.
426                pub const unsafe fn new_unchecked(value: $t) -> Self {
427                    Self(unsafe { NonZero::new_unchecked(value ^ <$t>::MAX) })
428                }
429            }
430        )*
431    };
432}
433
434impl_non_max_item!(
435    u8,
436    NonMaxU8,
437    "An unsigned 8-bit integer that cannot be `u8::MAX`.",
438    u16,
439    NonMaxU16,
440    "An unsigned 16-bit integer that cannot be `u16::MAX`.",
441    u32,
442    NonMaxU32,
443    "An unsigned 32-bit integer that cannot be `u32::MAX`.",
444    u64,
445    NonMaxU64,
446    "An unsigned 64-bit integer that cannot be `u64::MAX`.",
447    u128,
448    NonMaxU128,
449    "An unsigned 128-bit integer that cannot be `u128::MAX`.",
450    usize,
451    NonMaxUsize,
452    "An unsigned pointer-sized integer that cannot be `usize::MAX`.",
453    i8,
454    NonMaxI8,
455    "A signed 8-bit integer that cannot be `i8::MAX`.",
456    i16,
457    NonMaxI16,
458    "A signed 16-bit integer that cannot be `i16::MAX`.",
459    i32,
460    NonMaxI32,
461    "A signed 32-bit integer that cannot be `i32::MAX`.",
462    i64,
463    NonMaxI64,
464    "A signed 64-bit integer that cannot be `i64::MAX`.",
465    i128,
466    NonMaxI128,
467    "A signed 128-bit integer that cannot be `i128::MAX`.",
468    isize,
469    NonMaxIsize,
470    "A signed pointer-sized integer that cannot be `isize::MAX`."
471);
472
473#[doc(hidden)]
474#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
475pub struct Real;
476#[doc(hidden)]
477#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
478pub struct Inner;
479
480#[doc(hidden)]
481#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
482pub struct Value<T, M> {
483    value: T,
484    _marker: PhantomData<M>,
485}
486
487impl<T: NonMaxItem + Copy, M> Value<T, M> {
488    fn new(value: T) -> Self {
489        Self {
490            value,
491            _marker: PhantomData,
492        }
493    }
494
495    fn value(&self) -> T {
496        self.value
497    }
498}
499
500impl<T: NonMaxItem + Copy> Value<T, Real> {
501    fn to_inner_repr(self) -> Value<T, Inner> {
502        Value::new(T::transform(self.value))
503    }
504}
505
506impl<T: NonMaxItem + Copy + Add<Output = T>> Add for Value<T, Real> {
507    type Output = Self;
508    fn add(self, rhs: Self) -> Self::Output {
509        Self::new(self.value() + rhs.value())
510    }
511}
512
513impl<T: NonMaxItem + Copy + Sub<Output = T>> Sub for Value<T, Real> {
514    type Output = Self;
515    fn sub(self, rhs: Self) -> Self::Output {
516        Self::new(self.value() - rhs.value())
517    }
518}
519
520impl<T: NonMaxItem + Copy + Mul<Output = T>> Mul for Value<T, Real> {
521    type Output = Self;
522    fn mul(self, rhs: Self) -> Self::Output {
523        Self::new(self.value() * rhs.value())
524    }
525}
526
527impl<T: NonMaxItem + Copy + Div<Output = T>> Div for Value<T, Real> {
528    type Output = Self;
529    fn div(self, rhs: Self) -> Self::Output {
530        Self::new(self.value() / rhs.value())
531    }
532}
533
534impl<T: NonMaxItem + Copy + Rem<Output = T>> Rem for Value<T, Real> {
535    type Output = Self;
536    fn rem(self, rhs: Self) -> Self::Output {
537        Self::new(self.value() % rhs.value())
538    }
539}
540
541impl<T: NonMaxItem + Copy> Value<T, Inner> {
542    fn to_real_repr(self) -> Value<T, Real> {
543        Value::new(T::transform(self.value))
544    }
545
546    fn to_nonmax(self) -> Option<NonMax<T>> {
547        T::to_nonzero(self).map(NonMax)
548    }
549}
550
551impl<T: NonMaxItem + Copy + PartialOrd> PartialOrd for Value<T, Real> {
552    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
553        self.value().partial_cmp(&other.value())
554    }
555}
556
557impl<T: NonMaxItem + Copy + Ord> Ord for Value<T, Real> {
558    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
559        self.value().cmp(&other.value())
560    }
561}
562
563impl<T: NonMaxItem + Copy + Display> Display for Value<T, Real> {
564    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
565        write!(f, "{}", self.value())
566    }
567}
568
569#[cfg(test)]
570mod tests {
571    extern crate std;
572    use super::*;
573    use core::mem::size_of;
574    use std::collections::HashSet;
575
576    #[test]
577    fn test_hash() {
578        let mut set = HashSet::new();
579        set.insert(NonMaxU32::new(1).unwrap());
580        set.insert(NonMaxU32::new(2).unwrap());
581        set.insert(NonMaxU32::new(1).unwrap());
582
583        assert_eq!(set.len(), 2);
584        assert!(set.contains(&NonMaxU32::new(1).unwrap()));
585    }
586
587    #[test]
588    fn test_sizes() {
589        assert_eq!(size_of::<NonMaxU32>(), 4);
590        assert_eq!(size_of::<Option<NonMaxU32>>(), 4);
591
592        assert_eq!(size_of::<NonMaxI32>(), 4);
593        assert_eq!(size_of::<Option<NonMaxI32>>(), 4);
594
595        assert_eq!(size_of::<NonMaxU8>(), 1);
596        assert_eq!(size_of::<Option<NonMaxU8>>(), 1);
597    }
598
599    #[test]
600    fn test_conversions() {
601        let x = NonMaxU8::try_from(100).unwrap();
602        assert_eq!(u8::from(x), 100);
603
604        let max_val = u8::MAX;
605        assert!(NonMaxU8::try_from(max_val).is_err());
606    }
607
608    #[test]
609    fn test_arithmetic_with_val() {
610        let x = NonMaxU8::new(100).unwrap();
611        let y = x + 50;
612        assert_eq!(u8::from(y), 150);
613
614        let mut z = NonMaxU8::new(10).unwrap();
615        z += 20;
616        assert_eq!(u8::from(z), 30);
617
618        let a = NonMaxU8::new(10).unwrap();
619        let b = a * 5;
620        assert_eq!(u8::from(b), 50);
621
622        let c = NonMaxU8::new(100).unwrap();
623        let d = c / 3;
624        assert_eq!(u8::from(d), 33);
625    }
626
627    #[test]
628    fn test_add_overflow() {
629        let x = NonMaxU8::try_from(250).unwrap();
630        // Now it should return None instead of panicking
631        assert!(x.checked_add_val(10).is_none());
632    }
633
634    #[test]
635    fn test_add_to_max() {
636        let x = NonMaxU8::try_from(250).unwrap();
637        // Result is 255 (MAX), so it should return None
638        assert!(x.checked_add_val(5).is_none());
639    }
640
641    #[test]
642    fn test_signed_integer() {
643        // i8: -128 to 127. MAX is 127.
644        let x = NonMaxI8::try_from(100).unwrap();
645        let y = x + 20;
646        assert_eq!(i8::from(y), 120);
647
648        let z = NonMaxI8::try_from(-50).unwrap();
649        let w = z + 10;
650        assert_eq!(i8::from(w), -40);
651
652        // MIN (-128) is allowed
653        let min_val = NonMaxI8::try_from(i8::MIN).unwrap();
654        assert_eq!(i8::from(min_val), -128);
655    }
656
657    #[test]
658    fn test_signed_overflow() {
659        let x = NonMaxI8::try_from(120).unwrap();
660        // Overflow detected
661        assert!(x.checked_add_val(10).is_none());
662    }
663
664    #[test]
665    fn test_signed_to_max() {
666        let x = NonMaxI8::try_from(120).unwrap();
667        // Result is 127 (MAX), so None
668        assert!(x.checked_add_val(7).is_none());
669    }
670
671    #[test]
672    fn test_formatting() {
673        let x = NonMaxU8::new(254).unwrap();
674        assert_eq!(std::format!("{}", x), "254");
675        assert_eq!(std::format!("{:b}", x), "11111110");
676        assert_eq!(std::format!("{:o}", x), "376");
677        assert_eq!(std::format!("{:x}", x), "fe");
678        assert_eq!(std::format!("{:X}", x), "FE");
679    }
680
681    #[test]
682    fn test_min_max_constants() {
683        assert_eq!(NonMaxU8::MIN.get(), 0);
684        assert_eq!(NonMaxU8::MAX.get(), 254);
685        assert!(NonMaxU8::MIN.is_min());
686        assert!(NonMaxU8::MAX.is_max());
687        assert!(!NonMaxU8::MIN.is_max());
688        assert!(!NonMaxU8::MAX.is_min());
689
690        assert_eq!(NonMaxI8::MIN.get(), -128);
691        assert_eq!(NonMaxI8::MAX.get(), 126);
692    }
693
694    #[test]
695    fn test_zero_constant() {
696        assert_eq!(NonMaxU8::ZERO.get(), 0);
697        assert!(NonMaxU8::ZERO.is_zero());
698        assert_eq!(NonMaxI32::ZERO.get(), 0);
699        assert!(NonMaxI32::ZERO.is_zero());
700    }
701}