Skip to main content

dashu_base/
sign.rs

1//! Trait definitions for sign related operations.
2
3use core::{
4    cmp::Ordering,
5    ops::{Mul, MulAssign, Neg},
6};
7
8/// Absolute value.
9///
10/// # Examples
11/// ```
12/// use dashu_base::Abs;
13/// assert_eq!((-5).abs(), 5);
14/// ```
15pub trait Abs {
16    /// The type of the absolute value.
17    type Output;
18
19    /// Compute the absolute value.
20    fn abs(self) -> Self::Output;
21}
22
23/// Unsigned absolute value.
24///
25/// # Examples
26/// ```
27/// # use dashu_base::UnsignedAbs;
28/// assert_eq!((-5i8).unsigned_abs(), 5u8);
29/// ```
30pub trait UnsignedAbs {
31    /// The type of the unsigned absolute value.
32    type Output;
33
34    /// Compute the absolute value as an unsigned type.
35    fn unsigned_abs(self) -> Self::Output;
36}
37
38/// Compare the magnitude of this number to the magnitude of the other number
39///
40/// Note that this function will panic if either of the numbers is NaN.
41///
42/// # Examples
43///
44/// ```
45/// # use dashu_base::AbsOrd;
46/// assert!(5.abs_cmp(&-6).is_le());
47/// assert!(12.3.abs_cmp(&-12.3).is_eq());
48/// ```
49pub trait AbsOrd<Rhs = Self> {
50    /// Compare the magnitude of `self` against the magnitude of `rhs`.
51    fn abs_cmp(&self, rhs: &Rhs) -> Ordering;
52}
53
54/// This trait marks the number is signed.
55///
56/// Notice that the negative zeros (of [f32] and [f64]) are still considered
57/// to have a positive sign.
58///
59/// # Examples
60///
61/// ```
62/// # use dashu_base::{Signed, Sign};
63/// assert_eq!((-2).sign(), Sign::Negative);
64/// assert_eq!((-2.4).sign(), Sign::Negative);
65/// assert_eq!((0.).sign(), Sign::Positive);
66///
67/// assert!(2.is_positive());
68/// assert!((-2.4).is_negative());
69/// assert!((0.).is_positive());
70/// ```
71pub trait Signed {
72    /// Return the sign of the number.
73    fn sign(&self) -> Sign;
74
75    /// Returns `true` if the number is positive (including positive zero).
76    #[inline]
77    fn is_positive(&self) -> bool {
78        self.sign() == Sign::Positive
79    }
80    /// Returns `true` if the number is negative.
81    #[inline]
82    fn is_negative(&self) -> bool {
83        self.sign() == Sign::Negative
84    }
85}
86
87macro_rules! impl_abs_ops_prim {
88    ($($signed:ty;)*) => {$( // this branch is only for float
89        impl Abs for $signed {
90            type Output = $signed;
91            #[inline]
92            fn abs(self) -> Self::Output {
93                if self.is_nan() || self >= 0. {
94                    self
95                } else {
96                    -self
97                }
98            }
99        }
100    )*};
101    ($($signed:ty => $unsigned:ty;)*) => {$(
102        impl Abs for $signed {
103            type Output = $signed;
104            #[inline]
105            fn abs(self) -> Self::Output {
106                <$signed>::abs(self)
107            }
108        }
109
110        impl UnsignedAbs for $signed {
111            type Output = $unsigned;
112            #[inline]
113            fn unsigned_abs(self) -> Self::Output {
114                <$signed>::unsigned_abs(self)
115            }
116        }
117    )*}
118}
119impl_abs_ops_prim!(i8 => u8; i16 => u16; i32 => u32; i64 => u64; i128 => u128; isize => usize;);
120impl_abs_ops_prim!(f32; f64;);
121
122/// An enum representing the sign of a number
123///
124/// A sign can be converted to or from a boolean value, assuming `true` is [Negative].
125#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
126pub enum Sign {
127    /// A non-negative value (positive, or positive/negative zero).
128    Positive,
129    /// A negative value.
130    Negative,
131}
132
133use Sign::*;
134
135impl Sign {
136    /// Return the sign as a string slice for formatting.
137    ///
138    /// Returns `"-"` for [`Negative`], `"+"` for [`Positive`] when `sign_plus`
139    /// is true, or `""` for [`Positive`] when `sign_plus` is false.
140    ///
141    /// # Examples
142    ///
143    /// ```
144    /// # use dashu_base::Sign;
145    /// assert_eq!(Sign::Negative.as_sign_str(false), "-");
146    /// assert_eq!(Sign::Positive.as_sign_str(true), "+");
147    /// assert_eq!(Sign::Positive.as_sign_str(false), "");
148    /// ```
149    #[inline]
150    pub const fn as_sign_str(self, sign_plus: bool) -> &'static str {
151        match self {
152            Sign::Negative => "-",
153            Sign::Positive if sign_plus => "+",
154            _ => "",
155        }
156    }
157}
158
159impl From<bool> for Sign {
160    /// Convert boolean value to [Sign], returns [Negative] for `true`
161    #[inline]
162    fn from(v: bool) -> Self {
163        match v {
164            true => Self::Negative,
165            false => Self::Positive,
166        }
167    }
168}
169
170impl From<Sign> for bool {
171    /// Convert [Sign] to boolean value, returns `true` for [Negative]
172    #[inline]
173    fn from(v: Sign) -> Self {
174        match v {
175            Sign::Negative => true,
176            Sign::Positive => false,
177        }
178    }
179}
180
181impl Neg for Sign {
182    type Output = Sign;
183
184    #[inline]
185    fn neg(self) -> Sign {
186        match self {
187            Positive => Negative,
188            Negative => Positive,
189        }
190    }
191}
192
193impl Mul<Sign> for Sign {
194    type Output = Sign;
195
196    #[inline]
197    fn mul(self, rhs: Sign) -> Sign {
198        match (self, rhs) {
199            (Positive, Positive) => Positive,
200            (Positive, Negative) => Negative,
201            (Negative, Positive) => Negative,
202            (Negative, Negative) => Positive,
203        }
204    }
205}
206
207impl Mul<Ordering> for Sign {
208    type Output = Ordering;
209    #[inline]
210    fn mul(self, rhs: Ordering) -> Self::Output {
211        match self {
212            Positive => rhs,
213            Negative => rhs.reverse(),
214        }
215    }
216}
217
218impl Mul<Sign> for Ordering {
219    type Output = Ordering;
220    #[inline]
221    fn mul(self, rhs: Sign) -> Self::Output {
222        match rhs {
223            Positive => self,
224            Negative => self.reverse(),
225        }
226    }
227}
228
229impl MulAssign<Sign> for Sign {
230    #[inline]
231    fn mul_assign(&mut self, rhs: Sign) {
232        *self = *self * rhs;
233    }
234}
235
236impl PartialOrd for Sign {
237    #[inline]
238    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
239        Some(self.cmp(other))
240    }
241}
242
243impl Ord for Sign {
244    #[inline]
245    fn cmp(&self, other: &Self) -> Ordering {
246        match (self, other) {
247            (Positive, Negative) => Ordering::Greater,
248            (Negative, Positive) => Ordering::Less,
249            _ => Ordering::Equal,
250        }
251    }
252}
253
254macro_rules! impl_sign_ops_for_primitives {
255    ($($t:ty)*) => {$(
256        impl Mul<$t> for Sign {
257            type Output = $t;
258
259            #[inline]
260            fn mul(self, rhs: $t) -> Self::Output {
261                match self {
262                    Positive => rhs,
263                    Negative => -rhs
264                }
265            }
266        }
267
268        impl Mul<Sign> for $t {
269            type Output = $t;
270
271            #[inline]
272            fn mul(self, rhs: Sign) -> Self::Output {
273                match rhs {
274                    Positive => self,
275                    Negative => -self
276                }
277            }
278        }
279    )*};
280}
281impl_sign_ops_for_primitives!(i8 i16 i32 i64 i128 isize f32 f64);
282
283macro_rules! impl_signed_for_int {
284    ($($t:ty)*) => {$(
285        impl Signed for $t {
286            #[inline]
287            fn sign(&self) -> Sign {
288                Sign::from(*self < 0)
289            }
290        }
291
292        impl AbsOrd for $t {
293            #[inline]
294            fn abs_cmp(&self, rhs: &Self) -> Ordering {
295                self.abs().cmp(&rhs.abs())
296            }
297        }
298    )*};
299}
300impl_signed_for_int!(i8 i16 i32 i64 i128 isize);
301
302macro_rules! impl_signed_for_float {
303    ($t:ty, $shift:literal) => {
304        impl Signed for $t {
305            #[inline]
306            fn sign(&self) -> Sign {
307                if self.is_nan() {
308                    panic!("nan doesn't have a sign")
309                } else if *self == -0. {
310                    return Sign::Positive;
311                }
312                Sign::from(self.to_bits() >> $shift > 0)
313            }
314        }
315
316        impl AbsOrd for $t {
317            #[inline]
318            fn abs_cmp(&self, rhs: &Self) -> Ordering {
319                self.abs()
320                    .partial_cmp(&rhs.abs())
321                    .expect("abs_cmp is not allowed on NaNs!")
322            }
323        }
324    };
325}
326impl_signed_for_float!(f32, 31);
327impl_signed_for_float!(f64, 63);
328
329#[cfg(test)]
330mod tests {
331    use super::*;
332
333    #[test]
334    fn test_signed() {
335        assert_eq!(0i32.sign(), Sign::Positive);
336        assert_eq!(1i32.sign(), Sign::Positive);
337        assert_eq!((-1i32).sign(), Sign::Negative);
338
339        assert_eq!(0f32.sign(), Sign::Positive);
340        assert_eq!((-0f32).sign(), Sign::Positive);
341        assert_eq!(1f32.sign(), Sign::Positive);
342        assert_eq!((-1f32).sign(), Sign::Negative);
343    }
344
345    #[test]
346    #[should_panic]
347    fn test_signed_nan() {
348        let _ = f32::NAN.sign();
349    }
350
351    #[test]
352    #[should_panic]
353    fn test_abs_cmp_nan() {
354        let _ = f32::NAN.abs_cmp(&f32::NAN);
355    }
356}