dashu-int 0.6.2

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
//! Signed big integer.

use crate::{
    repr::{Repr, TypedRepr, TypedReprRef},
    Sign, UBig,
};

/// A signed arbitrary precision integer.
///
/// `IBig` represents an arbitrarily large signed integer. It shares [`UBig`]'s representation — the
/// sign bit is folded in without extra allocation — so it has the same small-integer inlining (values
/// that fit in a [`DoubleWord`](crate::DoubleWord) stay on the stack) and the same niche bit, making
/// [`Option<IBig>`] the same size as [`IBig`].
///
/// For the full discussion — construction, parsing, printing, and the memory layout — see the
/// [user guide](https://zyxin.xyz/dashu/types.html).
///
/// # Examples
///
/// Parsing and printing (base 2–36 is supported for string/literal parsing):
///
/// ```
/// // parsing
/// # use dashu_base::ParseError;
/// # use dashu_int::{IBig, Word};
/// let a = IBig::from(408580953453092208335085386466371u128);
/// let b = IBig::from(-0x1231abcd4134i64);
/// let c = IBig::from_str_radix("a2a123bbb127779cccc123", 32)?;
/// let d = IBig::from_str_radix("-1231abcd4134", 16)?;
/// assert_eq!(a, c);
/// assert_eq!(b, d);
///
/// // printing
/// assert_eq!(format!("{}", IBig::from(12)), "12");
/// assert_eq!(format!("{:#X}", IBig::from(-0xabcd)), "-0xABCD");
/// if Word::BITS == 64 {
///     // number of digits to display depends on the word size
///     assert_eq!(
///         format!("{:?}", IBig::NEG_ONE << 1000),
///         "-1071508607186267320..4386837205668069376"
///     );
/// }
/// # Ok::<(), ParseError>(())
/// ```
///
/// The niche bit makes `Option<IBig>` free, and `IBig` matches `UBig` in size:
///
/// ```
/// # use dashu_int::{IBig, UBig};
/// use core::mem::size_of;
/// assert_eq!(size_of::<IBig>(), size_of::<UBig>());
/// assert_eq!(size_of::<IBig>(), size_of::<Option<IBig>>());
/// ```
#[derive(Eq, Hash, PartialEq)]
#[repr(transparent)]
pub struct IBig(pub(crate) Repr);

impl IBig {
    #[inline]
    pub(crate) const fn as_sign_repr(&self) -> (Sign, TypedReprRef<'_>) {
        self.0.as_sign_typed()
    }

    #[inline]
    pub(crate) fn into_sign_repr(self) -> (Sign, TypedRepr) {
        self.0.into_sign_typed()
    }

    /// Get the raw representation in [Word][crate::Word]s.
    ///
    /// If the number is zero, then empty slice will be returned.
    ///
    /// # Examples
    ///
    /// ```
    /// # use dashu_int::{IBig, Sign};
    /// assert_eq!(IBig::ZERO.as_sign_words(), (Sign::Positive, &[] as &[_]));
    /// assert_eq!(IBig::NEG_ONE.as_sign_words().0, Sign::Negative);
    /// assert_eq!(IBig::NEG_ONE.as_sign_words().1, &[1]);
    /// ```
    #[inline]
    pub fn as_sign_words(&self) -> (Sign, &[crate::Word]) {
        self.0.as_sign_slice()
    }

