Skip to main content

oxinum_int/native/
int.rs

1//! Native `BigInt` — signed arbitrary-precision integer built as a
2//! `Sign` + `BigUint` magnitude pair.
3//!
4//! # Invariants
5//!
6//! - The canonical zero is the ONLY zero: when `mag.is_zero()`, the sign
7//!   MUST be `Sign::Positive`. This is enforced by every public constructor
8//!   and after every arithmetic operation via [`BigInt::canonicalize`].
9//!   Consequence: `+0 == -0` for `Eq`, `Ord`, and `Hash`.
10//! - Magnitude follows [`super::uint::BigUint`] invariants (little-endian
11//!   limbs, no trailing zeros).
12//!
13//! # Examples
14//!
15//! ```
16//! use oxinum_int::native::{BigInt, BigUint};
17//! use oxinum_core::Sign;
18//!
19//! let a = BigInt::from(-5i64);
20//! let b = BigInt::from(7i64);
21//! assert_eq!(&a + &b, BigInt::from(2i64));
22//! assert_eq!(a.signum(), Sign::Negative);
23//! assert!((-BigInt::zero()).is_zero());
24//! ```
25
26use super::uint::BigUint;
27use core::cmp::Ordering;
28use oxinum_core::Sign;
29use std::fmt;
30
31/// Native arbitrary-precision signed integer.
32///
33/// Represented as a [`Sign`] plus a non-negative [`BigUint`] magnitude. The
34/// canonical-zero invariant guarantees a unique representation of zero (always
35/// `Sign::Positive` + `BigUint::ZERO`).
36#[derive(Clone)]
37#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
38pub struct BigInt {
39    #[cfg_attr(feature = "serde", serde(with = "sign_serde"))]
40    pub(super) sign: Sign,
41    pub(super) mag: BigUint,
42}
43
44/// Serde helper for [`Sign`], which doesn't itself derive `Serialize` /
45/// `Deserialize` in `dashu-base 0.4`. We encode the sign as a `bool`,
46/// matching `dashu`'s own `From<Sign> for bool` convention
47/// (`true == Negative`).
48#[cfg(feature = "serde")]
49mod sign_serde {
50    use oxinum_core::Sign;
51    use serde::{Deserialize, Deserializer, Serialize, Serializer};
52
53    pub(super) fn serialize<S: Serializer>(s: &Sign, ser: S) -> Result<S::Ok, S::Error> {
54        bool::from(*s).serialize(ser)
55    }
56
57    pub(super) fn deserialize<'de, D: Deserializer<'de>>(de: D) -> Result<Sign, D::Error> {
58        bool::deserialize(de).map(Sign::from)
59    }
60}
61
62impl Default for BigInt {
63    #[inline]
64    fn default() -> Self {
65        Self::ZERO
66    }
67}
68
69impl BigInt {
70    /// The canonical zero value (`+0`).
71    pub const ZERO: BigInt = BigInt {
72        sign: Sign::Positive,
73        mag: BigUint::ZERO,
74    };
75
76    /// Construct a zero `BigInt`.
77    ///
78    /// # Examples
79    ///
80    /// ```
81    /// use oxinum_int::native::BigInt;
82    /// assert!(BigInt::zero().is_zero());
83    /// ```
84    #[inline]
85    pub fn zero() -> Self {
86        Self::ZERO
87    }
88
89    /// Construct a `BigInt` equal to `1`.
90    ///
91    /// # Examples
92    ///
93    /// ```
94    /// use oxinum_int::native::BigInt;
95    /// assert!(BigInt::one().is_one());
96    /// ```
97    #[inline]
98    pub fn one() -> Self {
99        Self {
100            sign: Sign::Positive,
101            mag: BigUint::one(),
102        }
103    }
104
105    /// Construct from an existing `(sign, magnitude)` pair. Re-canonicalizes
106    /// zero so that `BigInt::from_parts(Sign::Negative, BigUint::ZERO)` is
107    /// indistinguishable from `BigInt::zero()`.
108    ///
109    /// # Examples
110    ///
111    /// ```
112    /// use oxinum_int::native::{BigInt, BigUint};
113    /// use oxinum_core::Sign;
114    /// let a = BigInt::from_parts(Sign::Negative, BigUint::from_u64(7));
115    /// assert_eq!(format!("{a}"), "-7");
116    ///
117    /// // -0 canonicalizes to +0.
118    /// let neg_zero = BigInt::from_parts(Sign::Negative, BigUint::ZERO);
119    /// assert_eq!(neg_zero, BigInt::zero());
120    /// ```
121    pub fn from_parts(sign: Sign, mag: BigUint) -> Self {
122        let mut out = Self { sign, mag };
123        out.canonicalize();
124        out
125    }
126
127    /// Decompose into `(sign, magnitude)`. For zero, the returned sign is
128    /// always `Sign::Positive`.
129    ///
130    /// # Examples
131    ///
132    /// ```
133    /// use oxinum_int::native::{BigInt, BigUint};
134    /// use oxinum_core::Sign;
135    /// let n = BigInt::from(-42i64);
136    /// let (s, m) = n.into_parts();
137    /// assert_eq!(s, Sign::Negative);
138    /// assert_eq!(m, BigUint::from_u64(42));
139    /// ```
140    #[inline]
141    pub fn into_parts(self) -> (Sign, BigUint) {
142        (self.sign, self.mag)
143    }
144
145    /// Returns the sign of this number. For zero, returns `Sign::Positive`
146    /// (canonical-zero invariant).
147    #[inline]
148    pub fn sign(&self) -> Sign {
149        self.sign
150    }
151
152    /// Returns the sign as a method that follows the standard `signum`
153    /// convention: `+1`, `-1`, or `0`-as-`Positive`. Use [`Self::sign`] for
154    /// the raw sign enum.
155    ///
156    /// # Examples
157    ///
158    /// ```
159    /// use oxinum_int::native::BigInt;
160    /// use oxinum_core::Sign;
161    /// assert_eq!(BigInt::from(5i64).signum(), Sign::Positive);
162    /// assert_eq!(BigInt::from(-3i64).signum(), Sign::Negative);
163    /// // Zero is canonically positive in dashu_base::Sign.
164    /// assert_eq!(BigInt::zero().signum(), Sign::Positive);
165    /// ```
166    #[inline]
167    pub fn signum(&self) -> Sign {
168        self.sign
169    }
170
171    /// Returns a reference to the magnitude (always non-negative).
172    #[inline]
173    pub fn magnitude(&self) -> &BigUint {
174        &self.mag
175    }
176
177    /// Returns the absolute value as a non-negative `BigInt`.
178    ///
179    /// # Examples
180    ///
181    /// ```
182    /// use oxinum_int::native::BigInt;
183    /// assert_eq!(BigInt::from(-42i64).abs(), BigInt::from(42i64));
184    /// assert_eq!(BigInt::from(42i64).abs(), BigInt::from(42i64));
185    /// ```
186    pub fn abs(&self) -> Self {
187        Self {
188            sign: Sign::Positive,
189            mag: self.mag.clone(),
190        }
191    }
192
193    /// Returns `true` if this value is zero.
194    #[inline]
195    pub fn is_zero(&self) -> bool {
196        self.mag.is_zero()
197    }
198
199    /// Returns `true` if this value is `+1` (sign positive AND magnitude one).
200    #[inline]
201    pub fn is_one(&self) -> bool {
202        self.sign == Sign::Positive && self.mag.is_one()
203    }
204
205    /// Returns `true` if this value is strictly negative.
206    #[inline]
207    pub fn is_negative(&self) -> bool {
208        self.sign == Sign::Negative && !self.mag.is_zero()
209    }
210
211    /// Returns `true` if this value is strictly positive.
212    #[inline]
213    pub fn is_positive(&self) -> bool {
214        self.sign == Sign::Positive && !self.mag.is_zero()
215    }
216
217    /// Force the canonical-zero invariant: if `mag.is_zero()` then
218    /// `sign = Sign::Positive`. This is a no-op for non-zero values.
219    ///
220    /// Called by every constructor and after every arithmetic operation.
221    #[inline]
222    pub(crate) fn canonicalize(&mut self) {
223        if self.mag.is_zero() {
224            self.sign = Sign::Positive;
225        }
226    }
227}
228
229// ---------------------------------------------------------------------------
230// Equality, ordering, hashing
231// ---------------------------------------------------------------------------
232
233impl PartialEq for BigInt {
234    #[inline]
235    fn eq(&self, other: &Self) -> bool {
236        // Thanks to the canonical-zero invariant, `+0 == -0` is automatic:
237        // both have sign Positive and empty magnitude.
238        self.sign == other.sign && self.mag == other.mag
239    }
240}
241
242impl Eq for BigInt {}
243
244impl std::hash::Hash for BigInt {
245    #[inline]
246    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
247        // Hash on (sign, mag) — works correctly because zero is canonical.
248        // For zero, the sign is always Positive, so +0 and -0 are unhashable
249        // distinctly.
250        self.sign.hash(state);
251        self.mag.hash(state);
252    }
253}
254
255impl Ord for BigInt {
256    fn cmp(&self, other: &Self) -> Ordering {
257        match (self.sign, other.sign) {
258            // Same sign: compare magnitudes. For Negative, reverse the
259            // ordering (larger magnitude means smaller value).
260            (Sign::Positive, Sign::Positive) => self.mag.cmp(&other.mag),
261            (Sign::Negative, Sign::Negative) => other.mag.cmp(&self.mag),
262            // Mixed signs: if either side is zero, they are equal (canonical
263            // zero would have made both Positive — so reaching here means at
264            // least one side is non-zero). The strictly positive value wins.
265            (Sign::Positive, Sign::Negative) => {
266                // self is +x (x can be 0 but canonical-zero ensures other.mag
267                // is non-zero whenever other.sign == Negative).
268                if other.mag.is_zero() {
269                    // Cannot happen under canonical-zero, but stay defensive.
270                    Ordering::Equal
271                } else {
272                    Ordering::Greater
273                }
274            }
275            (Sign::Negative, Sign::Positive) => {
276                if self.mag.is_zero() {
277                    Ordering::Equal
278                } else {
279                    Ordering::Less
280                }
281            }
282        }
283    }
284}
285
286impl PartialOrd for BigInt {
287    #[inline]
288    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
289        Some(self.cmp(other))
290    }
291}
292
293// ---------------------------------------------------------------------------
294// Display / Debug
295// ---------------------------------------------------------------------------
296
297impl fmt::Display for BigInt {
298    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
299        if self.sign == Sign::Negative && !self.mag.is_zero() {
300            f.write_str("-")?;
301        }
302        fmt::Display::fmt(&self.mag, f)
303    }
304}
305
306impl fmt::Debug for BigInt {
307    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
308        let sign_str = if self.sign == Sign::Negative && !self.mag.is_zero() {
309            "-"
310        } else {
311            ""
312        };
313        write!(f, "BigInt({sign_str}{})", self.mag)
314    }
315}
316
317#[cfg(test)]
318mod tests {
319    use super::*;
320
321    #[test]
322    fn zero_is_canonical_under_from_parts() {
323        let pz = BigInt::from_parts(Sign::Positive, BigUint::ZERO);
324        let nz = BigInt::from_parts(Sign::Negative, BigUint::ZERO);
325        assert_eq!(pz, nz);
326        assert_eq!(pz.sign(), Sign::Positive);
327        assert_eq!(nz.sign(), Sign::Positive);
328    }
329
330    #[test]
331    fn is_zero_one_negative_positive() {
332        assert!(BigInt::zero().is_zero());
333        assert!(BigInt::one().is_one());
334        assert!(!BigInt::zero().is_negative());
335        assert!(!BigInt::zero().is_positive());
336        let neg = BigInt::from_parts(Sign::Negative, BigUint::from_u64(7));
337        assert!(neg.is_negative());
338        assert!(!neg.is_positive());
339    }
340
341    #[test]
342    fn ord_negative_less_than_positive() {
343        let n = BigInt::from_parts(Sign::Negative, BigUint::from_u64(100));
344        let p = BigInt::from_parts(Sign::Positive, BigUint::from_u64(1));
345        assert!(n < p);
346    }
347
348    #[test]
349    fn ord_two_negatives_larger_mag_is_smaller() {
350        let a = BigInt::from_parts(Sign::Negative, BigUint::from_u64(100));
351        let b = BigInt::from_parts(Sign::Negative, BigUint::from_u64(1));
352        assert!(a < b);
353    }
354}