use super::add::{read, read_small, significant_len};
use super::{ByteOrder, Number};
use core::cmp::Ordering;
use core::hash::{Hash, Hasher};
#[derive(Clone, Copy)]
pub(super) struct Magnitude<'a> {
pub(super) bytes: &'a [u8],
pub(super) big_endian: bool,
pub(super) negative: bool,
}
impl<'a> Magnitude<'a> {
#[inline(always)]
pub(super) fn of(n: &'a Number) -> Self {
let v = n.value.as_slice();
let (bytes, big_endian) = if n.byte_order == ByteOrder::BigEndian {
(&v[v.len() - significant_len::<true>(v)..], true)
} else {
(&v[..significant_len::<false>(v)], false)
};
Magnitude {
bytes,
big_endian,
negative: n.negative && !bytes.is_empty(),
}
}
#[inline(always)]
pub(super) fn small(self) -> u128 {
debug_assert!(self.bytes.len() <= 16);
let order = if self.big_endian {
ByteOrder::BigEndian
} else {
ByteOrder::LittleEndian
};
unsafe { read_small(self.bytes, order) }
}
}
#[inline(always)]
unsafe fn limb<const BE: bool>(v: &[u8], k: usize) -> u64 {
unsafe {
if BE {
u64::from_be_bytes(read(v.as_ptr().add(v.len() - 8 * k - 8)))
} else {
u64::from_le_bytes(read(v.as_ptr().add(8 * k)))
}
}
}
#[inline(always)]
fn top<const BE: bool>(v: &[u8]) -> u64 {
let r = v.len() % 8;
let bytes = if BE { &v[..r] } else { &v[v.len() - r..] };
if BE {
bytes.iter().fold(0, |t, &b| (t << 8) | u64::from(b))
} else {
bytes.iter().rev().fold(0, |t, &b| (t << 8) | u64::from(b))
}
}
#[inline(always)]
fn cmp_limbs<const ABE: bool, const BBE: bool>(a: &[u8], b: &[u8]) -> Ordering {
debug_assert_eq!(a.len(), b.len());
let ordering = top::<ABE>(a).cmp(&top::<BBE>(b));
if ordering != Ordering::Equal {
return ordering;
}
let mut k = a.len() / 8;
if ABE == BBE && k > 4 {
for _ in 0..4 {
k -= 1;
let (x, y) = unsafe { (limb::<ABE>(a, k), limb::<BBE>(b, k)) };
if x != y {
return x.cmp(&y);
}
}
if a == b {
return Ordering::Equal;
}
}
while k > 0 {
k -= 1;
let (x, y) = unsafe { (limb::<ABE>(a, k), limb::<BBE>(b, k)) };
if x != y {
return x.cmp(&y);
}
}
Ordering::Equal
}
#[inline(always)]
fn eq_limbs<const ABE: bool, const BBE: bool>(a: &[u8], b: &[u8]) -> bool {
debug_assert_eq!(a.len(), b.len());
let mut differ = top::<ABE>(a) ^ top::<BBE>(b);
for k in 0..a.len() / 8 {
differ |= unsafe { limb::<ABE>(a, k) ^ limb::<BBE>(b, k) };
}
differ == 0
}
#[inline(always)]
pub(super) fn cmp_magnitudes(a: Magnitude, b: Magnitude) -> Ordering {
let (x, y) = (a.bytes, b.bytes);
match x.len().cmp(&y.len()) {
Ordering::Equal => {}
unequal => return unequal,
}
match (a.big_endian, b.big_endian) {
(true, true) => x.cmp(y),
(false, false) => cmp_limbs::<false, false>(x, y),
(true, false) => cmp_limbs::<true, false>(x, y),
(false, true) => cmp_limbs::<false, true>(x, y),
}
}
#[inline(always)]
fn signed(negative_a: bool, negative_b: bool, magnitudes: impl FnOnce() -> Ordering) -> Ordering {
match (negative_a, negative_b) {
(false, true) => Ordering::Greater,
(true, false) => Ordering::Less,
(false, false) => magnitudes(),
(true, true) => magnitudes().reverse(),
}
}
#[inline(always)]
fn small_pair(a: &Number, b: &Number) -> Option<((bool, u128), (bool, u128))> {
if a.value.len() > 16 || b.value.len() > 16 {
return None;
}
let (x, y) = unsafe {
(
read_small(&a.value, a.byte_order),
read_small(&b.value, b.byte_order),
)
};
Some(((a.negative && x != 0, x), (b.negative && y != 0, y)))
}
impl PartialEq for Number {
#[inline]
fn eq(&self, other: &Number) -> bool {
if let Some((x, y)) = small_pair(self, other) {
return x == y;
}
let (a, b) = (Magnitude::of(self), Magnitude::of(other));
let (x, y) = (a.bytes, b.bytes);
if a.negative != b.negative || x.len() != y.len() {
return false;
}
match (a.big_endian, b.big_endian) {
(true, true) | (false, false) => x == y,
(true, false) => eq_limbs::<true, false>(x, y),
(false, true) => eq_limbs::<false, true>(x, y),
}
}
}
impl Eq for Number {}
impl PartialOrd for Number {
#[inline]
fn partial_cmp(&self, other: &Number) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Number {
#[inline]
fn cmp(&self, other: &Number) -> Ordering {
if let Some(((na, x), (nb, y))) = small_pair(self, other) {
return signed(na, nb, || x.cmp(&y));
}
let (a, b) = (Magnitude::of(self), Magnitude::of(other));
signed(a.negative, b.negative, || cmp_magnitudes(a, b))
}
}
impl Hash for Number {
fn hash<H: Hasher>(&self, state: &mut H) {
let m = Magnitude::of(self);
let v = m.bytes;
state.write_u8(u8::from(m.negative));
state.write_usize(v.len());
if !m.big_endian {
for chunk in v.chunks(HASH_CHUNK) {
state.write(chunk);
}
return;
}
let mut buffer = [0u8; HASH_CHUNK];
for chunk in v.rchunks(HASH_CHUNK) {
let out = &mut buffer[..chunk.len()];
for (o, &b) in out.iter_mut().zip(chunk.iter().rev()) {
*o = b;
}
state.write(out);
}
}
}
const HASH_CHUNK: usize = 256;
#[inline(always)]
pub(super) fn cmp_primitive(n: &Number, negative: bool, m: u128) -> Ordering {
let a = Magnitude::of(n);
signed(a.negative, negative, || {
if a.bytes.len() > 16 {
Ordering::Greater
} else {
a.small().cmp(&m)
}
})
}
macro_rules! compare_with {
($($t:ident),* $(,)?) => {$(
impl PartialEq<$t> for Number {
#[inline]
fn eq(&self, other: &$t) -> bool {
let (negative, m) = parts!(*other, $t);
cmp_primitive(self, negative, m) == Ordering::Equal
}
}
impl PartialOrd<$t> for Number {
#[inline]
fn partial_cmp(&self, other: &$t) -> Option<Ordering> {
let (negative, m) = parts!(*other, $t);
Some(cmp_primitive(self, negative, m))
}
}
)*};
}
macro_rules! parts {
($v:expr, u8) => {
(false, u128::from($v))
};
($v:expr, u16) => {
(false, u128::from($v))
};
($v:expr, u32) => {
(false, u128::from($v))
};
($v:expr, u64) => {
(false, u128::from($v))
};
($v:expr, u128) => {
(false, $v)
};
($v:expr, usize) => {
(false, $v as u128)
};
($v:expr, $t:ident) => {{
let v = $v;
(v < 0, v.unsigned_abs() as u128)
}};
}
pub(super) use parts;
compare_with!(
u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize
);