    /// Create an [IBig] from the [Sign] and a sequence of [Word][crate::Word]s (interpreted
    /// as the magnitude in little-endian order). This is the inverse of
    /// [as_sign_words][IBig::as_sign_words].
    ///
    /// Leading zero words in the input are trimmed. If the value is zero, the sign will be
    /// normalized to [Sign::Positive] (consistent with [sign][IBig::sign]).
    ///
    /// # Examples
    ///
    /// ```
    /// # use dashu_int::{IBig, Sign};
    /// assert_eq!(IBig::from_sign_words(Sign::Positive, &[] as &[_]), IBig::ZERO);
    /// assert_eq!(IBig::from_sign_words(Sign::Negative, &[1]), IBig::NEG_ONE);
    ///
    /// // the sign of zero is normalized to positive
    /// assert_eq!(IBig::from_sign_words(Sign::Negative, &[] as &[_]), IBig::ZERO);
    ///
    /// // roundtrip with as_sign_words
    /// let x = IBig::from(-0x1234_5678_9abc_def0i64);
    /// let (sign, words) = x.as_sign_words();
    /// assert_eq!(IBig::from_sign_words(sign, words), x);
    /// ```
    #[inline]
    pub fn from_sign_words(sign: Sign, words: &[crate::Word]) -> Self {
        Self(Repr::from_buffer(words.into()).with_sign(sign))
    }

    /// Get the sign of the number. Zero value has a positive sign.
    ///
    /// # Examples
    ///
    /// ```
    /// # use dashu_int::{IBig, Sign};
    /// assert_eq!(IBig::ZERO.sign(), Sign::Positive);
    /// assert_eq!(IBig::from(2).sign(), Sign::Positive);
    /// assert_eq!(IBig::from(-3).sign(), Sign::Negative);
    /// ```
    #[inline]
    pub const fn sign(&self) -> Sign {
        self.0.sign()
    }

    /// Convert the [IBig] into its [Sign] and [UBig] magnitude
    ///
    /// # Examples
    ///
    /// ```
    /// # use dashu_int::{IBig, Sign, UBig};
    /// assert_eq!(IBig::ZERO.into_parts(), (Sign::Positive, UBig::ZERO));
    /// assert_eq!(IBig::ONE.into_parts(), (Sign::Positive, UBig::ONE));
    /// assert_eq!(IBig::NEG_ONE.into_parts(), (Sign::Negative, UBig::ONE));
    /// ```
    #[inline]
    pub fn into_parts(self) -> (Sign, UBig) {
        let sign = self.0.sign();
        let mag = self.0.with_sign(Sign::Positive);
        (sign, UBig(mag))
    }

    /// Create an [IBig] from the [Sign] and [UBig] magnitude
    ///
    /// # Examples
    ///
    /// ```
    /// # use dashu_int::{IBig, Sign, UBig};
    /// assert_eq!(IBig::from_parts(Sign::Positive, UBig::ZERO), IBig::ZERO);
    /// assert_eq!(IBig::from_parts(Sign::Positive, UBig::ONE), IBig::ONE);
    /// assert_eq!(IBig::from_parts(Sign::Negative, UBig::ONE), IBig::NEG_ONE);
    /// ```
    #[inline]
    pub fn from_parts(sign: Sign, magnitude: UBig) -> Self {
        IBig(magnitude.0.with_sign(sign))
    }

    /// Create an IBig in a const context.
    ///
    /// The magnitude is limited to a [DoubleWord][crate::DoubleWord].
    ///
    /// # Examples
    ///
    /// ```
    /// # use dashu_int::{IBig, Sign, UBig};
    /// const ONE: IBig = IBig::from_parts_const(Sign::Positive, 1);
    /// assert_eq!(ONE, IBig::ONE);
    /// const NEG_ONE: IBig = IBig::from_parts_const(Sign::Negative, 1);
    /// assert_eq!(NEG_ONE, IBig::NEG_ONE);
    /// ```
    #[inline]
    pub const fn from_parts_const(sign: Sign, dword: crate::DoubleWord) -> Self {
        Self(Repr::from_dword(dword).with_sign(sign))
    }

    /// Create an IBig from an i64.
    ///
    /// This function is const on 32-bit and 64-bit targets.
    ///
    /// # Examples
    ///
    /// ```
    /// # use dashu_int::IBig;
    /// assert_eq!(IBig::from_i64(-42), IBig::from(-42i64));
    /// assert_eq!(IBig::from_i64(42), IBig::from(42i64));
    /// ```
    #[cfg(not(any(target_pointer_width = "16", force_bits = "16")))]
    #[inline]
    pub const fn from_i64(n: i64) -> Self {
        let sign = if n >= 0 {
            Sign::Positive
        } else {
            Sign::Negative
        };
        let mag = n.unsigned_abs() as crate::DoubleWord;
        Self(Repr::from_dword(mag).with_sign(sign))
    }

