dashu-int 0.6.0

Arbitrary-precision integer math library for Rust, balancing ergonomics and efficiency. Provides UBig and IBig (unsigned and signed), with small values inlined on the stack. Efficient arithmetic, modular arithmetic, and number theory (pow, ilog, gcd, gcd_ext); two's-complement bit operations; parsing and formatting in base 2-36; optional serde, rand, num-traits, rkyv, and zeroize.
Documentation
//! Primitive integral types.

use crate::{
    arch::word::{DoubleWord, SignedDoubleWord, SignedWord, Word},
    Sign::{self, *},
};
use core::{
    convert::{TryFrom, TryInto},
    fmt::Debug,
    hint::unreachable_unchecked,
    mem,
    ops::{Add, Div, Mul, Shl, Shr, Sub},
};
use dashu_base::ConversionError;

/// Cast [Word] to [DoubleWord].
#[inline]
pub const fn extend_word(word: Word) -> DoubleWord {
    word as DoubleWord
}

/// Cast [Word] to [SignedDoubleWord].
#[inline]
pub const fn signed_extend_word(word: Word) -> SignedDoubleWord {
    word as SignedDoubleWord
}

/// Create a [DoubleWord] from two `Word`s.
#[inline]
pub const fn double_word(low: Word, high: Word) -> DoubleWord {
    extend_word(low) | extend_word(high) << WORD_BITS
}

/// Split a [DoubleWord] into (low, high) parts
#[inline]
pub const fn split_dword(dw: DoubleWord) -> (Word, Word) {
    (dw as Word, (dw >> WORD_BITS) as Word)
}

/// Split a [SignedDoubleWord] into (low, high) parts, where the high part is signed
/// and low part is unsigned
#[inline]
pub const fn split_signed_dword(dw: SignedDoubleWord) -> (Word, SignedWord) {
    (dw as Word, (dw >> WORD_BITS) as SignedWord)
}

/// Get the low part of a `DoubleWord` if the high part is zero
#[inline]
pub const fn shrink_dword(dw: DoubleWord) -> Option<Word> {
    let (lo, hi) = split_dword(dw);
    if hi == 0 {
        Some(lo)
    } else {
        None
    }
}

/// Get the lowest double word of a slice of words
///
/// Note that then length is only checked in the debug mode.
#[inline(always)]
pub fn lowest_dword(words: &[Word]) -> DoubleWord {
    debug_assert!(words.len() >= 2);

    // SAFETY: length checked by the assertion above
    unsafe {
        let lo = *words.get_unchecked(0);
        let hi = *words.get_unchecked(1);
        double_word(lo, hi)
    }
}

/// Get the highest double word of a slice of words
///
/// Note that then length is only checked in the debug mode.
#[inline(always)]
pub fn highest_dword(words: &[Word]) -> DoubleWord {
    let len = words.len();
    debug_assert!(len >= 2);

    // SAFETY: length checked by the assertion above
    unsafe {
        let lo = *words.get_unchecked(len - 2);
        let hi = *words.get_unchecked(len - 1);
        double_word(lo, hi)
    }
}

/// Split the the highest word from the word array.
#[inline]
pub const fn split_hi_word(words: &[Word]) -> (Word, &[Word]) {
    debug_assert!(words.len() >= 2);
    match words.split_last() {
        Some((hi, lo)) => (*hi, lo),
        // SAFETY: the words length is checked by the assertion
        None => unsafe { unreachable_unchecked() },
    }
}

/// Locate the top non-zero word in a slice. It returns the position of the
/// word added by one for convenience, if the input is zero, then 0 is returned.
#[inline]
pub fn locate_top_word_plus_one(words: &[Word]) -> usize {
    for pos in (0..words.len()).rev() {
        if words[pos] != 0 {
            return pos + 1;
        }
    }
    0
}

