Skip to main content

oxinum_int/native/
convert.rs

1//! Primitive integer conversions for [`BigInt`] and [`BigUint`].
2//!
3//! - `From<u{8,16,32,64,128}>`, `From<i{8,16,32,64,128}>`, `From<usize>`,
4//!   `From<isize>` for `BigInt`.
5//! - Same `From` set for `BigUint` (signed flavors converted via
6//!   `unsigned_abs` and rejected only when the value is strictly negative —
7//!   `From` is infallible by signature, so negative signed inputs are *not*
8//!   provided as `From`; use `TryFrom` instead).
9//! - `TryFrom<&BigInt>` / `TryFrom<&BigUint>` for the same primitives, with
10//!   range checks returning [`OxiNumError::Overflow`] on out-of-range.
11
12use super::int::BigInt;
13use super::uint::BigUint;
14use crate::OxiNumError;
15use oxinum_core::Sign;
16
17// ---------------------------------------------------------------------------
18// From<*> for BigUint
19// ---------------------------------------------------------------------------
20
21impl From<u8> for BigUint {
22    #[inline]
23    fn from(v: u8) -> Self {
24        BigUint::from_u64(v as u64)
25    }
26}
27
28impl From<u16> for BigUint {
29    #[inline]
30    fn from(v: u16) -> Self {
31        BigUint::from_u64(v as u64)
32    }
33}
34
35impl From<u32> for BigUint {
36    #[inline]
37    fn from(v: u32) -> Self {
38        BigUint::from_u64(v as u64)
39    }
40}
41
42impl From<u64> for BigUint {
43    #[inline]
44    fn from(v: u64) -> Self {
45        BigUint::from_u64(v)
46    }
47}
48
49impl From<u128> for BigUint {
50    #[inline]
51    fn from(v: u128) -> Self {
52        BigUint::from_u128(v)
53    }
54}
55
56impl From<usize> for BigUint {
57    #[inline]
58    fn from(v: usize) -> Self {
59        BigUint::from_u64(v as u64)
60    }
61}
62
63// ---------------------------------------------------------------------------
64// From<*> for BigInt (signed and unsigned primitives)
65// ---------------------------------------------------------------------------
66
67#[inline]
68fn from_signed<T>(value: T) -> BigInt
69where
70    T: SignedPrimitive,
71{
72    if value.is_negative_signed() {
73        BigInt {
74            sign: Sign::Negative,
75            mag: BigUint::from_u128(value.unsigned_abs_u128()),
76        }
77        .normalize_zero()
78    } else {
79        BigInt {
80            sign: Sign::Positive,
81            mag: BigUint::from_u128(value.unsigned_abs_u128()),
82        }
83        .normalize_zero()
84    }
85}
86
87#[inline]
88fn from_unsigned(value: u128) -> BigInt {
89    BigInt {
90        sign: Sign::Positive,
91        mag: BigUint::from_u128(value),
92    }
93    .normalize_zero()
94}
95
96/// Implementation detail: capture `unsigned_abs` as a `u128` for all signed
97/// primitive widths so that we have one code path. The `i128::MIN` case is
98/// handled correctly because `i128::unsigned_abs() -> u128` returns
99/// `1u128 << 127`.
100trait SignedPrimitive {
101    fn is_negative_signed(&self) -> bool;
102    fn unsigned_abs_u128(&self) -> u128;
103}
104
105macro_rules! impl_signed_prim {
106    ($($t:ty),*) => {
107        $(
108            impl SignedPrimitive for $t {
109                #[inline]
110                fn is_negative_signed(&self) -> bool {
111                    *self < 0
112                }
113                #[inline]
114                fn unsigned_abs_u128(&self) -> u128 {
115                    self.unsigned_abs() as u128
116                }
117            }
118        )*
119    };
120}
121
122impl_signed_prim!(i8, i16, i32, i64, isize);
123
124// i128 needs special handling because `unsigned_abs` already returns u128.
125impl SignedPrimitive for i128 {
126    #[inline]
127    fn is_negative_signed(&self) -> bool {
128        *self < 0
129    }
130    #[inline]
131    fn unsigned_abs_u128(&self) -> u128 {
132        self.unsigned_abs()
133    }
134}
135
136impl BigInt {
137    /// Internal helper: ensure canonical-zero after construction without
138    /// re-borrowing through `canonicalize`. Kept private to this module.
139    #[inline]
140    fn normalize_zero(mut self) -> Self {
141        if self.mag.is_zero() {
142            self.sign = Sign::Positive;
143        }
144        self
145    }
146}
147
148impl From<u8> for BigInt {
149    #[inline]
150    fn from(v: u8) -> Self {
151        from_unsigned(v as u128)
152    }
153}
154
155impl From<u16> for BigInt {
156    #[inline]
157    fn from(v: u16) -> Self {
158        from_unsigned(v as u128)
159    }
160}
161
162impl From<u32> for BigInt {
163    #[inline]
164    fn from(v: u32) -> Self {
165        from_unsigned(v as u128)
166    }
167}
168
169impl From<u64> for BigInt {
170    #[inline]
171    fn from(v: u64) -> Self {
172        from_unsigned(v as u128)
173    }
174}
175
176impl From<u128> for BigInt {
177    #[inline]
178    fn from(v: u128) -> Self {
179        from_unsigned(v)
180    }
181}
182
183impl From<usize> for BigInt {
184    #[inline]
185    fn from(v: usize) -> Self {
186        from_unsigned(v as u128)
187    }
188}
189
190impl From<i8> for BigInt {
191    #[inline]
192    fn from(v: i8) -> Self {
193        from_signed(v)
194    }
195}
196
197impl From<i16> for BigInt {
198    #[inline]
199    fn from(v: i16) -> Self {
200        from_signed(v)
201    }
202}
203
204impl From<i32> for BigInt {
205    #[inline]
206    fn from(v: i32) -> Self {
207        from_signed(v)
208    }
209}
210
211impl From<i64> for BigInt {
212    #[inline]
213    fn from(v: i64) -> Self {
214        from_signed(v)
215    }
216}
217
218impl From<i128> for BigInt {
219    #[inline]
220    fn from(v: i128) -> Self {
221        from_signed(v)
222    }
223}
224
225impl From<isize> for BigInt {
226    #[inline]
227    fn from(v: isize) -> Self {
228        from_signed(v)
229    }
230}
231
232impl From<BigUint> for BigInt {
233    #[inline]
234    fn from(m: BigUint) -> Self {
235        BigInt::from_parts(Sign::Positive, m)
236    }
237}
238
239impl From<&BigUint> for BigInt {
240    #[inline]
241    fn from(m: &BigUint) -> Self {
242        BigInt::from_parts(Sign::Positive, m.clone())
243    }
244}
245
246// ---------------------------------------------------------------------------
247// TryFrom<&BigUint> for primitives
248// ---------------------------------------------------------------------------
249
250#[inline]
251fn biguint_to_u128(value: &BigUint) -> Result<u128, OxiNumError> {
252    let limbs = value.as_limbs();
253    match limbs.len() {
254        0 => Ok(0),
255        1 => Ok(limbs[0] as u128),
256        2 => Ok(((limbs[1] as u128) << 64) | (limbs[0] as u128)),
257        _ => Err(OxiNumError::Overflow(
258            format!("BigUint with {} limbs does not fit in u128", limbs.len()).into(),
259        )),
260    }
261}
262
263macro_rules! impl_try_from_biguint_unsigned {
264    ($($t:ident),*) => {
265        $(
266            impl TryFrom<&BigUint> for $t {
267                type Error = OxiNumError;
268                fn try_from(value: &BigUint) -> Result<Self, Self::Error> {
269                    let v = biguint_to_u128(value)?;
270                    if v > Self::MAX as u128 {
271                        return Err(OxiNumError::Overflow(format!(
272                            "BigUint {value} does not fit in {}",
273                            stringify!($t)
274                        ).into()));
275                    }
276                    Ok(v as Self)
277                }
278            }
279
280            impl TryFrom<BigUint> for $t {
281                type Error = OxiNumError;
282                fn try_from(value: BigUint) -> Result<Self, Self::Error> {
283                    Self::try_from(&value)
284                }
285            }
286        )*
287    };
288}
289
290// u128 needs a custom impl because comparing to u128::MAX is identity.
291impl TryFrom<&BigUint> for u128 {
292    type Error = OxiNumError;
293    fn try_from(value: &BigUint) -> Result<Self, Self::Error> {
294        biguint_to_u128(value)
295    }
296}
297
298impl TryFrom<BigUint> for u128 {
299    type Error = OxiNumError;
300    fn try_from(value: BigUint) -> Result<Self, Self::Error> {
301        biguint_to_u128(&value)
302    }
303}
304
305impl_try_from_biguint_unsigned!(u8, u16, u32, u64, usize);
306
307macro_rules! impl_try_from_biguint_signed {
308    ($($t:ident),*) => {
309        $(
310            impl TryFrom<&BigUint> for $t {
311                type Error = OxiNumError;
312                fn try_from(value: &BigUint) -> Result<Self, Self::Error> {
313                    let v = biguint_to_u128(value)?;
314                    if v > Self::MAX as u128 {
315                        return Err(OxiNumError::Overflow(format!(
316                            "BigUint {value} does not fit in {}",
317                            stringify!($t)
318                        ).into()));
319                    }
320                    Ok(v as Self)
321                }
322            }
323
324            impl TryFrom<BigUint> for $t {
325                type Error = OxiNumError;
326                fn try_from(value: BigUint) -> Result<Self, Self::Error> {
327                    Self::try_from(&value)
328                }
329            }
330        )*
331    };
332}
333
334// i128 special: cannot exceed i128::MAX (= 2^127 - 1).
335impl TryFrom<&BigUint> for i128 {
336    type Error = OxiNumError;
337    fn try_from(value: &BigUint) -> Result<Self, Self::Error> {
338        let v = biguint_to_u128(value)?;
339        if v > i128::MAX as u128 {
340            return Err(OxiNumError::Overflow(
341                format!("BigUint {value} does not fit in i128").into(),
342            ));
343        }
344        Ok(v as i128)
345    }
346}
347
348impl TryFrom<BigUint> for i128 {
349    type Error = OxiNumError;
350    fn try_from(value: BigUint) -> Result<Self, Self::Error> {
351        Self::try_from(&value)
352    }
353}
354
355impl_try_from_biguint_signed!(i8, i16, i32, i64, isize);
356
357// ---------------------------------------------------------------------------
358// TryFrom<&BigInt> for primitives
359// ---------------------------------------------------------------------------
360
361macro_rules! impl_try_from_bigint_unsigned {
362    ($($t:ident),*) => {
363        $(
364            impl TryFrom<&BigInt> for $t {
365                type Error = OxiNumError;
366                fn try_from(value: &BigInt) -> Result<Self, Self::Error> {
367                    if value.is_negative() {
368                        return Err(OxiNumError::Overflow(format!(
369                            "negative BigInt {value} cannot fit in {}",
370                            stringify!($t)
371                        ).into()));
372                    }
373                    <$t>::try_from(value.magnitude())
374                }
375            }
376
377            impl TryFrom<BigInt> for $t {
378                type Error = OxiNumError;
379                fn try_from(value: BigInt) -> Result<Self, Self::Error> {
380                    Self::try_from(&value)
381                }
382            }
383        )*
384    };
385}
386
387impl_try_from_bigint_unsigned!(u8, u16, u32, u64, u128, usize);
388
389// Signed targets require both:
390//   - the magnitude doesn't exceed T::MAX (for positive values)
391//   - or magnitude equals T::MAX as u128 + 1 (for negative MIN-boundary case)
392macro_rules! impl_try_from_bigint_signed {
393    ($($t:ident),*) => {
394        $(
395            impl TryFrom<&BigInt> for $t {
396                type Error = OxiNumError;
397                fn try_from(value: &BigInt) -> Result<Self, Self::Error> {
398                    let mag_u128 = biguint_to_u128(value.magnitude())?;
399                    let max_pos = <$t>::MAX as u128;
400                    let min_abs = (<$t>::MAX as u128) + 1; // |T::MIN| = 2^(bits-1)
401                    if value.is_negative() {
402                        if mag_u128 > min_abs {
403                            return Err(OxiNumError::Overflow(format!(
404                                "BigInt {value} does not fit in {} (too negative)",
405                                stringify!($t)
406                            ).into()));
407                        }
408                        if mag_u128 == min_abs {
409                            return Ok(<$t>::MIN);
410                        }
411                        // mag_u128 <= max_pos; negate safely.
412                        let pos = mag_u128 as i128;
413                        return Ok(-pos as $t);
414                    }
415                    if mag_u128 > max_pos {
416                        return Err(OxiNumError::Overflow(format!(
417                            "BigInt {value} does not fit in {} (too positive)",
418                            stringify!($t)
419                        ).into()));
420                    }
421                    Ok(mag_u128 as $t)
422                }
423            }
424
425            impl TryFrom<BigInt> for $t {
426                type Error = OxiNumError;
427                fn try_from(value: BigInt) -> Result<Self, Self::Error> {
428                    Self::try_from(&value)
429                }
430            }
431        )*
432    };
433}
434
435impl_try_from_bigint_signed!(i8, i16, i32, i64, isize);
436
437// i128 special-case (handles 2^127 = -i128::MIN distinctly).
438impl TryFrom<&BigInt> for i128 {
439    type Error = OxiNumError;
440    fn try_from(value: &BigInt) -> Result<Self, Self::Error> {
441        let mag_u128 = biguint_to_u128(value.magnitude())?;
442        let max_pos = i128::MAX as u128;
443        let min_abs = (i128::MAX as u128) + 1; // 2^127
444        if value.is_negative() {
445            if mag_u128 > min_abs {
446                return Err(OxiNumError::Overflow(
447                    format!("BigInt {value} does not fit in i128 (too negative)").into(),
448                ));
449            }
450            if mag_u128 == min_abs {
451                return Ok(i128::MIN);
452            }
453            return Ok(-(mag_u128 as i128));
454        }
455        if mag_u128 > max_pos {
456            return Err(OxiNumError::Overflow(
457                format!("BigInt {value} does not fit in i128 (too positive)").into(),
458            ));
459        }
460        Ok(mag_u128 as i128)
461    }
462}
463
464impl TryFrom<BigInt> for i128 {
465    type Error = OxiNumError;
466    fn try_from(value: BigInt) -> Result<Self, Self::Error> {
467        Self::try_from(&value)
468    }
469}
470
471#[cfg(test)]
472mod tests {
473    use super::*;
474
475    #[test]
476    fn from_i64_min_roundtrip() {
477        let n = BigInt::from(i64::MIN);
478        assert_eq!(format!("{n}"), format!("{}", i64::MIN));
479        let back: i64 = i64::try_from(&n).expect("i64::MIN fits in i64");
480        assert_eq!(back, i64::MIN);
481    }
482
483    #[test]
484    fn from_i128_min_roundtrip() {
485        let n = BigInt::from(i128::MIN);
486        let back: i128 = i128::try_from(&n).expect("i128::MIN fits in i128");
487        assert_eq!(back, i128::MIN);
488    }
489
490    #[test]
491    fn u64_max_roundtrip() {
492        let n = BigInt::from(u64::MAX);
493        let back: u64 = u64::try_from(&n).expect("u64::MAX fits in u64");
494        assert_eq!(back, u64::MAX);
495    }
496
497    #[test]
498    fn try_from_overflow_signed() {
499        let n = BigInt::from(u64::MAX);
500        // i64 cannot hold u64::MAX.
501        assert!(i64::try_from(&n).is_err());
502    }
503
504    #[test]
505    fn try_from_negative_into_unsigned_errors() {
506        let n = BigInt::from(-1i64);
507        assert!(u32::try_from(&n).is_err());
508    }
509}