Skip to main content

a/math/numbers/
cmp.rs

1//! Numbers compared by value: `==` and `!=`, `<`, `<=`, `>` and `>=`, `cmp`, `max`, `min` and
2//! `clamp`, with each other and with the primitive integers on the right, and a hash that agrees
3//! with `==`.
4//!
5//! Two numbers are equal when they are the same integer, whatever their byte orders and however
6//! their magnitudes are stored: zero bytes above the most significant non-zero byte count for
7//! nothing, an empty vector is zero, and so is a zero magnitude with `negative` set. The derived
8//! comparisons would compare the stored bytes instead: `[1]` big-endian and `[1]` little-endian
9//! would differ in their byte orders, and `[1, 0]` big-endian (256) would be below `[2]`.
10//!
11//! Magnitudes of the same length in the same byte order are compared as byte strings, which the
12//! standard library does with `memcmp`: big-endian, the first byte that differs is the most
13//! significant one, so lexicographic order is numeric order. Otherwise they are compared limb by
14//! limb from the top, each read in its own byte order.
15
16use super::add::{read, read_small, significant_len};
17use super::{ByteOrder, Number};
18use core::cmp::Ordering;
19use core::hash::{Hash, Hasher};
20
21/// The significant bytes of a number's magnitude, without the zero bytes above them, in the
22/// number's byte order, and its sign, never negative for zero.
23#[derive(Clone, Copy)]
24pub(super) struct Magnitude<'a> {
25    /// The significant bytes: the last ones of the vector big-endian, the first little-endian.
26    pub(super) bytes: &'a [u8],
27    /// Whether the bytes are big-endian.
28    pub(super) big_endian: bool,
29    /// Whether the number is below zero.
30    pub(super) negative: bool,
31}
32
33impl<'a> Magnitude<'a> {
34    /// The magnitude and sign of `n`.
35    #[inline(always)]
36    pub(super) fn of(n: &'a Number) -> Self {
37        let v = n.value.as_slice();
38        let (bytes, big_endian) = if n.byte_order == ByteOrder::BigEndian {
39            (&v[v.len() - significant_len::<true>(v)..], true)
40        } else {
41            (&v[..significant_len::<false>(v)], false)
42        };
43        Magnitude {
44            bytes,
45            big_endian,
46            negative: n.negative && !bytes.is_empty(),
47        }
48    }
49
50    /// The magnitude, which must have at most 16 bytes, as a `u128`.
51    #[inline(always)]
52    pub(super) fn small(self) -> u128 {
53        debug_assert!(self.bytes.len() <= 16);
54        let order = if self.big_endian {
55            ByteOrder::BigEndian
56        } else {
57            ByteOrder::LittleEndian
58        };
59        // SAFETY: the magnitude has at most 16 bytes, as the caller guarantees.
60        unsafe { read_small(self.bytes, order) }
61    }
62}
63
64/// Limb `k` of the magnitude `v` in the layout `BE`, the least significant limb being limb 0.
65///
66/// # Safety
67///
68/// `v` must hold at least `8 * k + 8` bytes.
69#[inline(always)]
70unsafe fn limb<const BE: bool>(v: &[u8], k: usize) -> u64 {
71    // SAFETY: as the caller guarantees, `[8k, 8k + 8)` lies inside `v`, and so does
72    // `[n - 8k - 8, n - 8k)`.
73    unsafe {
74        if BE {
75            u64::from_be_bytes(read(v.as_ptr().add(v.len() - 8 * k - 8)))
76        } else {
77            u64::from_le_bytes(read(v.as_ptr().add(8 * k)))
78        }
79    }
80}
81
82/// The `v.len() % 8` most significant bytes of the magnitude `v` in the layout `BE`, those above
83/// its whole limbs, as a number.
84#[inline(always)]
85fn top<const BE: bool>(v: &[u8]) -> u64 {
86    let r = v.len() % 8;
87    let bytes = if BE { &v[..r] } else { &v[v.len() - r..] };
88    if BE {
89        bytes.iter().fold(0, |t, &b| (t << 8) | u64::from(b))
90    } else {
91        bytes.iter().rev().fold(0, |t, &b| (t << 8) | u64::from(b))
92    }
93}
94
95/// Compares the magnitudes `a` and `b`, of the same length, laid out as `ABE` and `BBE` say, from
96/// the most significant limb down.
97#[inline(always)]
98fn cmp_limbs<const ABE: bool, const BBE: bool>(a: &[u8], b: &[u8]) -> Ordering {
99    debug_assert_eq!(a.len(), b.len());
100    let ordering = top::<ABE>(a).cmp(&top::<BBE>(b));
101    if ordering != Ordering::Equal {
102        return ordering;
103    }
104    let mut k = a.len() / 8;
105    if ABE == BBE && k > 4 {
106        // The same layout: the top limbs, where random magnitudes differ, then the whole
107        // magnitudes compared for equality as byte strings, which the standard library does with
108        // vector instructions; only magnitudes that differ below their top limbs are scanned
109        // further. Measured on an Apple M4 Max, equal magnitudes of 1 KiB took 107 cycles so
110        // against 199 limb by limb, and those of 16 KiB 1,233 against 2,801.
111        for _ in 0..4 {
112            k -= 1;
113            // SAFETY: both hold `a.len()` bytes, at least `8k + 8`.
114            let (x, y) = unsafe { (limb::<ABE>(a, k), limb::<BBE>(b, k)) };
115            if x != y {
116                return x.cmp(&y);
117            }
118        }
119        if a == b {
120            return Ordering::Equal;
121        }
122    }
123    while k > 0 {
124        k -= 1;
125        // SAFETY: both hold `a.len()` bytes, at least `8k + 8`.
126        let (x, y) = unsafe { (limb::<ABE>(a, k), limb::<BBE>(b, k)) };
127        if x != y {
128            return x.cmp(&y);
129        }
130    }
131    Ordering::Equal
132}
133
134/// Whether the magnitudes `a` and `b`, of the same length, laid out as `ABE` and `BBE` say (not
135/// both the same), are equal.
136#[inline(always)]
137fn eq_limbs<const ABE: bool, const BBE: bool>(a: &[u8], b: &[u8]) -> bool {
138    debug_assert_eq!(a.len(), b.len());
139    let mut differ = top::<ABE>(a) ^ top::<BBE>(b);
140    for k in 0..a.len() / 8 {
141        // SAFETY: both hold `a.len()` bytes, at least `8k + 8`.
142        differ |= unsafe { limb::<ABE>(a, k) ^ limb::<BBE>(b, k) };
143    }
144    differ == 0
145}
146
147/// Compares the magnitudes of `a` and `b`, whatever their byte orders.
148#[inline(always)]
149pub(super) fn cmp_magnitudes(a: Magnitude, b: Magnitude) -> Ordering {
150    let (x, y) = (a.bytes, b.bytes);
151    match x.len().cmp(&y.len()) {
152        Ordering::Equal => {}
153        unequal => return unequal,
154    }
155    match (a.big_endian, b.big_endian) {
156        (true, true) => x.cmp(y),
157        (false, false) => cmp_limbs::<false, false>(x, y),
158        (true, false) => cmp_limbs::<true, false>(x, y),
159        (false, true) => cmp_limbs::<false, true>(x, y),
160    }
161}
162
163/// Compares two signed values, `negative_a` and `negative_b` false for zero, by the order of
164/// their magnitudes, which `magnitudes` gives.
165#[inline(always)]
166fn signed(negative_a: bool, negative_b: bool, magnitudes: impl FnOnce() -> Ordering) -> Ordering {
167    match (negative_a, negative_b) {
168        (false, true) => Ordering::Greater,
169        (true, false) => Ordering::Less,
170        (false, false) => magnitudes(),
171        (true, true) => magnitudes().reverse(),
172    }
173}
174
175/// The signs and magnitudes of `a` and `b` when both vectors hold at most 16 bytes, read as
176/// `u128`s without looking for their significant bytes; signs are not set for zero.
177#[inline(always)]
178fn small_pair(a: &Number, b: &Number) -> Option<((bool, u128), (bool, u128))> {
179    if a.value.len() > 16 || b.value.len() > 16 {
180        return None;
181    }
182    // SAFETY: both vectors hold at most 16 bytes.
183    let (x, y) = unsafe {
184        (
185            read_small(&a.value, a.byte_order),
186            read_small(&b.value, b.byte_order),
187        )
188    };
189    Some(((a.negative && x != 0, x), (b.negative && y != 0, y)))
190}
191
192impl PartialEq for Number {
193    /// Whether the numbers are the same integer, whatever their byte orders and however their
194    /// magnitudes are stored.
195    ///
196    /// # Examples
197    ///
198    /// ```
199    /// use a::math::numbers::{ByteOrder, Number};
200    ///
201    /// let big = Number::new_from_u32(0x0102_0304, ByteOrder::BigEndian);
202    /// let little = Number::new_from_u32(0x0102_0304, ByteOrder::LittleEndian);
203    /// assert_eq!(big, little);
204    ///
205    /// // Zero bytes above the most significant one count for nothing, and zero has no sign.
206    /// assert_eq!(Number::new_signed(vec![0], true, ByteOrder::BigEndian), Number::default());
207    /// assert_ne!(Number::new_from_i8(-1, ByteOrder::BigEndian), Number::new_from_i8(1, ByteOrder::BigEndian));
208    /// ```
209    #[inline]
210    fn eq(&self, other: &Number) -> bool {
211        if let Some((x, y)) = small_pair(self, other) {
212            return x == y;
213        }
214        let (a, b) = (Magnitude::of(self), Magnitude::of(other));
215        let (x, y) = (a.bytes, b.bytes);
216        if a.negative != b.negative || x.len() != y.len() {
217            return false;
218        }
219        match (a.big_endian, b.big_endian) {
220            (true, true) | (false, false) => x == y,
221            (true, false) => eq_limbs::<true, false>(x, y),
222            (false, true) => eq_limbs::<false, true>(x, y),
223        }
224    }
225}
226
227impl Eq for Number {}
228
229impl PartialOrd for Number {
230    /// Compares the numbers by value, as [`Ord::cmp`] does.
231    #[inline]
232    fn partial_cmp(&self, other: &Number) -> Option<Ordering> {
233        Some(self.cmp(other))
234    }
235}
236
237impl Ord for Number {
238    /// Compares the numbers by value: every negative number is below zero, and zero below every
239    /// positive one, whatever their byte orders and however their magnitudes are stored.
240    ///
241    /// # Examples
242    ///
243    /// ```
244    /// use a::math::numbers::{ByteOrder, Number};
245    ///
246    /// let mut v = vec![
247    ///     Number::new_from_i32(300, ByteOrder::BigEndian),
248    ///     Number::new_from_i32(-5, ByteOrder::LittleEndian),
249    ///     Number::new_from_i32(7, ByteOrder::LittleEndian),
250    /// ];
251    /// v.sort();
252    /// assert_eq!(v, [-5, 7, 300]);
253    /// assert!(Number::new_from_i32(-300, ByteOrder::BigEndian) < -5);
254    /// ```
255    #[inline]
256    fn cmp(&self, other: &Number) -> Ordering {
257        if let Some(((na, x), (nb, y))) = small_pair(self, other) {
258            return signed(na, nb, || x.cmp(&y));
259        }
260        let (a, b) = (Magnitude::of(self), Magnitude::of(other));
261        signed(a.negative, b.negative, || cmp_magnitudes(a, b))
262    }
263}
264
265impl Hash for Number {
266    /// Hashes the number by value, so that numbers that are equal hash alike, whatever their byte
267    /// orders and however their magnitudes are stored: the sign, the significant length, then the
268    /// significant bytes of the magnitude, the least significant first.
269    ///
270    /// # Examples
271    ///
272    /// ```
273    /// use a::math::numbers::{ByteOrder, Number};
274    /// use std::collections::HashSet;
275    ///
276    /// let mut seen = HashSet::new();
277    /// seen.insert(Number::new_from_u64(1 << 40, ByteOrder::BigEndian));
278    /// assert!(seen.contains(&Number::new(vec![0, 0, 0, 0, 0, 1, 0], ByteOrder::LittleEndian)));
279    /// ```
280    fn hash<H: Hasher>(&self, state: &mut H) {
281        let m = Magnitude::of(self);
282        let v = m.bytes;
283        state.write_u8(u8::from(m.negative));
284        state.write_usize(v.len());
285        // The magnitude little-endian, in chunks of `HASH_CHUNK` bytes from the least significant
286        // one, so that both byte orders hash the same bytes in the same calls: a big-endian
287        // magnitude is turned around a chunk at a time in a buffer.
288        if !m.big_endian {
289            for chunk in v.chunks(HASH_CHUNK) {
290                state.write(chunk);
291            }
292            return;
293        }
294        let mut buffer = [0u8; HASH_CHUNK];
295        for chunk in v.rchunks(HASH_CHUNK) {
296            let out = &mut buffer[..chunk.len()];
297            for (o, &b) in out.iter_mut().zip(chunk.iter().rev()) {
298                *o = b;
299            }
300            state.write(out);
301        }
302    }
303}
304
305/// The bytes of a magnitude hashed in one call to the hasher: measured on an Apple M4 Max with
306/// the standard library's SipHash, a magnitude of 1 KiB took 835 cycles so against 1,976 as
307/// a `u64` a limb.
308const HASH_CHUNK: usize = 256;
309
310/// Compares the number `n` with a primitive integer, given by its sign `negative`, never set for
311/// zero, and its magnitude `m`.
312#[inline(always)]
313pub(super) fn cmp_primitive(n: &Number, negative: bool, m: u128) -> Ordering {
314    let a = Magnitude::of(n);
315    signed(a.negative, negative, || {
316        if a.bytes.len() > 16 {
317            Ordering::Greater
318        } else {
319            a.small().cmp(&m)
320        }
321    })
322}
323
324/// Comparisons of numbers with a primitive integer type, the number on the left.
325///
326/// Not the other way round: `impl PartialEq<Number> for i32` would leave `i32` two types to be
327/// compared with, and code that leaves the other type to inference, such as `v == vec![]` for a
328/// `Vec<i32>`, would no longer compile wherever this crate is linked.
329macro_rules! compare_with {
330    ($($t:ident),* $(,)?) => {$(
331        impl PartialEq<$t> for Number {
332            /// Whether the number is the integer `other`.
333            #[inline]
334            fn eq(&self, other: &$t) -> bool {
335                let (negative, m) = parts!(*other, $t);
336                cmp_primitive(self, negative, m) == Ordering::Equal
337            }
338        }
339
340        impl PartialOrd<$t> for Number {
341            /// Compares the number with the integer `other` by value.
342            #[inline]
343            fn partial_cmp(&self, other: &$t) -> Option<Ordering> {
344                let (negative, m) = parts!(*other, $t);
345                Some(cmp_primitive(self, negative, m))
346            }
347        }
348    )*};
349}
350
351/// The sign, never set for zero, and the magnitude of a value of a primitive integer type.
352macro_rules! parts {
353    ($v:expr, u8) => {
354        (false, u128::from($v))
355    };
356    ($v:expr, u16) => {
357        (false, u128::from($v))
358    };
359    ($v:expr, u32) => {
360        (false, u128::from($v))
361    };
362    ($v:expr, u64) => {
363        (false, u128::from($v))
364    };
365    ($v:expr, u128) => {
366        (false, $v)
367    };
368    ($v:expr, usize) => {
369        (false, $v as u128)
370    };
371    ($v:expr, $t:ident) => {{
372        let v = $v;
373        (v < 0, v.unsigned_abs() as u128)
374    }};
375}
376pub(super) use parts;
377
378compare_with!(
379    u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize
380);