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}