mz_ore/
overflowing.rs

1// Copyright 2019 The Rust Project Contributors
2// Copyright Materialize, Inc. and contributors. All rights reserved.
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License in the LICENSE file at the
7// root of this repository, or online at
8//
9//     http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17//! Overflowing number types.
18
19#[cfg(feature = "proptest")]
20use proptest_derive::Arbitrary;
21use serde::{Deserialize, Serialize};
22use std::ops::{Add, AddAssign, Div, Mul, Neg, Rem, Sub, SubAssign};
23
24/// Overflowing number. Operations panic on overflow, even in release mode.
25///
26/// The `ore_overflowing_behavior` feature flag can be used to control the
27/// overflow behavior:
28/// * `panic`: panic on overflow (default when debug assertions are enabled).
29/// * `soft_panic`: log a warning on overflow, or panic, depending on whether
30///   soft assertions are enbaled.
31/// * `ignore`: ignore overflow (default when debug assertions are disabled).
32/// The default value is `panic` when `debug_assertions` are enabled, or `ignore` otherwise.
33///
34/// The non-aborting modes simply return the result of the operation, which can
35/// include overflows.
36#[derive(Debug, Default, Ord, PartialOrd, Eq, PartialEq, Copy, Clone, Serialize, Deserialize)]
37#[cfg_attr(feature = "proptest", derive(Arbitrary))]
38pub struct Overflowing<T>(T);
39
40/// The behavior of the [`Overflowing`] type when an overflow occurs.
41#[derive(Debug)]
42pub enum OverflowingBehavior {
43    /// Panic on overflow. Corresponds to the `panic` string.
44    Panic,
45    /// Soft panic on overflow. Corresponds to the `soft_panic` string.
46    SoftPanic,
47    /// Ignore overflow. Corresponds to the `ignore` string.
48    Ignore,
49}
50
51impl std::str::FromStr for OverflowingBehavior {
52    type Err = String;
53
54    fn from_str(s: &str) -> Result<Self, Self::Err> {
55        match s {
56            _ if s.eq_ignore_ascii_case("panic") => Ok(OverflowingBehavior::Panic),
57            _ if s.eq_ignore_ascii_case("soft_panic") => Ok(OverflowingBehavior::SoftPanic),
58            _ if s.eq_ignore_ascii_case("ignore") => Ok(OverflowingBehavior::Ignore),
59            _ => Err(format!("Invalid OverflowingBehavior: {s}")),
60        }
61    }
62}
63
64/// Set the overflowing behavior for the process.
65///
66/// This function is thread-safe and can be used to change the behavior at runtime.
67///
68/// The default behavior is to ignore overflows.
69pub fn set_behavior(behavior: OverflowingBehavior) {
70    overflowing_support::set_overflowing_mode(behavior);
71}
72
73impl<T> Overflowing<T> {
74    /// Returns the inner value.
75    pub fn into_inner(self) -> T {
76        self.0
77    }
78}
79
80impl<T: std::fmt::Display> std::fmt::Display for Overflowing<T> {
81    #[inline(always)]
82    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83        self.0.fmt(f)
84    }
85}
86
87#[cfg(feature = "columnar")]
88mod columnar {
89    use crate::overflowing::Overflowing;
90    use columnar::common::index::CopyAs;
91    use columnar::{AsBytes, Clear, Columnar, Container, FromBytes, Index, IndexAs, Len, Push};
92    use serde::{Deserialize, Serialize};
93
94    impl<T: Columnar + Copy + Send> Columnar for Overflowing<T>
95    where
96        Vec<T>: Container<T>,
97        Overflowing<T>: From<T>,
98        for<'a> <T as Columnar>::Ref<'a>: CopyAs<T>,
99    {
100        type Ref<'a> = Overflowing<T>;
101        #[inline(always)]
102        fn into_owned<'a>(other: Self::Ref<'a>) -> Self {
103            other
104        }
105        type Container = Overflows<T, Vec<T>>;
106    }
107
108    /// Columnar container for [`Overflowing`].
109    #[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
110    pub struct Overflows<T, TC>(TC, std::marker::PhantomData<T>);
111
112    impl<T, TC: Default> Default for Overflows<T, TC> {
113        #[inline(always)]
114        fn default() -> Self {
115            Self(TC::default(), std::marker::PhantomData)
116        }
117    }
118
119    impl<T: Columnar + Copy + Send, TC: Container<T>> Container<Overflowing<T>> for Overflows<T, TC>
120    where
121        Vec<T>: Container<T>,
122        Overflowing<T>: From<T>,
123        for<'a> <T as Columnar>::Ref<'a>: CopyAs<T>,
124    {
125        type Borrowed<'a>
126            = Overflows<T, TC::Borrowed<'a>>
127        where
128            Self: 'a;
129        #[inline(always)]
130        fn borrow<'a>(&'a self) -> Self::Borrowed<'a> {
131            Overflows(self.0.borrow(), std::marker::PhantomData)
132        }
133    }
134
135    impl<'a, T: Copy, TC: AsBytes<'a>> AsBytes<'a> for Overflows<T, TC> {
136        #[inline(always)]
137        fn as_bytes(&self) -> impl Iterator<Item = (u64, &'a [u8])> {
138            self.0.as_bytes()
139        }
140    }
141
142    impl<'a, T: Copy, TC: FromBytes<'a>> FromBytes<'a> for Overflows<T, TC> {
143        #[inline(always)]
144        fn from_bytes(bytes: &mut impl Iterator<Item = &'a [u8]>) -> Self {
145            Self(TC::from_bytes(bytes), std::marker::PhantomData)
146        }
147    }
148
149    impl<T: Copy, TC: Len> Len for Overflows<T, TC> {
150        #[inline(always)]
151        fn len(&self) -> usize {
152            self.0.len()
153        }
154    }
155
156    impl<T: Copy, TC: Clear> Clear for Overflows<T, TC> {
157        #[inline(always)]
158        fn clear(&mut self) {
159            self.0.clear();
160        }
161    }
162
163    impl<T: Copy, TC: IndexAs<T>> Index for Overflows<T, TC>
164    where
165        Overflowing<T>: From<T>,
166    {
167        type Ref = Overflowing<T>;
168        #[inline(always)]
169        fn get(&self, index: usize) -> Self::Ref {
170            self.0.index_as(index).into()
171        }
172    }
173
174    impl<T: Copy, TC: Push<T>> Push<Overflowing<T>> for Overflows<T, TC> {
175        #[inline(always)]
176        fn push(&mut self, item: Overflowing<T>) {
177            self.0.push(item.0);
178        }
179    }
180
181    impl<T: Copy, TC: Push<T>> Push<&Overflowing<T>> for Overflows<T, TC> {
182        #[inline(always)]
183        fn push(&mut self, item: &Overflowing<T>) {
184            self.0.push(item.0);
185        }
186    }
187}
188
189macro_rules! impl_overflowing {
190    ($t:ty) => {
191        impl Overflowing<$t> {
192            /// The value zero.
193            pub const ZERO: Self = Self(0);
194            /// The value one.
195            pub const ONE: Self = Self(1);
196            /// The minimum value.
197            pub const MIN: Self = Self(<$t>::MIN);
198            /// The maximum value.
199            pub const MAX: Self = Self(<$t>::MAX);
200
201            /// Checked addition. Returns `None` if overflow occurred.
202            #[inline(always)]
203            pub fn checked_add(self, rhs: Self) -> Option<Self> {
204                self.0.checked_add(rhs.0).map(Self)
205            }
206
207            /// Wrapping addition.
208            #[inline(always)]
209            pub fn wrapping_add(self, rhs: Self) -> Self {
210                Self(self.0.wrapping_add(rhs.0))
211            }
212
213            /// Checked multiplication. Returns `None` if overflow occurred.
214            #[inline(always)]
215            pub fn checked_mul(self, rhs: Self) -> Option<Self> {
216                self.0.checked_mul(rhs.0).map(Self)
217            }
218
219            /// Wrapping multiplication.
220            #[inline(always)]
221            pub fn wrapping_mul(self, rhs: Self) -> Self {
222                Self(self.0.wrapping_mul(rhs.0))
223            }
224
225            /// Returns `true` if the number is zero.
226            pub fn is_zero(self) -> bool {
227                self == Self::ZERO
228            }
229        }
230
231        impl Add<Self> for Overflowing<$t> {
232            type Output = Self;
233
234            #[inline(always)]
235            fn add(self, rhs: Self) -> Self::Output {
236                match self.0.overflowing_add(rhs.0) {
237                    (result, true) => {
238                        overflowing_support::handle_overflow(result, format_args!("{self} + {rhs}"))
239                    }
240                    (result, false) => Self(result),
241                }
242            }
243        }
244
245        impl<'a> Add<&'a Self> for Overflowing<$t> {
246            type Output = Self;
247
248            #[inline(always)]
249            fn add(self, rhs: &'a Self) -> Self::Output {
250                match self.0.overflowing_add(rhs.0) {
251                    (result, true) => {
252                        overflowing_support::handle_overflow(result, format_args!("{self} + {rhs}"))
253                    }
254                    (result, false) => Self(result),
255                }
256            }
257        }
258
259        impl AddAssign<Self> for Overflowing<$t> {
260            #[inline(always)]
261            fn add_assign(&mut self, rhs: Self) {
262                *self = *self + rhs;
263            }
264        }
265
266        impl AddAssign<&Self> for Overflowing<$t> {
267            #[inline(always)]
268            fn add_assign(&mut self, rhs: &Self) {
269                *self = *self + *rhs;
270            }
271        }
272
273        impl Div<Self> for Overflowing<$t> {
274            type Output = Overflowing<<$t as Div>::Output>;
275
276            #[inline(always)]
277            fn div(self, rhs: Self) -> Self::Output {
278                match self.0.overflowing_div(rhs.0) {
279                    (result, true) => {
280                        overflowing_support::handle_overflow(result, format_args!("{self} / {rhs}"))
281                    }
282                    (result, false) => Self(result),
283                }
284            }
285        }
286
287        impl Rem<Self> for Overflowing<$t> {
288            type Output = Overflowing<<$t as Rem>::Output>;
289
290            #[inline(always)]
291            fn rem(self, rhs: Self) -> Self::Output {
292                match self.0.overflowing_rem(rhs.0) {
293                    (result, true) => {
294                        overflowing_support::handle_overflow(result, format_args!("{self} % {rhs}"))
295                    }
296                    (result, false) => Self(result),
297                }
298            }
299        }
300
301        impl Sub<Self> for Overflowing<$t> {
302            type Output = Self;
303
304            #[inline(always)]
305            fn sub(self, rhs: Self) -> Self::Output {
306                match self.0.overflowing_sub(rhs.0) {
307                    (result, true) => {
308                        overflowing_support::handle_overflow(result, format_args!("{self} - {rhs}"))
309                    }
310                    (result, false) => Self(result),
311                }
312            }
313        }
314
315        impl<'a> Sub<&'a Self> for Overflowing<$t> {
316            type Output = Self;
317
318            #[inline(always)]
319            fn sub(self, rhs: &'a Self) -> Self::Output {
320                match self.0.overflowing_sub(rhs.0) {
321                    (result, true) => {
322                        overflowing_support::handle_overflow(result, format_args!("{self} - {rhs}"))
323                    }
324                    (result, false) => Self(result),
325                }
326            }
327        }
328
329        impl SubAssign<Self> for Overflowing<$t> {
330            #[inline(always)]
331            fn sub_assign(&mut self, rhs: Self) {
332                *self = *self - rhs;
333            }
334        }
335
336        impl SubAssign<&Self> for Overflowing<$t> {
337            #[inline(always)]
338            fn sub_assign(&mut self, rhs: &Self) {
339                *self = *self - *rhs;
340            }
341        }
342
343        impl std::iter::Sum<Overflowing<$t>> for Overflowing<$t> {
344            #[inline(always)]
345            fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
346                iter.fold(Self::ZERO, |a, b| a + b)
347            }
348        }
349
350        impl<'a> std::iter::Sum<&'a Overflowing<$t>> for Overflowing<$t> {
351            #[inline(always)]
352            fn sum<I: Iterator<Item = &'a Self>>(iter: I) -> Self {
353                iter.fold(Self::ZERO, |a, b| a + b)
354            }
355        }
356
357        impl Mul for Overflowing<$t> {
358            type Output = Self;
359
360            #[inline(always)]
361            fn mul(self, rhs: Self) -> Self::Output {
362                match self.0.overflowing_mul(rhs.0) {
363                    (result, true) => {
364                        overflowing_support::handle_overflow(result, format_args!("{self} * {rhs}"))
365                    }
366                    (result, false) => Self(result),
367                }
368            }
369        }
370
371        #[cfg(feature = "differential-dataflow")]
372        impl differential_dataflow::difference::IsZero for Overflowing<$t> {
373            #[inline(always)]
374            fn is_zero(&self) -> bool {
375                self.0.is_zero()
376            }
377        }
378
379        #[cfg(feature = "differential-dataflow")]
380        impl differential_dataflow::difference::Semigroup for Overflowing<$t> {
381            #[inline(always)]
382            fn plus_equals(&mut self, rhs: &Self) {
383                *self += *rhs
384            }
385        }
386
387        #[cfg(feature = "differential-dataflow")]
388        impl differential_dataflow::difference::Monoid for Overflowing<$t> {
389            #[inline(always)]
390            fn zero() -> Self {
391                Self::ZERO
392            }
393        }
394
395        #[cfg(feature = "differential-dataflow")]
396        impl differential_dataflow::difference::Multiply<Self> for Overflowing<$t> {
397            type Output = Self;
398            #[inline(always)]
399            fn multiply(self, rhs: &Self) -> Self::Output {
400                self * *rhs
401            }
402        }
403
404        #[cfg(feature = "columnation")]
405        impl columnation::Columnation for Overflowing<$t> {
406            type InnerRegion = columnation::CopyRegion<Self>;
407        }
408
409        impl std::str::FromStr for Overflowing<$t> {
410            type Err = <$t as std::str::FromStr>::Err;
411
412            #[inline(always)]
413            fn from_str(s: &str) -> Result<Self, Self::Err> {
414                <$t>::from_str(s).map(Self)
415            }
416        }
417
418        impl std::hash::Hash for Overflowing<$t> {
419            #[inline(always)]
420            fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
421                self.0.hash(state);
422            }
423        }
424
425        impl<T> crate::cast::CastFrom<T> for Overflowing<$t>
426        where
427            $t: crate::cast::CastFrom<T>,
428        {
429            #[inline(always)]
430            fn cast_from(value: T) -> Self {
431                Self(<$t>::cast_from(value))
432            }
433        }
434
435        #[cfg(feature = "num-traits")]
436        impl num_traits::identities::Zero for Overflowing<$t> {
437            #[inline(always)]
438            fn zero() -> Self {
439                Self::ZERO
440            }
441            #[inline(always)]
442            fn is_zero(&self) -> bool {
443                self.0.is_zero()
444            }
445        }
446
447        #[cfg(feature = "num-traits")]
448        impl num_traits::identities::One for Overflowing<$t> {
449            #[inline(always)]
450            fn one() -> Self {
451                Self::ONE
452            }
453        }
454
455        #[cfg(feature = "num-traits")]
456        impl num_traits::Num for Overflowing<$t> {
457            type FromStrRadixErr = <$t as num_traits::Num>::FromStrRadixErr;
458
459            #[inline(always)]
460            fn from_str_radix(str: &str, radix: u32) -> Result<Self, Self::FromStrRadixErr> {
461                <$t>::from_str_radix(str, radix).map(Self)
462            }
463        }
464    };
465}
466
467macro_rules! impl_overflowing_from {
468    ($t:ty, $($f:ty)+) => {
469        $(
470            impl From<$f> for Overflowing<$t> {
471                #[inline(always)]
472                fn from(value: $f) -> Self {
473                    Self(value.into())
474                }
475            }
476        )+
477    };
478}
479
480macro_rules! impl_overflowing_from_overflowing {
481    ($t:ty, $($f:ty)+) => {
482        $(
483            impl From<Overflowing<$f>> for Overflowing<$t> {
484                #[inline(always)]
485                fn from(value: Overflowing<$f>) -> Self {
486                    Self(value.0.into())
487                }
488            }
489        )+
490    };
491}
492
493macro_rules! impl_overflowing_try_from {
494    ($t:ty, $($f:ty)+) => {
495        $(
496            impl TryFrom<$f> for Overflowing<$t> {
497                type Error = <$t as TryFrom<$f>>::Error;
498                #[inline(always)]
499                fn try_from(value: $f) -> Result<Self, Self::Error> {
500                    <$t>::try_from(value).map(Self)
501                }
502            }
503
504            impl TryFrom<Overflowing<$f>> for Overflowing<$t> {
505                type Error = <$t as TryFrom<$f>>::Error;
506                #[inline(always)]
507                fn try_from(value: Overflowing<$f>) -> Result<Self, Self::Error> {
508                    <$t>::try_from(value.0).map(Self)
509                }
510            }
511        )+
512    };
513}
514
515// Implement Overflowing for signed types.
516macro_rules! impl_overflowing_signed {
517    ($t:ty, $u:ty) => {
518        impl Overflowing<$t> {
519            /// The value minus one.
520            pub const MINUS_ONE: Self = Self(-1);
521
522            /// Returns the absolute value of the number.
523            pub fn abs(self) -> Self {
524                Self(self.0.abs())
525            }
526
527            /// Returns the absolute value of the number as an unsigned integer.
528            #[inline(always)]
529            pub fn unsigned_abs(self) -> $u {
530                self.0.unsigned_abs()
531            }
532
533            /// Returns `true` if the number is positive and `false` if the number is zero
534            /// or negative.
535            ///
536            /// # Examples
537            ///
538            /// ```
539            /// # use mz_ore::Overflowing;
540            /// assert!(!Overflowing::<i64>::from(-10i32).is_positive());
541            /// assert!(Overflowing::<i64>::from(10i32).is_positive());
542            /// ```
543            pub fn is_positive(self) -> bool {
544                self > Self::ZERO
545            }
546
547            /// Returns `true` if the number is negative and `false` if the number is zero
548            /// or positive.
549            ///
550            /// # Examples
551            ///
552            /// ```
553            /// # use mz_ore::Overflowing;
554            /// assert!(Overflowing::<i64>::from(-10i32).is_negative());
555            /// assert!(!Overflowing::<i64>::from(10i32).is_negative());
556            /// ```
557            pub fn is_negative(self) -> bool {
558                self < Self::ZERO
559            }
560        }
561
562        impl Neg for Overflowing<$t> {
563            type Output = Overflowing<<$t as Neg>::Output>;
564
565            #[inline(always)]
566            fn neg(self) -> Self::Output {
567                match self.0.overflowing_neg() {
568                    (result, true) => {
569                        overflowing_support::handle_overflow(result, format_args!("-{self}"))
570                    }
571                    (result, false) => Self(result),
572                }
573            }
574        }
575
576        impl Neg for &Overflowing<$t> {
577            type Output = Overflowing<<$t as Neg>::Output>;
578
579            #[inline(always)]
580            fn neg(self) -> Self::Output {
581                match self.0.overflowing_neg() {
582                    (result, true) => {
583                        overflowing_support::handle_overflow(result, format_args!("-{self}"))
584                    }
585                    (result, false) => Overflowing(result),
586                }
587            }
588        }
589
590        #[cfg(feature = "differential-dataflow")]
591        impl differential_dataflow::difference::Abelian for Overflowing<$t> {
592            #[inline(always)]
593            fn negate(&mut self) {
594                *self = -*self
595            }
596        }
597
598        #[cfg(feature = "num-traits")]
599        impl num_traits::sign::Signed for Overflowing<$t> {
600            #[inline(always)]
601            fn abs(&self) -> Self {
602                Self(self.0.abs())
603            }
604            #[inline(always)]
605            fn abs_sub(&self, other: &Self) -> Self {
606                Self(self.0.abs_sub(&other.0))
607            }
608            #[inline(always)]
609            fn signum(&self) -> Self {
610                Self(self.0.signum())
611            }
612            #[inline(always)]
613            fn is_positive(&self) -> bool {
614                self.0.is_positive()
615            }
616            #[inline(always)]
617            fn is_negative(&self) -> bool {
618                self.0.is_negative()
619            }
620        }
621    };
622}
623
624macro_rules! overflowing {
625    ($t:ty, $($fit:ty)+, $($may_fit:ty)+ $(, $unsigned:ty)?) => {
626        impl_overflowing!($t);
627        impl_overflowing_from!($t, $($fit)+ $t);
628        impl_overflowing_from_overflowing!($t, $($fit)+);
629        impl_overflowing_try_from!($t, $($may_fit)+);
630        $( impl_overflowing_signed!($t, $unsigned); )?
631    };
632}
633
634// type, types that certainly fit, types that may fit, optional corresponding unsigned type
635overflowing!(u8, bool, u16 u32 u64 u128 i8 i16 i32 i64 i128 isize usize);
636overflowing!(u16, bool u8, u32 u64 u128 i8 i16 i32 i64 i128 isize usize);
637overflowing!(u32, bool u8 u16, u64 u128 i8 i16 i32 i64 i128 isize usize);
638overflowing!(u64, bool u8 u16 u32, u128 i8 i16 i32 i64 i128 isize usize);
639overflowing!(u128, bool u8 u16 u32 u64, i8 i16 i32 i64 i128 isize usize);
640
641overflowing!(i8, bool, u8 i16 u16 i32 u32 i64 u64 i128 u128 isize usize, u8);
642overflowing!(i16, bool i8 u8, u16 i32 u32 i64 u64 i128 u128 isize usize, u16);
643overflowing!(i32, bool i8 u8 i16 u16, u32 i64 u64 i128 u128 isize usize, u32);
644overflowing!(i64, bool i8 u8 i16 u16 i32 u32, u64 i128 u128 isize usize, u64);
645overflowing!(i128, bool i8 u8 i16 u16 i32 u32 i64 u64, u128 isize usize, u128);
646
647mod overflowing_support {
648    use std::sync::atomic::AtomicUsize;
649
650    use crate::overflowing::OverflowingBehavior;
651
652    /// Ignore overflow.
653    const MODE_IGNORE: usize = 0;
654    /// Soft assert on overflow.
655    const MODE_SOFT_PANIC: usize = 1;
656    /// Panic on overflow.
657    const MODE_PANIC: usize = 2;
658
659    static OVERFLOWING_MODE: AtomicUsize = AtomicUsize::new(MODE_IGNORE);
660
661    /// Handles overflow for [`Overflowing`](super::Overflowing) numbers.
662    #[track_caller]
663    #[cold]
664    pub(super) fn handle_overflow<T: Into<O>, O>(result: T, description: std::fmt::Arguments) -> O {
665        let mode = OVERFLOWING_MODE.load(std::sync::atomic::Ordering::Relaxed);
666        match mode {
667            #[cfg(not(target_arch = "wasm32"))]
668            MODE_SOFT_PANIC => crate::soft_panic_or_log!("Overflow: {description}"),
669            // We cannot use the logging `soft_panic_or_log` in wasm, so we panic instead (soft
670            // assertions are always enabled in wasm).
671            #[cfg(target_arch = "wasm32")]
672            MODE_SOFT_PANIC => panic!("Overflow: {description}"),
673            MODE_PANIC => panic!("Overflow: {description}"),
674            // MODE_IGNORE and all other (impossible) values
675            _ => {}
676        }
677        result.into()
678    }
679
680    /// Set the overflowing mode.
681    pub(crate) fn set_overflowing_mode(behavior: OverflowingBehavior) {
682        let value = match behavior {
683            OverflowingBehavior::Panic => MODE_PANIC,
684            OverflowingBehavior::SoftPanic => MODE_SOFT_PANIC,
685            OverflowingBehavior::Ignore => MODE_IGNORE,
686        };
687        OVERFLOWING_MODE.store(value, std::sync::atomic::Ordering::Relaxed);
688    }
689}
690
691#[cfg(test)]
692mod test {
693    use super::*;
694
695    #[cfg(debug_assertions)]
696    #[crate::test]
697    #[should_panic]
698    fn test_panicking_add() {
699        set_behavior(OverflowingBehavior::Panic);
700        let _ = Overflowing::<i8>::MAX + Overflowing::<i8>::ONE;
701    }
702
703    #[crate::test]
704    fn test_wrapping_add() {
705        let result = Overflowing::<i8>::MAX.wrapping_add(Overflowing::<i8>::ONE);
706        assert_eq!(result, Overflowing::<i8>::MIN);
707    }
708
709    #[crate::test]
710    fn test_checked_add() {
711        let result = Overflowing::<i8>::MAX.checked_add(Overflowing::<i8>::ONE);
712        assert_eq!(result, None);
713    }
714}