ion_rs/types/
integer.rs

1use crate::element::Element;
2use crate::ion_data::{IonEq, IonOrd};
3use crate::result::{decoding_error, IonError};
4use num_bigint::{BigInt, BigUint, ToBigUint};
5use num_traits::{ToPrimitive, Zero};
6use std::cmp::Ordering;
7use std::fmt::{Display, Formatter};
8use std::ops::{Add, Neg};
9
10/// Provides convenient integer accessors for integer values that are like [`Int`]
11pub trait IntAccess {
12    /// Returns the value as an `i64` if it can be represented as such.
13    ///
14    /// ## Usage
15    /// ```
16    /// # use ion_rs::element::*;
17    /// # use ion_rs::element::*;
18    /// # use ion_rs::types::*;
19    /// # use num_bigint::*;
20    /// let big_int = Int::BigInt(BigInt::from(100));
21    /// let i64_int = Int::I64(100);
22    /// assert_eq!(big_int.as_i64(), i64_int.as_i64());
23    ///
24    /// // works on element too
25    /// let big_elem: Element = big_int.into();
26    /// let i64_elem: Element = i64_int.into();
27    ///
28    /// assert_eq!(big_elem.as_i64(), i64_elem.as_i64());
29    /// ```
30    fn as_i64(&self) -> Option<i64>;
31
32    /// Returns a reference as a [`BigInt`] if it is represented as such.  Note that this
33    /// method may return `None` if the underlying representation *is not* stored in a [`BigInt`]
34    /// such as if it is represented as an `i64` so it is somewhat asymmetric with respect
35    /// to [`IntAccess::as_i64`].
36    ///
37    /// ## Usage
38    /// ```
39    /// # use ion_rs::element::*;
40    /// # use ion_rs::element::*;
41    /// # use ion_rs::types::*;
42    /// # use num_bigint::*;
43    /// # use std::str::FromStr;
44    /// let big_int = Int::BigInt(BigInt::from(100));
45    /// assert_eq!(
46    ///     BigInt::from_str("100").unwrap(),
47    ///     *big_int.as_big_int().unwrap()
48    /// );
49    /// let i64_int = Int::I64(100);
50    /// assert_eq!(None, i64_int.as_big_int());
51    ///
52    /// // works on element too
53    /// let big_elem: Element = big_int.into();
54    /// assert_eq!(
55    ///     BigInt::from_str("100").unwrap(),
56    ///     *big_elem.as_big_int().unwrap()
57    /// );
58    /// let i64_elem: Element = i64_int.into();
59    /// assert_eq!(None, i64_elem.as_big_int());
60    /// ```
61    fn as_big_int(&self) -> Option<&BigInt>;
62}
63
64/// Represents a UInt of any size. Used for reading binary integers and symbol Ids.
65/// Used to represent the unsigned magnitude of Decimal values and fractional seconds.
66#[derive(Debug, Clone)]
67pub enum UInt {
68    U64(u64),
69    BigUInt(BigUint),
70}
71
72impl UInt {
73    /// Compares a [u64] integer with a [BigUint] to see if they are equal. This method never
74    /// allocates. It will always prefer to downgrade a BigUint and compare the two integers as
75    /// u64 values. If this is not possible, then the two numbers cannot be equal anyway.
76    fn cross_representation_eq(m1: u64, m2: &BigUint) -> bool {
77        UInt::cross_representation_cmp(m1, m2) == Ordering::Equal
78    }
79
80    /// Compares a [u64] integer with a [BigUint]. This method never allocates. It will always
81    /// prefer to downgrade a BigUint and compare the two integers as u64 values. If this is
82    /// not possible, then the BigUint is larger than the u64.
83    fn cross_representation_cmp(m1: u64, m2: &BigUint) -> Ordering {
84        // Try to downgrade the BigUint first since that's cheaper than upgrading the u64.
85        if let Some(downgraded_m2) = m2.to_u64() {
86            // If the conversion succeeds, compare the resulting values.
87            return m1.cmp(&downgraded_m2);
88        }
89        // Otherwise, the BigUint must be larger than the u64.
90        Ordering::Less
91    }
92
93    /// Returns the number of digits in the base-10 representation of the UInteger.
94    pub(crate) fn number_of_decimal_digits(&self) -> u64 {
95        match self {
96            UInt::U64(u64_value) => super::num_decimal_digits_in_u64(*u64_value),
97            UInt::BigUInt(big_uint_value) => UInt::calculate_big_uint_digits(big_uint_value),
98        }
99    }
100
101    fn calculate_big_uint_digits(int: &BigUint) -> u64 {
102        if int.is_zero() {
103            return 1;
104        }
105        let mut digits = 0;
106        let mut dividend = int.to_owned();
107        let ten: BigUint = BigUint::from(10u64);
108        while dividend > BigUint::zero() {
109            dividend /= &ten;
110            digits += 1;
111        }
112        digits
113    }
114}
115
116impl PartialEq for UInt {
117    fn eq(&self, other: &Self) -> bool {
118        use UInt::*;
119        match (self, other) {
120            (U64(m1), U64(m2)) => m1 == m2,
121            (BigUInt(m1), BigUInt(m2)) => m1 == m2,
122            (U64(m1), BigUInt(m2)) => UInt::cross_representation_eq(*m1, m2),
123            (BigUInt(m1), U64(m2)) => UInt::cross_representation_eq(*m2, m1),
124        }
125    }
126}
127
128impl Eq for UInt {}
129
130impl PartialOrd for UInt {
131    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
132        Some(self.cmp(other))
133    }
134}
135
136impl Ord for UInt {
137    fn cmp(&self, other: &Self) -> Ordering {
138        use UInt::*;
139        match (self, other) {
140            (U64(m1), U64(m2)) => m1.cmp(m2),
141            (BigUInt(m1), BigUInt(m2)) => m1.cmp(m2),
142            (U64(m1), BigUInt(m2)) => UInt::cross_representation_cmp(*m1, m2),
143            (BigUInt(m1), U64(m2)) => UInt::cross_representation_cmp(*m2, m1).reverse(),
144        }
145    }
146}
147
148impl From<UInt> for Int {
149    fn from(value: UInt) -> Self {
150        match value {
151            UInt::U64(uint) => {
152                if let Ok(signed) = i64::try_from(uint) {
153                    // The u64 was successfully converted to an i64
154                    Int::I64(signed)
155                } else {
156                    // The u64 was slightly too big to be represented as an i64; it required the
157                    // 64th bit to store the magnitude. Up-convert it to a BigInt.
158                    big_integer_from_u64(uint)
159                }
160            }
161            UInt::BigUInt(big_uint) => big_integer_from_big_uint(big_uint),
162        }
163    }
164}
165
166impl From<BigUint> for UInt {
167    fn from(value: BigUint) -> Self {
168        // prefer a compact representation for the magnitude
169        match value.to_u64() {
170            Some(unsigned) => UInt::U64(unsigned),
171            None => UInt::BigUInt(value),
172        }
173    }
174}
175
176impl From<UInt> for BigUint {
177    fn from(value: UInt) -> Self {
178        use UInt::*;
179        match value {
180            U64(m) => BigUint::from(m),
181            BigUInt(m) => m,
182        }
183    }
184}
185
186impl ToBigUint for UInt {
187    fn to_biguint(&self) -> Option<BigUint> {
188        // This implementation never fails, but the trait requires an `Option` return type.
189        Some(self.clone().into())
190    }
191}
192
193// This macro makes it possible to turn unsigned int primitives into a UInteger using `.into()`.
194// Note that it works for both signed and unsigned ints. The resulting UInteger will be the
195// absolute value of the integer being converted.
196macro_rules! impl_uint_from_small_unsigned_int_types {
197    ($($t:ty),*) => ($(
198        impl From<$t> for UInt {
199            fn from(value: $t) -> UInt {
200                UInt::U64(value as u64)
201            }
202        }
203    )*)
204}
205
206impl_uint_from_small_unsigned_int_types!(u8, u16, u32, u64, usize);
207
208macro_rules! impl_uint_from_small_signed_int_types {
209    ($($t:ty),*) => ($(
210        impl From<$t> for UInt {
211            fn from(value: $t) -> UInt {
212                let abs_value = value.unsigned_abs();
213                UInt::U64(abs_value.try_into().unwrap())
214            }
215        }
216    )*)
217}
218
219impl_uint_from_small_signed_int_types!(i8, i16, i32, i64, isize);
220
221impl From<u128> for UInt {
222    fn from(value: u128) -> UInt {
223        UInt::BigUInt(BigUint::from(value))
224    }
225}
226
227impl From<i128> for UInt {
228    fn from(value: i128) -> UInt {
229        UInt::BigUInt(value.abs().to_biguint().unwrap())
230    }
231}
232
233impl From<Int> for UInt {
234    fn from(value: Int) -> Self {
235        match value {
236            Int::I64(i) => i.into(),
237            // num_bigint::BigInt's `into_parts` consumes the BigInt and returns a
238            // (sign: Sign, magnitude: BigUint) tuple. We only care about the magnitude, so we
239            // extract it here with `.1` ---------------v and then convert the BigUint to a UInteger
240            Int::BigInt(i) => i.into_parts().1.into(), // <-- using `.into()`
241        }
242    }
243}
244
245#[inline(never)]
246fn big_integer_from_u64(value: u64) -> Int {
247    Int::BigInt(BigInt::from(value))
248}
249
250#[inline(never)]
251fn big_integer_from_big_uint(value: BigUint) -> Int {
252    Int::BigInt(BigInt::from(value))
253}
254
255impl TryFrom<&UInt> for i64 {
256    type Error = IonError;
257
258    fn try_from(value: &UInt) -> Result<Self, Self::Error> {
259        match value {
260            UInt::U64(uint) => i64::try_from(*uint).or_else(|_| {
261                decoding_error(format!(
262                    "Unsigned integer {uint:?} was too large to be represented as an i64."
263                ))
264            }),
265            UInt::BigUInt(big_uint) => i64::try_from(big_uint).or_else(|_| {
266                decoding_error(format!(
267                    "Unsigned integer {big_uint:?} was too large to be represented as an i64."
268                ))
269            }),
270        }
271    }
272}
273
274impl TryFrom<&UInt> for usize {
275    type Error = IonError;
276
277    fn try_from(value: &UInt) -> Result<Self, Self::Error> {
278        match value {
279            UInt::U64(uint) => usize::try_from(*uint).or_else(|_| {
280                decoding_error(format!(
281                    "Unsigned integer {uint:?} was too large to be represented as an usize."
282                ))
283            }),
284            UInt::BigUInt(big_uint) => usize::try_from(big_uint).or_else(|_| {
285                decoding_error(format!(
286                    "Unsigned integer {big_uint:?} was too large to be represented as an usize."
287                ))
288            }),
289        }
290    }
291}
292
293/// Container for either an integer that can fit in a 64-bit word or an arbitrarily sized
294/// [`BigInt`].
295///
296/// See [`IntAccess`] for common operations.
297#[derive(Debug, Clone)]
298pub enum Int {
299    I64(i64),
300    BigInt(BigInt),
301}
302
303impl Int {
304    /// Compares a [i64] integer with a [BigInt] to see if they are equal. This method never
305    /// allocates. It will always prefer to downgrade a BigUint and compare the two integers as
306    /// u64 values. If this is not possible, then the two numbers cannot be equal anyway.
307    fn cross_representation_eq(m1: i64, m2: &BigInt) -> bool {
308        Int::cross_representation_cmp(m1, m2) == Ordering::Equal
309    }
310
311    /// Compares a [i64] integer with a [BigInt]. This method never allocates. It will always
312    /// prefer to downgrade a BigUint and compare the two integers as u64 values. If this is
313    /// not possible, then the BigUint is larger than the u64.
314    fn cross_representation_cmp(m1: i64, m2: &BigInt) -> Ordering {
315        // Try to downgrade the BigUint first since that's cheaper than upgrading the u64.
316        if let Some(downgraded_m2) = m2.to_i64() {
317            // If the conversion succeeds, compare the resulting values.
318            return m1.cmp(&downgraded_m2);
319        }
320        // Otherwise, the BigUint must be larger than the u64.
321        Ordering::Less
322    }
323}
324
325impl IntAccess for Int {
326    #[inline]
327    fn as_i64(&self) -> Option<i64> {
328        match &self {
329            Int::I64(i) => Some(*i),
330            Int::BigInt(big) => big.to_i64(),
331        }
332    }
333
334    #[inline]
335    fn as_big_int(&self) -> Option<&BigInt> {
336        match &self {
337            Int::I64(_) => None,
338            Int::BigInt(big) => Some(big),
339        }
340    }
341}
342
343impl PartialEq for Int {
344    fn eq(&self, other: &Self) -> bool {
345        use Int::*;
346        match (self, other) {
347            (I64(m1), I64(m2)) => m1 == m2,
348            (BigInt(m1), BigInt(m2)) => m1 == m2,
349            (I64(m1), BigInt(m2)) => Int::cross_representation_eq(*m1, m2),
350            (BigInt(m1), I64(m2)) => Int::cross_representation_eq(*m2, m1),
351        }
352    }
353}
354
355impl Eq for Int {}
356
357impl IonEq for Int {
358    fn ion_eq(&self, other: &Self) -> bool {
359        self == other
360    }
361}
362
363impl IonOrd for Int {
364    fn ion_cmp(&self, other: &Self) -> Ordering {
365        self.cmp(other)
366    }
367}
368
369impl Neg for Int {
370    type Output = Self;
371
372    fn neg(self) -> Self::Output {
373        use Int::*;
374        match self {
375            I64(value) => I64(-value),
376            BigInt(value) => BigInt(-value),
377        }
378    }
379}
380
381impl PartialOrd for Int {
382    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
383        Some(self.cmp(other))
384    }
385}
386
387impl Ord for Int {
388    fn cmp(&self, other: &Self) -> Ordering {
389        use Int::*;
390        match (self, other) {
391            (I64(m1), I64(m2)) => m1.cmp(m2),
392            (BigInt(m1), BigInt(m2)) => m1.cmp(m2),
393            (I64(m1), BigInt(m2)) => Int::cross_representation_cmp(*m1, m2),
394            (BigInt(m1), I64(m2)) => Int::cross_representation_cmp(*m2, m1).reverse(),
395        }
396    }
397}
398
399impl Add<Self> for Int {
400    type Output = Int;
401
402    fn add(self, rhs: Self) -> Self::Output {
403        // The alias 'Big' differentiates the enum variant from the wrapped 'BigInt' type
404        use Int::{BigInt as Big, I64};
405        match (self, rhs) {
406            (I64(this), I64(that)) => {
407                // Try to add the i64s together; if they overflow, upconvert to BigInts
408                match this.checked_add(that) {
409                    Some(result) => I64(result),
410                    None => Big(BigInt::from(this).add(BigInt::from(that))),
411                }
412            }
413            (I64(this), Big(that)) => Big(BigInt::from(this).add(that)),
414            (Big(this), I64(that)) => Big(this.add(&BigInt::from(that))),
415            (Big(this), Big(that)) => Big(this.add(&that)),
416        }
417    }
418}
419
420impl Zero for Int {
421    fn zero() -> Self {
422        Int::I64(0)
423    }
424
425    fn is_zero(&self) -> bool {
426        match self {
427            Int::I64(value) => *value == 0i64,
428            Int::BigInt(value) => value.is_zero(),
429        }
430    }
431}
432
433impl Display for UInt {
434    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
435        match &self {
436            UInt::U64(i) => write!(f, "{i}"),
437            UInt::BigUInt(i) => write!(f, "{i}"),
438        }
439    }
440}
441
442// Trivial conversion to Int::I64 from integers that can safely be converted to an i64
443macro_rules! impl_int_i64_from {
444    ($($t:ty),*) => ($(
445        impl From<$t> for Int {
446            fn from(value: $t) -> Int {
447                let i64_value = i64::from(value);
448                Int::I64(i64_value)
449            }
450        }
451    )*)
452}
453impl_int_i64_from!(u8, u16, u32, i8, i16, i32, i64);
454
455// Conversion to Integer from integer types that may or may not fit in an i64
456macro_rules! impl_int_from {
457    ($($t:ty),*) => ($(
458        impl From<$t> for Int {
459            fn from(value: $t) -> Int {
460                match i64::try_from(value) {
461                    Ok(i64_value) => Int::I64(i64_value),
462                    Err(_) => Int::BigInt(BigInt::from(value))
463                }
464            }
465        }
466    )*)
467}
468
469impl_int_from!(isize, usize, u64);
470
471impl From<BigUint> for Int {
472    fn from(value: BigUint) -> Self {
473        let big_int = BigInt::from(value);
474        Int::BigInt(big_int)
475    }
476}
477
478impl From<BigInt> for Int {
479    fn from(value: BigInt) -> Self {
480        Int::BigInt(value)
481    }
482}
483
484impl IntAccess for Element {
485    fn as_i64(&self) -> Option<i64> {
486        match self.as_int() {
487            Some(any) => any.as_i64(),
488            _ => None,
489        }
490    }
491
492    fn as_big_int(&self) -> Option<&BigInt> {
493        match self.as_int() {
494            Some(any) => any.as_big_int(),
495            _ => None,
496        }
497    }
498}
499
500impl Display for Int {
501    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
502        match &self {
503            Int::I64(i) => write!(f, "{i}"),
504            Int::BigInt(i) => write!(f, "{i}"),
505        }
506    }
507}
508
509#[cfg(test)]
510mod integer_tests {
511    use num_bigint::BigInt;
512    use std::io::Write;
513
514    use num_bigint::BigUint;
515    use num_traits::Zero;
516    // The 'Big' alias helps distinguish between the enum variant and the wrapped numeric type
517    use crate::types::Int::{self, BigInt as Big, I64};
518    use crate::types::UInt;
519    use rstest::*;
520    use std::cmp::Ordering;
521
522    #[test]
523    fn is_zero() {
524        assert!(I64(0).is_zero());
525        assert!(Big(BigInt::from(0)).is_zero());
526        assert!(!I64(55).is_zero());
527        assert!(!Big(BigInt::from(55)).is_zero());
528        assert!(!I64(-55).is_zero());
529        assert!(!Big(BigInt::from(-55)).is_zero());
530    }
531
532    #[test]
533    fn zero() {
534        assert!(Int::zero().is_zero());
535    }
536
537    #[test]
538    fn add() {
539        assert_eq!(I64(0) + I64(0), I64(0));
540        assert_eq!(I64(5) + I64(7), I64(12));
541        assert_eq!(I64(-5) + I64(7), I64(2));
542        assert_eq!(I64(100) + Big(BigInt::from(1000)), Big(BigInt::from(1100)));
543        assert_eq!(Big(BigInt::from(100)) + I64(1000), Big(BigInt::from(1100)));
544        assert_eq!(
545            Big(BigInt::from(100)) + Big(BigInt::from(1000)),
546            Big(BigInt::from(1100))
547        );
548    }
549
550    #[rstest]
551    #[case::i64(Int::I64(5), Int::I64(4), Ordering::Greater)]
552    #[case::i64_equal(Int::I64(-5), Int::I64(-5), Ordering::Equal)]
553    #[case::i64_gt_big_int(Int::I64(4), Int::BigInt(BigInt::from(3)), Ordering::Greater)]
554    #[case::i64_eq_big_int(Int::I64(3), Int::BigInt(BigInt::from(3)), Ordering::Equal)]
555    #[case::i64_lt_big_int(Int::I64(-3), Int::BigInt(BigInt::from(5)), Ordering::Less)]
556    #[case::big_int(
557        Int::BigInt(BigInt::from(1100)),
558        Int::BigInt(BigInt::from(-1005)),
559        Ordering::Greater
560    )]
561    #[case::big_int(
562        Int::BigInt(BigInt::from(1100)),
563        Int::BigInt(BigInt::from(1100)),
564        Ordering::Equal
565    )]
566    fn integer_ordering_tests(#[case] this: Int, #[case] other: Int, #[case] expected: Ordering) {
567        assert_eq!(this.cmp(&other), expected)
568    }
569
570    #[rstest]
571    #[case::u64(UInt::U64(5), UInt::U64(4), Ordering::Greater)]
572    #[case::u64_equal(UInt::U64(5), UInt::U64(5), Ordering::Equal)]
573    #[case::u64_gt_big_uint(UInt::U64(4), UInt::BigUInt(BigUint::from(3u64)), Ordering::Greater)]
574    #[case::u64_lt_big_uint(UInt::U64(3), UInt::BigUInt(BigUint::from(5u64)), Ordering::Less)]
575    #[case::u64_eq_big_uint(UInt::U64(3), UInt::BigUInt(BigUint::from(3u64)), Ordering::Equal)]
576    #[case::big_uint(
577        UInt::BigUInt(BigUint::from(1100u64)),
578        UInt::BigUInt(BigUint::from(1005u64)),
579        Ordering::Greater
580    )]
581    #[case::big_uint(
582        UInt::BigUInt(BigUint::from(1005u64)),
583        UInt::BigUInt(BigUint::from(1005u64)),
584        Ordering::Equal
585    )]
586    fn unsigned_integer_ordering_tests(
587        #[case] this: UInt,
588        #[case] other: UInt,
589        #[case] expected: Ordering,
590    ) {
591        assert_eq!(this.cmp(&other), expected)
592    }
593
594    #[rstest]
595    #[case(UInt::U64(1), 1)] // only one test case for U64 as that's delegated to another impl
596    #[case(UInt::BigUInt(BigUint::from(0u64)), 1)]
597    #[case(UInt::BigUInt(BigUint::from(1u64)), 1)]
598    #[case(UInt::BigUInt(BigUint::from(10u64)), 2)]
599    #[case(UInt::BigUInt(BigUint::from(3117u64)), 4)]
600    fn uint_decimal_digits_test(#[case] uint: UInt, #[case] expected: i32) {
601        assert_eq!(uint.number_of_decimal_digits(), expected as u64)
602    }
603
604    #[rstest]
605    #[case(Int::I64(5), "5")]
606    #[case(Int::I64(-5), "-5")]
607    #[case(Int::I64(0), "0")]
608    #[case(Int::BigInt(BigInt::from(1100)), "1100")]
609    #[case(Int::BigInt(BigInt::from(-1100)), "-1100")]
610    fn int_display_test(#[case] value: Int, #[case] expect: String) {
611        let mut buf = Vec::new();
612        write!(&mut buf, "{value}").unwrap();
613        assert_eq!(expect, String::from_utf8(buf).unwrap());
614    }
615
616    #[rstest]
617    #[case(UInt::U64(5), "5")]
618    #[case(UInt::U64(0), "0")]
619    #[case(UInt::BigUInt(BigUint::from(0u64)), "0")]
620    #[case(UInt::BigUInt(BigUint::from(1100u64)), "1100")]
621    fn uint_display_test(#[case] value: UInt, #[case] expect: String) {
622        let mut buf = Vec::new();
623        write!(&mut buf, "{value}").unwrap();
624        assert_eq!(expect, String::from_utf8(buf).unwrap());
625    }
626}