Skip to main content

relp_num/integer/big/
properties.rs

1use std::cmp::Ordering;
2use std::ops::Deref;
3
4use crate::{NonZero, Sign, Ubig};
5use crate::integer::big::NonZeroUbig;
6use crate::integer::big::ops::building_blocks::is_well_formed;
7use crate::Signed;
8
9impl<const S: usize> NonZero for Ubig<S> {
10    #[inline]
11    fn is_not_zero(&self) -> bool {
12        !self.0.is_empty()
13    }
14}
15
16impl<const S: usize> NonZero for NonZeroUbig<S> {
17    #[inline]
18    fn is_not_zero(&self) -> bool {
19        true
20    }
21}
22
23impl<const S: usize> Deref for Ubig<S> {
24    type Target = [usize];
25
26    fn deref(&self) -> &Self::Target {
27        &self.0
28    }
29}
30
31impl<const S: usize> Deref for NonZeroUbig<S> {
32    type Target = [usize];
33
34    fn deref(&self) -> &Self::Target {
35        &self.0
36    }
37}
38
39impl<const S: usize> Signed for Ubig<S> {
40    #[inline]
41    fn signum(&self) -> Sign {
42        // Unlike `NonZeroUbig`, this type represents zero, so it is not always positive.
43        if self.is_not_zero() { Sign::Positive } else { Sign::Zero }
44    }
45
46    #[inline]
47    fn is_positive(&self) -> bool {
48        self.is_not_zero()
49    }
50
51    #[inline]
52    fn is_negative(&self) -> bool {
53        false
54    }
55}
56
57impl<const S: usize> Signed for NonZeroUbig<S> {
58    fn signum(&self) -> Sign {
59        Sign::Positive
60    }
61
62    fn is_positive(&self) -> bool {
63        true
64    }
65
66    fn is_negative(&self) -> bool {
67        false
68    }
69}
70
71impl<const S: usize> Ord for NonZeroUbig<S> {
72    fn cmp(&self, other: &Self) -> Ordering {
73        cmp(&self.0, &other.0)
74    }
75}
76
77impl<const S: usize> Ord for Ubig<S> {
78    fn cmp(&self, other: &Self) -> Ordering {
79        cmp(&self.0, &other.0)
80    }
81}
82
83impl<const S: usize> PartialOrd for NonZeroUbig<S> {
84    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
85        Some(self.cmp(other))
86    }
87}
88
89impl<const S: usize> PartialOrd for Ubig<S> {
90    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
91        Some(self.cmp(other))
92    }
93}
94
95/// Compare two well formed values by their words, most significant first.
96///
97/// The length is compared with an early return rather than in a `match` around the loop: writing
98/// it as a `match` makes the compiler compute the unequal length answer before it knows whether it
99/// needs it, which costs the equal length case, and that is the case that reaches the loop.
100///
101/// The loop zips before it reverses. Zipped slice iterators walk a single counter and read both
102/// words unchecked, so a word costs a load, a compare and a decrement, with no bounds check on
103/// either side. Reversing each side before zipping defeats that: it gives two independent cursors
104/// and roughly twice the loop body.
105#[must_use]
106#[inline]
107pub fn cmp(left: &[usize], right: &[usize]) -> Ordering {
108    debug_assert!(is_well_formed(left));
109    debug_assert!(is_well_formed(right));
110
111    // Neither value has a leading zero word, so the longer one is the larger one.
112    if left.len() != right.len() {
113        return left.len().cmp(&right.len());
114    }
115
116    for (left_word, right_word) in left.iter().zip(right.iter()).rev() {
117        match left_word.cmp(right_word) {
118            Ordering::Less => return Ordering::Less,
119            Ordering::Equal => {}
120            Ordering::Greater => return Ordering::Greater,
121        }
122    }
123
124    Ordering::Equal
125}
126
127#[cfg(test)]
128mod test {
129    use std::cmp::Ordering;
130
131    use crate::{NonZero, Sign, Signed, Ubig};
132    use crate::integer::big::NonZeroUbig;
133    use crate::integer::big::properties::cmp;
134
135    /// `Ubig` represents zero, so it is not unconditionally positive.
136    ///
137    /// It used to report `Sign::Positive` for every value, including the zero that `NonZero` in
138    /// this same file correctly recognises, which contradicts the documented meaning of
139    /// `is_positive` as strictly greater than zero.
140    #[test]
141    fn test_signed_zero_is_not_positive() {
142        let zero = Ubig::<8>::from(0_u128);
143        assert!(!zero.is_not_zero());
144        assert_eq!(zero.signum(), Sign::Zero);
145        assert!(!zero.is_positive());
146        assert!(!zero.is_negative());
147
148        for value in [1_u128, 2, u64::MAX as u128, u128::MAX] {
149            let big = Ubig::<8>::from(value);
150            assert!(big.is_not_zero(), "{value}");
151            assert_eq!(big.signum(), Sign::Positive, "{value}");
152            assert!(big.is_positive(), "{value}");
153            assert!(!big.is_negative(), "{value}");
154        }
155    }
156
157    /// The non zero variant is positive by construction.
158    #[test]
159    fn test_non_zero_signed_is_positive() {
160        for value in [1_u128, 2, u128::MAX] {
161            let big = NonZeroUbig::<8>::new_u128(value).unwrap();
162            assert_eq!(big.signum(), Sign::Positive, "{value}");
163            assert!(big.is_positive(), "{value}");
164        }
165    }
166
167    /// Both halves of `cmp`: the word count decides, and only then the words themselves.
168    ///
169    /// The word loop runs from the most significant word down, so a difference high up decides the
170    /// comparison even when a lower word points the other way.
171    #[test]
172    fn test_cmp() {
173        assert_eq!(cmp(&[], &[]), Ordering::Equal);
174        assert_eq!(cmp(&[], &[1]), Ordering::Less);
175        assert_eq!(cmp(&[1], &[]), Ordering::Greater);
176
177        // A longer value is larger, whatever its words: neither has a leading zero word.
178        assert_eq!(cmp(&[1], &[usize::MAX, 1]), Ordering::Less);
179        assert_eq!(cmp(&[usize::MAX, 1], &[1]), Ordering::Greater);
180
181        assert_eq!(cmp(&[7], &[7]), Ordering::Equal);
182        assert_eq!(cmp(&[6], &[7]), Ordering::Less);
183        assert_eq!(cmp(&[7], &[6]), Ordering::Greater);
184
185        // The most significant word decides, against what the least significant one says.
186        assert_eq!(cmp(&[usize::MAX, 1], &[0, 2]), Ordering::Less);
187        assert_eq!(cmp(&[0, 2], &[usize::MAX, 1]), Ordering::Greater);
188
189        // Equal in the most significant word, decided by the one below it.
190        assert_eq!(cmp(&[1, 2, 3], &[2, 2, 3]), Ordering::Less);
191        assert_eq!(cmp(&[3, 2, 3], &[3, 1, 3]), Ordering::Greater);
192        assert_eq!(cmp(&[1, 2, 3], &[1, 2, 3]), Ordering::Equal);
193    }
194}