pub trait PrimitiveUnsigned
where
    Self: Copy,
    Self: Debug,
    Self: Default,
    Self: From<u8>,
    Self: TryFrom<Word>,
    Self: TryFrom<DoubleWord>,
    Self: TryInto<Word>,
    Self: TryInto<DoubleWord>,
    Self: TryInto<usize>,
    Self: Eq,
    Self: Add<Output = Self>,
    Self: Div<Output = Self>,
    Self: Mul<Output = Self>,
    Self: Sub<Output = Self>,
    Self: Shl<u32, Output = Self>,
    Self: Shr<u32, Output = Self>,
{
    const BYTE_SIZE: usize = mem::size_of::<Self>();
    const BIT_SIZE: u32 = 8 * Self::BYTE_SIZE as u32;
    type ByteRepr: AsRef<[u8]> + AsMut<[u8]>;

    fn to_le_bytes(self) -> Self::ByteRepr;
    fn from_le_bytes(repr: Self::ByteRepr) -> Self;
    fn leading_zeros(self) -> u32;
}

pub trait PrimitiveSigned
where
    Self: Copy,
    Self: TryFrom<DoubleWord>,
    Self::Unsigned: PrimitiveUnsigned,
    Self::Unsigned: TryFrom<Self>,
    Self::Unsigned: TryInto<Self>,
{
    type Unsigned;

    fn to_sign_magnitude(self) -> (Sign, Self::Unsigned);
    fn try_from_sign_magnitude(sign: Sign, mag: Self::Unsigned) -> Result<Self, ConversionError>;
}

macro_rules! impl_primitive_unsigned {
    ($t:ty) => {
        impl PrimitiveUnsigned for $t {
            type ByteRepr = [u8; Self::BYTE_SIZE];

            #[inline]
            fn to_le_bytes(self) -> Self::ByteRepr {
                self.to_le_bytes()
            }

            #[inline]
            fn from_le_bytes(repr: Self::ByteRepr) -> Self {
                Self::from_le_bytes(repr)
            }

            #[inline]
            fn leading_zeros(self) -> u32 {
                self.leading_zeros()
            }
        }
    };
}

macro_rules! impl_primitive_signed {
    ($t:ty, $u:ty) => {
        impl PrimitiveSigned for $t {
            type Unsigned = $u;

            #[inline]
            fn to_sign_magnitude(self) -> (Sign, Self::Unsigned) {
                if self >= 0 {
                    (Positive, self as Self::Unsigned)
                } else {
                    (Negative, (self as Self::Unsigned).wrapping_neg())
                }
            }

            #[inline]
            fn try_from_sign_magnitude(
                sign: Sign,
                mag: Self::Unsigned,
            ) -> Result<Self, ConversionError> {
                match sign {
                    Positive => mag.try_into().map_err(|_| ConversionError::OutOfBounds),
                    Negative => {
                        let x = mag.wrapping_neg() as Self;
                        if x <= 0 {
                            Ok(x)
                        } else {
                            Err(ConversionError::OutOfBounds)
                        }
                    }
                }
            }
        }
    };
}

impl_primitive_unsigned!(u8);
impl_primitive_unsigned!(u16);
impl_primitive_unsigned!(u32);
impl_primitive_unsigned!(u64);
impl_primitive_unsigned!(u128);
impl_primitive_unsigned!(usize);

impl_primitive_signed!(i8, u8);
impl_primitive_signed!(i16, u16);
impl_primitive_signed!(i32, u32);
impl_primitive_signed!(i64, u64);
impl_primitive_signed!(i128, u128);
impl_primitive_signed!(isize, usize);

pub const WORD_BITS: u32 = Word::BIT_SIZE;
pub const WORD_BITS_USIZE: usize = WORD_BITS as usize;
pub const WORD_BYTES: usize = Word::BYTE_SIZE;
pub const DWORD_BITS: u32 = DoubleWord::BIT_SIZE;
pub const DWORD_BITS_USIZE: usize = DWORD_BITS as usize;
pub const DWORD_BYTES: usize = DoubleWord::BYTE_SIZE;

