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);