Skip to main content

relp_num/non_zero/
sign.rs

1//! # Signs of nonzero values
2//!
3//! Usual sign implementations have a sign for zero, the implementation in this module does not.
4use std::cmp::Ordering;
5use std::ops::{Mul, MulAssign, Neg, Not};
6
7use crate::{Negateable, Signed};
8use crate::non_zero::NonZero;
9use crate::sign::Sign;
10
11/// A signed number that can have a nonzero value.
12pub trait NonZeroSigned: NonZero + Signed {
13    /// Whether the value is positive or negative.
14    fn non_zero_signum(&self) -> NonZeroSign;
15    /// Whether `x > 0`.
16    #[inline]
17    fn non_zero_is_positive(&self) -> bool {
18        self.non_zero_signum() == NonZeroSign::Positive
19    }
20    /// Whether `x < 0`.
21    #[inline]
22    fn non_zero_is_negative(&self) -> bool {
23        self.non_zero_signum() == NonZeroSign::Negative
24    }
25}
26
27/// Sign of a nonzero value.
28///
29/// Existing `Sign` traits, such in `num`, typically have a third value for the sign of 0. Working
30/// with that trait creates many branches or match cases that should never be possible.
31#[derive(Eq, PartialEq, Copy, Clone, Debug)]
32pub enum NonZeroSign {
33    /// `x > 0`
34    Positive = 1,
35    /// `x < 0`
36    Negative = -1,
37}
38
39/// Derive the non zero sign from the general sign, panicking on zero.
40///
41/// Used for the number types that can represent zero, where the guarantee is a contract the
42/// caller has to keep rather than something the type enforces. Types that cannot be zero get a
43/// total implementation instead, and never reach a panic.
44macro_rules! non_zero_signed_by_panic {
45    ($($t:ty),+ $(,)?) => {$(
46        impl NonZeroSigned for $t {
47            #[inline]
48            #[track_caller]
49            fn non_zero_signum(&self) -> NonZeroSign {
50                match Signed::signum(self) {
51                    Sign::Positive => NonZeroSign::Positive,
52                    Sign::Negative => NonZeroSign::Negative,
53                    Sign::Zero => panic!(
54                        "attempt to take the non zero sign of a zero value of type {}",
55                        std::any::type_name::<$t>(),
56                    ),
57                }
58            }
59        }
60    )+}
61}
62
63non_zero_signed_by_panic!(i8, i16, i32, i64, i128, isize);
64non_zero_signed_by_panic!(u8, u16, u32, u64, u128, usize);
65non_zero_signed_by_panic!(Sign);
66
67/// Derive the non zero sign from a type that cannot represent zero.
68macro_rules! non_zero_signed_total {
69    ($t:ty, $sign:expr) => {
70        /// The type cannot represent zero, so there is no failure case.
71        impl NonZeroSigned for $t {
72            #[inline]
73            fn non_zero_signum(&self) -> NonZeroSign {
74                $sign(self)
75            }
76        }
77    }
78}
79
80non_zero_signed_total!(crate::fixed::One, |_: &crate::fixed::One| NonZeroSign::Positive);
81non_zero_signed_total!(crate::fixed::SignedOne, |value: &crate::fixed::SignedOne| match value {
82    crate::fixed::SignedOne::PlusOne => NonZeroSign::Positive,
83    crate::fixed::SignedOne::MinusOne => NonZeroSign::Negative,
84});
85
86impl Signed for NonZeroSign {
87    fn signum(&self) -> Sign {
88        match self {
89            NonZeroSign::Positive => Sign::Positive,
90            NonZeroSign::Negative => Sign::Negative,
91        }
92    }
93}
94
95impl NonZero for NonZeroSign {
96    fn is_not_zero(&self) -> bool {
97        true
98    }
99}
100
101impl Negateable for NonZeroSign {
102    fn negate(&mut self) {
103        *self = match self {
104            NonZeroSign::Positive => NonZeroSign::Negative,
105            NonZeroSign::Negative => NonZeroSign::Positive,
106        }
107    }
108}
109
110impl Not for NonZeroSign {
111    type Output = Self;
112
113    fn not(mut self) -> Self::Output {
114        Negateable::negate(&mut self);
115        self
116    }
117}
118
119impl Neg for NonZeroSign {
120    type Output = Self;
121
122    fn neg(mut self) -> Self::Output {
123        Negateable::negate(&mut self);
124        self
125    }
126}
127
128impl From<Sign> for NonZeroSign {
129    fn from(other: Sign) -> Self {
130        debug_assert!(other.is_not_zero());
131
132        match other {
133            Sign::Positive => NonZeroSign::Positive,
134            Sign::Zero => panic!("attempt to convert a zero sign into a non zero sign"),
135            Sign::Negative => NonZeroSign::Negative,
136        }
137    }
138}
139
140/// Signs are totally ordered as `Negative < Positive`, matching the discriminants.
141impl Ord for NonZeroSign {
142    #[inline]
143    fn cmp(&self, other: &Self) -> Ordering {
144        (*self as i8).cmp(&(*other as i8))
145    }
146}
147
148impl PartialOrd for NonZeroSign {
149    #[inline]
150    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
151        Some(self.cmp(other))
152    }
153}
154
155impl Mul for NonZeroSign {
156    type Output = Self;
157
158    fn mul(mut self, rhs: Self) -> Self::Output {
159        MulAssign::mul_assign(&mut self, rhs);
160        self
161    }
162}
163
164impl MulAssign for NonZeroSign {
165    fn mul_assign(&mut self, rhs: Self) {
166        *self = match (*self, rhs) {
167            (NonZeroSign::Positive, NonZeroSign::Positive) => NonZeroSign::Positive,
168            (NonZeroSign::Positive, NonZeroSign::Negative) => NonZeroSign::Negative,
169            (NonZeroSign::Negative, NonZeroSign::Positive) => NonZeroSign::Negative,
170            (NonZeroSign::Negative, NonZeroSign::Negative) => NonZeroSign::Positive,
171        }
172    }
173}
174
175#[cfg(test)]
176mod test {
177    use std::cmp::Ordering;
178
179    use crate::{NonZeroSign, NonZeroSigned, Sign};
180    use crate::{R64, RB};
181
182    #[test]
183    fn test_cmp() {
184        assert!(NonZeroSign::Positive > NonZeroSign::Negative);
185        assert_eq!(NonZeroSign::Positive.partial_cmp(&NonZeroSign::Positive), Some(Ordering::Equal));
186        assert_eq!(NonZeroSign::Negative.partial_cmp(&NonZeroSign::Negative), Some(Ordering::Equal));
187        assert_eq!(NonZeroSign::Negative.partial_cmp(&NonZeroSign::Positive), Some(Ordering::Less));
188    }
189
190    /// `a == b` must imply `partial_cmp(a, b) == Some(Equal)`.
191    #[test]
192    fn test_cmp_contract() {
193        for a in [NonZeroSign::Negative, NonZeroSign::Positive] {
194            assert_eq!(a.partial_cmp(&a), Some(Ordering::Equal));
195            assert!(a <= a);
196            assert!(a >= a);
197            for b in [NonZeroSign::Negative, NonZeroSign::Positive] {
198                assert_eq!(a == b, a.partial_cmp(&b) == Some(Ordering::Equal));
199                assert_eq!(a.partial_cmp(&b), Some(a.cmp(&b)));
200                assert_eq!(a.cmp(&b), b.cmp(&a).reverse());
201            }
202        }
203        assert!(NonZeroSign::Negative < NonZeroSign::Positive);
204    }
205
206    #[test]
207    fn test_numbers() {
208        use crate::Signed;
209        assert_eq!(Signed::signum(&RB!(1)), Sign::Positive);
210        assert_eq!(Signed::signum(&RB!(-1)), Sign::Negative);
211        assert!(RB!(1).is_positive());
212        assert!(RB!(-1).is_negative());
213    }
214
215    #[test]
216    fn test_numbers_non_zero() {
217        assert_eq!(1.non_zero_signum(), NonZeroSign::Positive);
218        assert_eq!((-1).non_zero_signum(), NonZeroSign::Negative);
219
220        assert_eq!(RB!(-1).non_zero_signum() * RB!(-1).non_zero_signum(), NonZeroSign::Positive);
221        assert_eq!(RB!(1).non_zero_signum() * RB!(1).non_zero_signum(), NonZeroSign::Positive);
222
223        assert_eq!(RB!(-1).non_zero_signum() * RB!(1).non_zero_signum(), NonZeroSign::Negative);
224
225        assert_eq!(R64!(1).non_zero_signum(), NonZeroSign::Positive);
226        assert_eq!(R64!(-1).non_zero_signum(), NonZeroSign::Negative);
227
228        assert_eq!(R64!(-1).non_zero_signum() * R64!(-1).non_zero_signum(), NonZeroSign::Positive);
229        assert_eq!(R64!(1).non_zero_signum() * R64!(1).non_zero_signum(), NonZeroSign::Positive);
230        assert_eq!(R64!(-1).non_zero_signum() * R64!(1).non_zero_signum(), NonZeroSign::Negative);
231    }
232
233    #[test]
234    #[should_panic]
235    fn test_zero() {
236        RB!(0).non_zero_signum();
237    }
238}