#[inline]
pub fn word_from_le_bytes_partial<const ONE_PAD: bool>(bytes: &[u8]) -> Word {
    let pad: u8 = if ONE_PAD { 0xff } else { 0 };
    let mut word_bytes = [pad; WORD_BYTES];
    word_bytes[..bytes.len()].copy_from_slice(bytes);
    Word::from_le_bytes(word_bytes)
}

#[inline]
pub fn dword_from_le_bytes_partial<const ONE_PAD: bool>(bytes: &[u8]) -> DoubleWord {
    let pad: u8 = if ONE_PAD { 0xff } else { 0 };
    let mut dword_bytes = [pad; DWORD_BYTES];
    dword_bytes[..bytes.len()].copy_from_slice(bytes);
    DoubleWord::from_le_bytes(dword_bytes)
}

#[inline]
pub fn word_from_be_bytes_partial<const ONE_PAD: bool>(bytes: &[u8]) -> Word {
    let pad: u8 = if ONE_PAD { 0xff } else { 0 };
    let mut word_bytes = [pad; WORD_BYTES];
    word_bytes[WORD_BYTES - bytes.len()..].copy_from_slice(bytes);
    Word::from_be_bytes(word_bytes)
}

#[inline]
pub fn dword_from_be_bytes_partial<const ONE_PAD: bool>(bytes: &[u8]) -> DoubleWord {
    let pad: u8 = if ONE_PAD { 0xff } else { 0 };
    let mut dword_bytes = [pad; DWORD_BYTES];
    dword_bytes[DWORD_BYTES - bytes.len()..].copy_from_slice(bytes);
    DoubleWord::from_be_bytes(dword_bytes)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_bits_bytes() {
        assert_eq!(u8::BIT_SIZE, 8);
        assert_eq!(u64::BIT_SIZE, 64);
        assert_eq!(u8::BYTE_SIZE, 1);
        assert_eq!(u64::BYTE_SIZE, 8);
    }

    #[test]
    fn test_word_from_le_bytes_partial() {
        assert_eq!(word_from_le_bytes_partial::<false>(&[1, 2]), 0x0201);
    }

    #[test]
    fn test_word_from_be_bytes_partial() {
        assert_eq!(word_from_be_bytes_partial::<false>(&[1, 2]), 0x0102);
    }

    #[test]
    fn test_double_word() {
        assert_eq!(DoubleWord::BIT_SIZE, 2 * WORD_BITS);
        assert_eq!(split_dword(double_word(3, 4)), (3, 4));
    }

    #[test]
    fn test_to_sign_magnitude() {
        assert_eq!(0.to_sign_magnitude(), (Positive, 0u32));
        assert_eq!(5.to_sign_magnitude(), (Positive, 5u32));
        assert_eq!(0x7fffffff.to_sign_magnitude(), (Positive, 0x7fffffffu32));
        assert_eq!((-0x80000000).to_sign_magnitude(), (Negative, 0x80000000u32));
    }

    #[test]
    fn test_try_from_sign_magnitude() {
        assert_eq!(i32::try_from_sign_magnitude(Positive, 0), Ok(0));
        assert_eq!(i32::try_from_sign_magnitude(Positive, 5), Ok(5));
        assert_eq!(i32::try_from_sign_magnitude(Positive, 0x7fffffff), Ok(0x7fffffff));
        assert!(i32::try_from_sign_magnitude(Positive, 0x80000000).is_err());
        assert_eq!(i32::try_from_sign_magnitude(Negative, 0), Ok(0));
        assert_eq!(i32::try_from_sign_magnitude(Negative, 5), Ok(-5));
        assert_eq!(i32::try_from_sign_magnitude(Negative, 0x7fffffff), Ok(-0x7fffffff));
        assert_eq!(i32::try_from_sign_magnitude(Negative, 0x80000000), Ok(-0x80000000));
        assert!(i32::try_from_sign_magnitude(Negative, 0x80000001).is_err());
        assert!(i32::try_from_sign_magnitude(Negative, 0xffffffff).is_err());
    }
}