Skip to main content

yui_core/conc/num/
sign.rs

1//! The sign `±` as a two-element type, isomorphic to the multiplicative
2//! group `{+1, -1} ⊂ ℤ`.
3
4use std::ops::{Mul, Neg};
5use derive_more::{Display, Debug};
6use is_even::IsEven;
7use num_traits::Signed;
8
9/// A sign, either `+` or `-`. Behaves as the multiplicative group `{±1}`.
10#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default, Display, Debug)]
11#[cfg_attr(feature = "serde", derive(serde_repr::Serialize_repr, serde_repr::Deserialize_repr))]
12#[repr(i8)]
13pub enum Sign {
14    #[default]
15    #[display("+")]
16    #[debug("+")]
17    Pos = 1,
18
19    #[display("-")]
20    #[debug("-")]
21    Neg = -1
22}
23
24impl Sign {
25    pub fn is_positive(&self) -> bool {
26        self == &Sign::Pos
27    }
28
29    pub fn is_negative(&self) -> bool {
30        !self.is_positive()
31    }
32
33    /// `(-1)^val`: `Pos` if `val` is even, `Neg` if odd.
34    pub fn from_parity<I: IsEven>(val: I) -> Self {
35        if val.is_even() {
36            Sign::Pos
37        } else {
38            Sign::Neg
39        }
40    }
41}
42
43macro_rules! impl_int_conversion {
44    ($t:tt) => {
45        impl From<$t> for Sign {
46            fn from(value: $t) -> Self {
47                match value {
48                     1 => Sign::Pos,
49                    -1 => Sign::Neg,
50                     _ => panic!()
51                }
52            }
53        }
54
55        impl From<Sign> for $t {
56            fn from(value: Sign) -> Self {
57                match value {
58                    Sign::Pos =>  1,
59                    Sign::Neg => -1
60                }
61            }
62        }
63    };
64}
65
66impl_int_conversion!(i8);
67impl_int_conversion!(i16);
68impl_int_conversion!(i32);
69impl_int_conversion!(i64);
70impl_int_conversion!(isize);
71
72impl Neg for Sign {
73    type Output = Self;
74    fn neg(self) -> Self {
75        use Sign::*;
76        match self {
77            Neg => Pos,
78            Pos => Neg
79        }
80    }
81}
82
83impl Mul for Sign {
84    type Output = Self;
85
86    fn mul(self, rhs: Self) -> Self::Output {
87        use Sign::*;
88        match (self, rhs) {
89            (Pos, Pos) | (Neg, Neg) => Pos,
90            _ => Neg
91        }
92    }
93}
94
95/// Types that have a sign (e.g. signed integers).
96pub trait GetSign {
97    fn sign(&self) -> Sign;
98}
99
100impl<T> GetSign for T where T: Signed {
101    fn sign(&self) -> Sign {
102        assert!(!self.is_zero(), "zero has no sign");
103        if self.is_positive() {
104            Sign::Pos
105        } else {
106            Sign::Neg
107        }
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114
115    #[test]
116    #[should_panic(expected = "zero has no sign")]
117    fn sign_of_zero() {
118        let _ = 0i64.sign();
119    }
120
121    #[test]
122    fn ord() {
123        assert!(Sign::Neg < Sign::Pos)
124    }
125
126    #[test]
127    fn to_string() {
128        assert_eq!(&Sign::Neg.to_string(), "-");
129        assert_eq!(&Sign::Pos.to_string(), "+");
130    }
131
132    #[cfg(feature = "serde")]
133    #[test]
134    fn serialize() {
135        let s = Sign::Pos;
136        let ser = serde_json::to_string(&s).unwrap();
137        assert_eq!(ser, "1");
138
139        let des = serde_json::from_str(&ser).unwrap();
140        assert_eq!(s, des);
141
142        let s = Sign::Neg;
143        let ser = serde_json::to_string(&s).unwrap();
144        assert_eq!(ser, "-1");
145
146        let des = serde_json::from_str(&ser).unwrap();
147        assert_eq!(s, des);
148    }
149}