    /// Create an IBig from an i64.
    ///
    /// On 16-bit targets `i64` is wider than [`DoubleWord`][crate::DoubleWord], so this delegates
    /// to `From<i64>` and is not `const`; on 32-bit and 64-bit targets the `const` constructor
    /// above is used instead.
    #[cfg(any(target_pointer_width = "16", force_bits = "16"))]
    #[inline]
    pub fn from_i64(n: i64) -> Self {
        Self::from(n)
    }

    /// Create an IBig from a static sequence of [Word][crate::Word]s and a sign.
    ///
    /// See [UBig::from_static_words] for why this method is unsafe. This method
    /// is intended for the use of static creation macros.
    #[doc(hidden)]
    #[inline]
    pub const unsafe fn from_static_words(sign: Sign, words: &'static [crate::Word]) -> Self {
        Self(Repr::from_static_words(words).with_sign(sign))
    }

    /// [IBig] with value 0
    pub const ZERO: Self = Self(Repr::zero());
    /// [IBig] with value 1
    pub const ONE: Self = Self(Repr::one());
    /// [IBig] with value -1
    pub const NEG_ONE: Self = Self(Repr::neg_one());

    /// Check whether the number is 0
    ///
    /// # Examples
    ///
    /// ```
    /// # use dashu_int::IBig;
    /// assert!(IBig::ZERO.is_zero());
    /// assert!(!IBig::ONE.is_zero());
    /// ```
    #[inline]
    pub const fn is_zero(&self) -> bool {
        self.0.is_zero()
    }

    /// Check whether the number is 1
    ///
    /// # Examples
    ///
    /// ```
    /// # use dashu_int::IBig;
    /// assert!(!IBig::ZERO.is_one());
    /// assert!(IBig::ONE.is_one());
    /// ```
    #[inline]
    pub const fn is_one(&self) -> bool {
        self.0.is_one()
    }
}

// This custom implementation is necessary due to https://github.com/rust-lang/rust/issues/98374
impl Clone for IBig {
    #[inline]
    fn clone(&self) -> IBig {
        IBig(self.0.clone())
    }
    #[inline]
    fn clone_from(&mut self, source: &IBig) {
        self.0.clone_from(&source.0)
    }
}

#[cfg(test)]
mod tests {
    use crate::{IBig, Sign};

    #[test]
    fn from_sign_words_normalize() {
        assert_eq!(IBig::from_sign_words(Sign::Positive, &[]), IBig::ZERO);
        assert_eq!(IBig::from_sign_words(Sign::Negative, &[]), IBig::ZERO);
        assert_eq!(IBig::from_sign_words(Sign::Negative, &[0]), IBig::ZERO);
        assert_eq!(IBig::from_sign_words(Sign::Negative, &[1, 0]), IBig::NEG_ONE);
        assert_eq!(IBig::from_sign_words(Sign::Negative, &[1]), IBig::NEG_ONE);
        assert_eq!(IBig::from_sign_words(Sign::Positive, &[1]), IBig::ONE);
        assert_eq!(IBig::from_sign_words(Sign::Negative, &[1, 2]).sign(), Sign::Negative);
    }

    #[test]
    fn sign_words_roundtrip() {
        // values covering the repr boundaries: zero, small, double word, multi-word
        let values = [
            IBig::ZERO,
            IBig::ONE,
            IBig::NEG_ONE,
            IBig::from(u128::MAX),
            -IBig::from(u128::MAX),
            IBig::from(u128::MAX) * IBig::from(0xdead_beef_cafe_babe_u128),
            -(IBig::from(u128::MAX) * IBig::from(0xdead_beef_cafe_babe_u128)),
        ];
        for v in values {
            let (sign, words) = v.as_sign_words();
            assert_eq!(IBig::from_sign_words(sign, words), v, "value: {v:?}");
        }
    }
}