Skip to main content

relp_num/integer/
sign.rs

1//! # NonZero signs of integers
2use std::cmp::Ordering;
3use std::num::NonZero;
4
5use crate::Negateable;
6use crate::{NonZeroSign, NonZeroSigned};
7use crate::Sign;
8use crate::Signed;
9
10macro_rules! unsigned {
11    ($($ty:ty),+ $(,)?) => {$(
12        impl Signed for $ty {
13            #[inline]
14            fn signum(&self) -> Sign {
15                if *self == 0 {
16                    Sign::Zero
17                } else {
18                    Sign::Positive
19                }
20            }
21        }
22    )+}
23}
24
25unsigned!(u8, u16, u32, u64, u128, usize);
26
27macro_rules! signed {
28    ($($ty:ty),+ $(,)?) => {$(
29        impl Signed for $ty {
30            #[inline]
31            fn signum(&self) -> Sign {
32                match self.cmp(&0) {
33                    Ordering::Less => Sign::Negative,
34                    Ordering::Equal => Sign::Zero,
35                    Ordering::Greater => Sign::Positive,
36                }
37            }
38        }
39
40        impl Negateable for $ty {
41            #[inline]
42            #[track_caller]
43            fn negate(&mut self) {
44                // Two's complement has one more negative value than positive ones, so negating
45                // `MIN` has no answer. Plain negation panics on it in debug and returns `MIN`
46                // again in release, which leaves a value whose sign did not flip; checking makes
47                // both profiles agree and keeps the wrong sign from escaping.
48                match self.checked_neg() {
49                    Some(negated) => *self = negated,
50                    None => panic!(concat!("cannot negate ", stringify!($ty), "::MIN")),
51                }
52            }
53        }
54    )+}
55}
56
57signed!(i8, i16, i32, i64, i128, isize);
58
59macro_rules! non_zero_unsigned {
60    ($($ty:ty),+ $(,)?) => {$(
61        impl Signed for NonZero<$ty> {
62            #[inline]
63            fn signum(&self) -> Sign {
64                Sign::Positive
65            }
66        }
67
68        /// The type cannot represent zero, so there is no failure case.
69        impl NonZeroSigned for NonZero<$ty> {
70            #[inline]
71            fn non_zero_signum(&self) -> NonZeroSign {
72                NonZeroSign::Positive
73            }
74        }
75    )+}
76}
77
78non_zero_unsigned!(u8, u16, u32, u64, u128, usize);
79
80macro_rules! non_zero_signed {
81    ($($ty:ty),+ $(,)?) => {$(
82        impl Signed for NonZero<$ty> {
83            #[inline]
84            fn signum(&self) -> Sign {
85                if self.get() > 0 {
86                    Sign::Positive
87                } else {
88                    Sign::Negative
89                }
90            }
91        }
92
93        impl Negateable for NonZero<$ty> {
94            #[inline]
95            #[track_caller]
96            fn negate(&mut self) {
97                // See the `signed!` macro: `MIN` has no negation. Wrapping would return `MIN`
98                // again, which is still non zero and so still sound, but reports the sign it
99                // started with.
100                match self.get().checked_neg() {
101                    Some(negated) => *self = unsafe {
102                        // SAFETY: The negation of a non zero value is non zero.
103                        NonZero::new_unchecked(negated)
104                    },
105                    None => panic!(concat!("cannot negate NonZero<", stringify!($ty), ">::MIN")),
106                }
107            }
108        }
109
110        /// The type cannot represent zero, so there is no failure case.
111        impl NonZeroSigned for NonZero<$ty> {
112            #[inline]
113            fn non_zero_signum(&self) -> NonZeroSign {
114                if self.get() > 0 {
115                    NonZeroSign::Positive
116                } else {
117                    NonZeroSign::Negative
118                }
119            }
120        }
121    )+}
122}
123
124non_zero_signed!(i8, i16, i32, i64, i128, isize);
125
126#[cfg(test)]
127mod test {
128    use std::num::{NonZeroI8, NonZeroU8};
129
130    use crate::{Negateable, NonZeroSign, NonZeroSigned};
131
132    #[test]
133    fn test_zero_sign() {
134        assert_eq!(1_u32.non_zero_signum(), NonZeroSign::Positive);
135        assert_eq!(-1_u32.non_zero_signum(), NonZeroSign::Negative);
136
137        assert_eq!(NonZeroU8::new(1).unwrap().non_zero_signum(), NonZeroSign::Positive);
138        assert_eq!(NonZeroI8::new(1).unwrap().non_zero_signum(), NonZeroSign::Positive);
139        assert_eq!(NonZeroI8::new(-1).unwrap().non_zero_signum(), NonZeroSign::Negative);
140    }
141
142    #[test]
143    #[should_panic]
144    fn test_non_zero_on_zero() {
145        0_u32.non_zero_signum();
146    }
147
148    #[test]
149    #[should_panic]
150    fn test_non_zero_on_zero_signed() {
151        0_i8.non_zero_signum();
152    }
153
154    /// Two's complement has no counterpart for `MIN`, so negating it has to fail rather than
155    /// return `MIN` again with an unflipped sign, which is what release builds used to do.
156    #[test]
157    fn test_negate_minimum_panics() {
158        macro_rules! check {
159            ($ty:ty, $nz:ty) => {
160                let mut value = <$ty>::MIN;
161                assert!(std::panic::catch_unwind(move || value.negate()).is_err());
162
163                let mut value = <$nz>::new(<$ty>::MIN).unwrap();
164                assert!(std::panic::catch_unwind(move || value.negate()).is_err());
165
166                // Everything else still negates, and twice is the identity
167                for start in [<$ty>::MIN + 1, -1, 1, <$ty>::MAX] {
168                    let mut value = start;
169                    value.negate();
170                    assert_eq!(value, -start, "{} {start}", stringify!($ty));
171                    value.negate();
172                    assert_eq!(value, start, "{} {start}", stringify!($ty));
173                }
174            }
175        }
176
177        let hook = std::panic::take_hook();
178        std::panic::set_hook(Box::new(|_| {}));
179
180        check!(i8, std::num::NonZeroI8);
181        check!(i16, std::num::NonZeroI16);
182        check!(i32, std::num::NonZeroI32);
183        check!(i64, std::num::NonZeroI64);
184        check!(i128, std::num::NonZeroI128);
185        check!(isize, std::num::NonZeroIsize);
186
187        std::panic::set_hook(hook);
188    }
189}