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
39impl<T: NonZero + Signed> NonZeroSigned for T {
40    #[inline]
41    fn non_zero_signum(&self) -> NonZeroSign {
42        match self.signum() {
43            Sign::Positive => NonZeroSign::Positive,
44            Sign::Zero => panic!("attempt to convert a zero sign into a non zero sign"),
45            Sign::Negative => NonZeroSign::Negative,
46        }
47    }
48}
49
50impl Signed for NonZeroSign {
51    fn signum(&self) -> Sign {
52        match self {
53            NonZeroSign::Positive => Sign::Positive,
54            NonZeroSign::Negative => Sign::Negative,
55        }
56    }
57}
58
59impl NonZero for NonZeroSign {
60    fn is_not_zero(&self) -> bool {
61        true
62    }
63}
64
65impl Negateable for NonZeroSign {
66    fn negate(&mut self) {
67        *self = match self {
68            NonZeroSign::Positive => NonZeroSign::Negative,
69            NonZeroSign::Negative => NonZeroSign::Positive,
70        }
71    }
72}
73
74impl Not for NonZeroSign {
75    type Output = Self;
76
77    fn not(mut self) -> Self::Output {
78        Negateable::negate(&mut self);
79        self
80    }
81}
82
83impl Neg for NonZeroSign {
84    type Output = Self;
85
86    fn neg(mut self) -> Self::Output {
87        Negateable::negate(&mut self);
88        self
89    }
90}
91
92impl From<Sign> for NonZeroSign {
93    fn from(other: Sign) -> Self {
94        debug_assert!(other.is_not_zero());
95
96        match other {
97            Sign::Positive => NonZeroSign::Positive,
98            Sign::Zero => panic!("attempt to convert a zero sign into a non zero sign"),
99            Sign::Negative => NonZeroSign::Negative,
100        }
101    }
102}
103
104impl PartialOrd for NonZeroSign {
105    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
106        match (self, other) {
107            (NonZeroSign::Positive, NonZeroSign::Positive) => None,
108            (NonZeroSign::Positive, NonZeroSign::Negative) => Some(Ordering::Greater),
109            (NonZeroSign::Negative, NonZeroSign::Positive) => Some(Ordering::Less),
110            (NonZeroSign::Negative, NonZeroSign::Negative) => None,
111        }
112    }
113}
114
115impl Mul for NonZeroSign {
116    type Output = Self;
117
118    fn mul(mut self, rhs: Self) -> Self::Output {
119        MulAssign::mul_assign(&mut self, rhs);
120        self
121    }
122}
123
124impl MulAssign for NonZeroSign {
125    fn mul_assign(&mut self, rhs: Self) {
126        *self = match (*self, rhs) {
127            (NonZeroSign::Positive, NonZeroSign::Positive) => NonZeroSign::Positive,
128            (NonZeroSign::Positive, NonZeroSign::Negative) => NonZeroSign::Negative,
129            (NonZeroSign::Negative, NonZeroSign::Positive) => NonZeroSign::Negative,
130            (NonZeroSign::Negative, NonZeroSign::Negative) => NonZeroSign::Positive,
131        }
132    }
133}
134
135#[cfg(test)]
136mod test {
137    use std::cmp::Ordering;
138
139    use crate::{NonZeroSign, NonZeroSigned, Sign};
140    use crate::{R64, RB};
141
142    #[test]
143    fn test_cmp() {
144        assert!(NonZeroSign::Positive > NonZeroSign::Negative);
145        assert_eq!(NonZeroSign::Positive.partial_cmp(&NonZeroSign::Positive), None);
146        assert_eq!(NonZeroSign::Negative.partial_cmp(&NonZeroSign::Negative), None);
147        assert_eq!(NonZeroSign::Negative.partial_cmp(&NonZeroSign::Positive), Some(Ordering::Less));
148    }
149
150    #[test]
151    fn test_numbers() {
152        use crate::Signed;
153        assert_eq!(Signed::signum(&RB!(1)), Sign::Positive);
154        assert_eq!(Signed::signum(&RB!(-1)), Sign::Negative);
155        assert!(RB!(1).is_positive());
156        assert!(RB!(-1).is_negative());
157    }
158
159    #[test]
160    fn test_numbers_non_zero() {
161        assert_eq!(1.non_zero_signum(), NonZeroSign::Positive);
162        assert_eq!((-1).non_zero_signum(), NonZeroSign::Negative);
163
164        assert_eq!(RB!(-1).non_zero_signum() * RB!(-1).non_zero_signum(), NonZeroSign::Positive);
165        assert_eq!(RB!(1).non_zero_signum() * RB!(1).non_zero_signum(), NonZeroSign::Positive);
166
167        assert_eq!(RB!(-1).non_zero_signum() * RB!(1).non_zero_signum(), NonZeroSign::Negative);
168
169        assert_eq!(R64!(1).non_zero_signum(), NonZeroSign::Positive);
170        assert_eq!(R64!(-1).non_zero_signum(), NonZeroSign::Negative);
171
172        assert_eq!(R64!(-1).non_zero_signum() * R64!(-1).non_zero_signum(), NonZeroSign::Positive);
173        assert_eq!(R64!(1).non_zero_signum() * R64!(1).non_zero_signum(), NonZeroSign::Positive);
174        assert_eq!(R64!(-1).non_zero_signum() * R64!(1).non_zero_signum(), NonZeroSign::Negative);
175    }
176
177    #[test]
178    #[should_panic]
179    fn test_zero() {
180        RB!(0).non_zero_signum();
181    }
182}