Skip to main content

ion_rs/types/integer/
mod.rs

1mod big_small;
2mod int_data;
3
4use crate::ion_data::{IonDataHash, IonDataOrd, IonEq};
5use crate::result::IonFailure;
6use crate::types::CountDecimalDigits;
7use crate::{IonError, IonResult};
8use big_small::AsBigOrSmallValue;
9pub(crate) use int_data::{IntData, UIntData};
10use num_bigint::BigInt;
11use std::cmp::Ordering;
12use std::fmt::{Display, Formatter};
13use std::hash::{Hash, Hasher};
14use std::mem;
15use std::ops::{Add, Neg};
16
17/// Represents an unsigned integer of any size.
18#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
19pub struct UInt {
20    pub(crate) data: UIntData,
21}
22
23impl UInt {
24    pub const ZERO: UInt = UInt {
25        data: UIntData::ZERO,
26    };
27
28    #[inline]
29    pub(crate) fn new(data: impl Into<u128>) -> Self {
30        Self {
31            data: UIntData::from(data.into()),
32        }
33    }
34
35    pub(crate) fn from_str_radix(s: &str, radix: u32) -> IonResult<Self> {
36        let data = UIntData::from_str_radix(s, radix)?;
37        Ok(Self { data })
38    }
39
40    pub(crate) fn from_be_bytes(bytes: &[u8]) -> UInt {
41        let data = UIntData::from_be_bytes(bytes);
42        Self { data }
43    }
44
45    /// Attempts to convert this `UInt` to a `usize`. If the value is too large to fit,
46    /// returns `None`.
47    pub fn as_usize(&self) -> Option<usize> {
48        usize::try_from(self).ok()
49    }
50
51    /// Attempts to convert this `UInt` to a `u64`. If the value is too large to fit,
52    /// returns `None`.
53    pub fn as_u64(&self) -> Option<u64> {
54        u64::try_from(self).ok()
55    }
56
57    /// Attempts to convert this `UInt` to a `u128`. If the value is too large to fit,
58    /// returns `None`.
59    pub fn as_u128(&self) -> Option<u128> {
60        u128::try_from(self).ok()
61    }
62
63    /// Attempts to convert this `UInt` to a `usize`. If the value is too large to fit,
64    /// returns an [`IonError`].
65    pub fn expect_usize(&self) -> IonResult<usize> {
66        usize::try_from(self)
67            .map_err(|_| IonError::decoding_error("UInt was too large to convert to a usize"))
68    }
69
70    /// Attempts to convert this `UInt` to a `u64`. If the value is too large to fit,
71    /// returns an [`IonError`].
72    pub fn expect_u64(&self) -> IonResult<u64> {
73        u64::try_from(self)
74            .map_err(|_| IonError::decoding_error("UInt was too large to convert to a u64"))
75    }
76
77    /// Attempts to convert this `UInt` to a `u128`. If the value is too large to fit,
78    /// returns an [`IonError`].
79    pub fn expect_u128(&self) -> IonResult<u128> {
80        u128::try_from(self)
81            .map_err(|_| IonError::decoding_error("UInt was too large to convert to a u128"))
82    }
83
84    /// Returns the number of digits in the base-10 representation of the UInteger.
85    pub fn number_of_decimal_digits(&self) -> u32 {
86        self.data.count_decimal_digits()
87    }
88
89    pub fn from_le_bytes(bytes: &[u8]) -> UInt {
90        UInt {
91            data: UIntData::from_le_bytes(bytes),
92        }
93    }
94
95    pub fn to_le_bytes(&self) -> Vec<u8> {
96        self.data.to_le_bytes()
97    }
98
99    /// Returns `true` if this value is zero.
100    pub fn is_zero(&self) -> bool {
101        self.data == UIntData::ZERO
102    }
103}
104
105impl From<UIntData> for UInt {
106    fn from(value: UIntData) -> Self {
107        UInt { data: value }
108    }
109}
110
111// This macro makes it possible to turn unsigned int primitives into a UInteger using `.into()`.
112// Note that it works for both signed and unsigned ints. The resulting UInteger will be the
113// absolute value of the integer being converted.
114macro_rules! impl_uint_from_unsigned_int_types {
115    ($($t:ty),*) => ($(
116        impl From<$t> for UInt {
117            #[inline]
118            fn from(value: $t) -> UInt {
119                UInt::new(value)
120            }
121        }
122    )*)
123}
124
125impl_uint_from_unsigned_int_types!(u8, u16, u32, u64, u128);
126
127impl From<usize> for UInt {
128    #[inline]
129    fn from(value: usize) -> Self {
130        debug_assert!(
131            mem::size_of::<usize>() <= mem::size_of::<u128>(),
132            "usize cannot be cast to u128 safely on this platform"
133        );
134        UInt::new(value as u128)
135    }
136}
137
138macro_rules! impl_uint_try_from_signed_int_types {
139    ($($t:ty),*) => ($(
140        impl TryFrom<$t> for UInt {
141            type Error = IonError;
142            fn try_from(value: $t) -> Result<Self, Self::Error> {
143                if value.is_negative() {
144                    return IonResult::decoding_error("cannot convert a negative number to a UInt");
145                }
146                Ok(UInt::from(value.unsigned_abs()))
147            }
148        }
149    )*)
150}
151
152impl_uint_try_from_signed_int_types!(i8, i16, i32, i64, i128, isize);
153
154macro_rules! impl_int_types_try_from_uint {
155    ($($t:ty),*) => ($(
156        impl TryFrom<&UInt> for $t {
157            type Error = IonError;
158
159            fn try_from(value: &UInt) -> Result<Self, Self::Error> {
160                <$t>::try_from(value.clone().data).map_err(|_| {
161                    IonError::decoding_error(
162                            concat!("UInt was too large to fit in a ", stringify!($t))
163                        )
164                })
165            }
166        }
167    )*)
168}
169
170impl_int_types_try_from_uint!(i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize);
171
172impl TryFrom<Int> for UInt {
173    // Returns the unit type if the input `Int` is negative.
174    type Error = IonError;
175
176    fn try_from(value: Int) -> Result<Self, Self::Error> {
177        if value.data.is_negative() {
178            return IonResult::decoding_error("cannot convert negative Int to a UInt");
179        }
180        Ok(value.data.unsigned_abs().into())
181    }
182}
183
184impl TryFrom<&Int> for UInt {
185    type Error = IonError;
186
187    fn try_from(value: &Int) -> Result<Self, Self::Error> {
188        value.clone().try_into()
189    }
190}
191
192impl From<&UInt> for UInt {
193    fn from(value: &UInt) -> Self {
194        value.clone()
195    }
196}
197
198impl From<&Int> for Int {
199    fn from(value: &Int) -> Self {
200        value.clone()
201    }
202}
203
204macro_rules! impl_small_int_try_from_int {
205    ($($t:ty),*) => ($(
206        impl TryFrom<Int> for $t {
207            type Error = IonError;
208
209            fn try_from(value: Int) -> Result<Self, Self::Error> {
210                <$t>::try_from(value.data).map_err(|_| {
211                    IonError::decoding_error(concat!("Int was outside the range of a(n) ", stringify!($t)))
212                })
213            }
214        }
215    )*)
216}
217
218impl_small_int_try_from_int!(i8, i16, i32, i64, i128, isize);
219impl_small_int_try_from_int!(u8, u16, u32, u64, u128, usize);
220
221macro_rules! impl_small_unsigned_int_try_from_uint {
222    ($($t:ty),*) => ($(
223        impl TryFrom<UInt> for $t {
224            type Error = IonError;
225
226            fn try_from(value: UInt) -> Result<Self, Self::Error> {
227                <$t>::try_from(value.data).map_err(|_| {
228                    IonError::decoding_error(concat!("UInt was outside the range of a(n) ", stringify!($t)))
229                })
230            }
231        }
232    )*)
233}
234
235impl_small_unsigned_int_try_from_uint!(u8, u16, u32, u64, u128, usize);
236
237#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
238/// A signed integer of arbitrary size.
239/// ```
240/// # use ion_rs::IonResult;
241/// # fn main() -> IonResult<()> {
242/// use ion_rs::{Element, Int};
243///
244/// let element = Element::read_one("-42")?;
245///
246/// // Access the element's integer value...
247/// let int: &Int = element.expect_int()?;
248/// // ...and convert it to an i64. `as_i64()` will return `None` if
249/// // the Int is too large to fit in an i64.
250/// assert_eq!(int.as_i64(), Some(-42i64));
251///
252/// // The `expect_i64()` is similar to `as_i64()`, but returns an
253/// // `IonError` instead of `None` if the conversion cannot be completed.
254/// assert_eq!(element.expect_i64()?, -42i64);
255/// # Ok(())
256/// # }
257/// ```
258pub struct Int {
259    pub(crate) data: IntData,
260}
261
262impl Int {
263    pub const ZERO: Int = Int {
264        data: IntData::ZERO,
265    };
266
267    #[allow(unused)]
268    pub(crate) fn new(data: impl Into<i128>) -> Self {
269        Self {
270            data: IntData::from(data.into()),
271        }
272    }
273
274    /// Returns a [`UInt`] representing the unsigned magnitude of this `Int`.
275    pub fn unsigned_abs(&self) -> UInt {
276        self.data.unsigned_abs().into()
277    }
278
279    /// Returns `true` if this value is less than zero.
280    /// If this value is greater than or equal to zero, returns `false`.
281    pub fn is_negative(&self) -> bool {
282        self.data.is_negative()
283    }
284
285    /// If this value is small enough to fit in an `i64`, returns `Ok(i64)`. Otherwise,
286    /// returns a [`DecodingError`](IonError::Decoding).
287    #[inline]
288    pub fn expect_i64(&self) -> IonResult<i64> {
289        self.as_i64().ok_or_else(
290            #[inline(never)]
291            || IonError::decoding_error(format!("Int {self} was not in the range of an i64.")),
292        )
293    }
294
295    #[inline(always)]
296    pub fn as_u32(&self) -> Option<u32> {
297        u32::try_from(&self.data).ok()
298    }
299
300    #[inline]
301    pub fn expect_u32(&self) -> IonResult<u32> {
302        self.as_u32().ok_or_else(
303            #[inline(never)]
304            || IonError::decoding_error(format!("Int {self} was not in the range of a u32.")),
305        )
306    }
307
308    #[inline(always)]
309    pub fn as_u64(&self) -> Option<u64> {
310        u64::try_from(&self.data).ok()
311    }
312
313    #[inline]
314    pub fn expect_u64(&self) -> IonResult<u64> {
315        self.as_u64().ok_or_else(
316            #[inline(never)]
317            || IonError::decoding_error(format!("Int {self} was not in the range of a u64.")),
318        )
319    }
320
321    #[inline(always)]
322    pub fn as_usize(&self) -> Option<usize> {
323        usize::try_from(&self.data).ok()
324    }
325
326    #[inline]
327    pub fn expect_usize(&self) -> IonResult<usize> {
328        self.as_usize().ok_or_else(
329            #[inline(never)]
330            || IonError::decoding_error(format!("Int {self} was not in the range of a usize.")),
331        )
332    }
333
334    /// If this value is small enough to fit in an `i128`, returns `Ok(i128)`. Otherwise,
335    /// returns a [`DecodingError`](IonError::Decoding).
336    pub fn expect_i128(&self) -> IonResult<i128> {
337        self.as_i128().ok_or_else(|| {
338            IonError::decoding_error(format!("Int {self} was not in the range of an i128."))
339        })
340    }
341
342    /// If this value is small enough to fit in an `i64`, returns `Some(i64)`. Otherwise, returns
343    /// `None`.
344    pub fn as_i64(&self) -> Option<i64> {
345        i64::try_from(&self.data).ok()
346    }
347
348    /// If this value is small enough to fit in an `i128`, returns `Some(i128)`. Otherwise, returns
349    /// `None`.
350    pub fn as_i128(&self) -> Option<i128> {
351        i128::try_from(&self.data).ok()
352    }
353
354    pub fn from_le_signed_bytes(bytes: &[u8]) -> Int {
355        Int {
356            data: IntData::from_le_signed_bytes(bytes),
357        }
358    }
359
360    pub fn to_le_signed_bytes(&self) -> Vec<u8> {
361        self.data.to_le_bytes()
362    }
363
364    #[cfg_attr(not(feature = "bigdecimal"), allow(dead_code))]
365    // Only used for bigdecimal conversion.
366    pub(crate) fn to_bigint(&self) -> BigInt {
367        self.data.as_big_value().into_owned()
368    }
369
370    /// Returns `true` if this value is zero.
371    pub fn is_zero(&self) -> bool {
372        self.data == IntData::ZERO
373    }
374
375    /// Returns the negation of this value.
376    #[allow(clippy::should_implement_trait)]
377    pub fn neg(self) -> Self {
378        self.data.neg().into()
379    }
380
381    /// Returns the sum of this value and `rhs`.
382    pub(crate) fn add(self, rhs: Self) -> Self {
383        self.data.add(rhs.data).into()
384    }
385}
386
387impl IonEq for Int {
388    fn ion_eq(&self, other: &Self) -> bool {
389        self == other
390    }
391}
392
393impl IonDataOrd for Int {
394    fn ion_cmp(&self, other: &Self) -> Ordering {
395        self.cmp(other)
396    }
397}
398
399impl IonDataHash for Int {
400    fn ion_data_hash<H: Hasher>(&self, state: &mut H) {
401        self.hash(state)
402    }
403}
404
405impl CountDecimalDigits for Int {
406    fn count_decimal_digits(self) -> u32 {
407        self.data.count_decimal_digits()
408    }
409}
410
411impl CountDecimalDigits for UInt {
412    fn count_decimal_digits(self) -> u32 {
413        self.data.count_decimal_digits()
414    }
415}
416
417impl Display for UInt {
418    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
419        write!(f, "{}", self.data)
420    }
421}
422
423impl<T> From<T> for Int
424where
425    T: Into<IntData>,
426{
427    fn from(value: T) -> Self {
428        Self { data: value.into() }
429    }
430}
431
432impl From<UInt> for IntData {
433    fn from(value: UInt) -> Self {
434        value.data.into()
435    }
436}
437
438impl From<&UInt> for Int {
439    fn from(value: &UInt) -> Self {
440        IntData::from(value.data.clone()).into()
441    }
442}
443
444impl Display for Int {
445    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
446        write!(f, "{}", self.data)
447    }
448}
449
450#[cfg(test)]
451mod integer_tests {
452    use std::io::Write;
453
454    use super::*;
455    use crate::types::UInt;
456    use rstest::*;
457    use std::cmp::Ordering;
458
459    #[test]
460    fn is_zero() {
461        assert!(Int::from(0).is_zero());
462        assert!(Int::from(0i128).is_zero());
463        assert!(!Int::from(55).is_zero());
464        assert!(!Int::from(55i128).is_zero());
465        assert!(!Int::from(-55).is_zero());
466        assert!(!Int::from(-55i128).is_zero());
467    }
468
469    #[test]
470    fn zero() {
471        assert!(Int::ZERO.is_zero());
472    }
473
474    #[rstest]
475    #[case::i64(5.into(), 4.into(), Ordering::Greater)]
476    #[case::i64_equal(Int::from(-5), Int::from(-5), Ordering::Equal)]
477    #[case::i64_gt_big_int(Int::from(4), Int::from(3i128), Ordering::Greater)]
478    #[case::i64_eq_big_int(Int::from(3), Int::from(3i128), Ordering::Equal)]
479    #[case::i64_lt_big_int(Int::from(-3), Int::from(5i128), Ordering::Less)]
480    #[case::big_int(
481        Int::from(1100i128),
482        Int::from(-1005i128),
483        Ordering::Greater
484    )]
485    #[case::big_int(Int::from(1100i128), Int::from(1100i128), Ordering::Equal)]
486    #[case::big_int_lt_i64(Int::from(-9223372036854775809i128), Int::from(0), Ordering::Less)]
487    #[case::big_int_gt_i64(Int::from(9223372036854775809i128), Int::from(0), Ordering::Greater)]
488    #[case::i64_gt_big_int_i128(Int::from(0), Int::from(9223372036854775809i128), Ordering::Less)]
489    #[case::i64_lt_big_int_i128(Int::from(0), Int::from(-9223372036854775809i128),  Ordering::Greater)]
490    fn integer_ordering_tests(#[case] this: Int, #[case] other: Int, #[case] expected: Ordering) {
491        assert_eq!(this.cmp(&other), expected)
492    }
493
494    #[rstest]
495    #[case::u64(UInt::from(5u64), UInt::from(4u64), Ordering::Greater)]
496    #[case::u64_equal(UInt::from(5u64), UInt::from(5u64), Ordering::Equal)]
497    #[case::u64_gt_big_uint(UInt::from(4u64), UInt::from(3u128), Ordering::Greater)]
498    #[case::u64_lt_big_uint(UInt::from(3u64), UInt::from(5u128), Ordering::Less)]
499    #[case::u64_eq_big_uint(UInt::from(3u64), UInt::from(3u128), Ordering::Equal)]
500    #[case::big_uint(UInt::from(1100u128), UInt::from(1005u128), Ordering::Greater)]
501    #[case::big_uint(UInt::from(1005u128), UInt::from(1005u128), Ordering::Equal)]
502    fn unsigned_integer_ordering_tests(
503        #[case] this: UInt,
504        #[case] other: UInt,
505        #[case] expected: Ordering,
506    ) {
507        assert_eq!(this.cmp(&other), expected)
508    }
509
510    #[rstest]
511    #[case(UInt::from(1u64), 1)] // only one test case for U64 as that's delegated to another impl
512    #[case(UInt::from(0u128), 1)]
513    #[case(UInt::from(1u128), 1)]
514    #[case(UInt::from(10u128), 2)]
515    #[case(UInt::from(3117u128), 4)]
516    fn uint_decimal_digits_test(#[case] uint: UInt, #[case] expected: u32) {
517        assert_eq!(uint.number_of_decimal_digits(), expected)
518    }
519
520    #[rstest]
521    #[case(Int::from(5), "5")]
522    #[case(Int::from(-5), "-5")]
523    #[case(Int::from(0), "0")]
524    #[case(Int::from(1100i128), "1100")]
525    #[case(Int::from(-1100i128), "-1100")]
526    fn int_display_test(#[case] value: Int, #[case] expect: String) {
527        let mut buf = Vec::new();
528        write!(&mut buf, "{value}").unwrap();
529        assert_eq!(expect, String::from_utf8(buf).unwrap());
530    }
531
532    #[rstest]
533    #[case(UInt::from(5u64), "5")]
534    #[case(UInt::from(0u64), "0")]
535    #[case(UInt::from(0u128), "0")]
536    #[case(UInt::from(1100u128), "1100")]
537    fn uint_display_test(#[case] value: UInt, #[case] expect: String) {
538        let mut buf = Vec::new();
539        write!(&mut buf, "{value}").unwrap();
540        assert_eq!(expect, String::from_utf8(buf).unwrap());
541    }
542
543    #[test]
544    fn u8_from_uint() {
545        assert_eq!(u8::try_from(UInt::from(0u64)), Ok(0u8));
546        assert_eq!(u8::try_from(UInt::from(21u64)), Ok(21u8));
547        assert_eq!(u8::try_from(UInt::from(255u64)), Ok(255u8));
548        assert_eq!(u8::try_from(UInt::from(255u128)), Ok(255u8));
549        assert!(u8::try_from(UInt::from(999u64)).is_err())
550    }
551
552    #[test]
553    fn u16_from_uint() {
554        assert_eq!(u16::try_from(UInt::from(0u64)), Ok(0u16));
555        assert_eq!(u16::try_from(UInt::from(999u64)), Ok(999u16));
556        assert_eq!(u16::try_from(UInt::from(16500u64)), Ok(16500u16));
557        assert_eq!(u16::try_from(UInt::from(16500u128)), Ok(16500u16));
558        assert!(u16::try_from(UInt::from(128_000u64)).is_err())
559    }
560
561    #[test]
562    fn u32_from_uint() {
563        assert_eq!(u32::try_from(UInt::from(0u64)), Ok(0u32));
564        assert_eq!(u32::try_from(UInt::from(16500u64)), Ok(16500u32));
565        assert_eq!(u32::try_from(UInt::from(128_000u64)), Ok(128_000u32));
566        assert_eq!(u32::try_from(UInt::from(128_000u128)), Ok(128_000u32));
567        assert!(u32::try_from(UInt::from(5_000_000_000u64)).is_err())
568    }
569
570    #[test]
571    fn u64_from_uint() {
572        assert_eq!(u64::try_from(UInt::from(0u64)), Ok(0u64));
573        assert_eq!(u64::try_from(UInt::from(128_000u64)), Ok(128_000u64));
574        assert_eq!(
575            u64::try_from(UInt::from(5_000_000_000u64)),
576            Ok(5_000_000_000u64)
577        );
578        assert!(u64::try_from(UInt::from(u128::MAX)).is_err())
579    }
580
581    #[test]
582    fn usize_from_uint() {
583        assert_eq!(usize::try_from(UInt::from(0u64)), Ok(0usize));
584        assert_eq!(usize::try_from(UInt::from(16500u64)), Ok(16500usize));
585        assert_eq!(usize::try_from(UInt::from(128_000u64)), Ok(128_000usize));
586        assert_eq!(usize::try_from(UInt::from(128_000u128)), Ok(128_000usize));
587        assert!(usize::try_from(UInt::from(u128::MAX)).is_err())
588    }
589
590    #[test]
591    fn as_usize() {
592        assert_eq!(UInt::from(128_000u64).as_usize(), Some(128_000usize));
593        assert_eq!(UInt::from(128_000u128).as_usize(), Some(128_000usize));
594        assert!(UInt::from(u128::MAX).as_usize().is_none())
595    }
596
597    #[test]
598    fn expect_usize() {
599        assert_eq!(UInt::from(128_000u64).expect_usize(), Ok(128_000usize));
600        assert_eq!(UInt::from(128_000u128).expect_usize(), Ok(128_000usize));
601        assert!(UInt::from(u128::MAX).expect_usize().is_err())
602    }
603
604    #[test]
605    fn as_u64() {
606        assert_eq!(UInt::from(128_000u64).as_u64(), Some(128_000u64));
607        assert_eq!(UInt::from(128_000u128).as_u64(), Some(128_000u64));
608        assert!(UInt::from(u128::MAX).as_u64().is_none())
609    }
610
611    #[test]
612    fn expect_u64() {
613        assert_eq!(UInt::from(128_000u64).expect_u64(), Ok(128_000u64));
614        assert_eq!(UInt::from(128_000u128).expect_u64(), Ok(128_000u64));
615        assert!(UInt::from(u128::MAX).expect_u64().is_err())
616    }
617
618    #[test]
619    fn int_as_u64() {
620        assert_eq!(Int::from(128_000i64).as_u64(), Some(128_000u64));
621        assert_eq!(Int::from(0i64).as_u64(), Some(0u64));
622        assert!(Int::from(-1i64).as_u64().is_none());
623        assert!(Int::from(i128::MAX).as_u64().is_none());
624    }
625
626    #[test]
627    fn int_expect_u64() {
628        assert_eq!(Int::from(128_000i64).expect_u64(), Ok(128_000u64));
629        assert_eq!(Int::from(0i64).expect_u64(), Ok(0u64));
630        assert!(Int::from(-1i64).expect_u64().is_err());
631        assert!(Int::from(i128::MAX).expect_u64().is_err());
632    }
633
634    // ===== Big value tests =====
635
636    #[test]
637    fn int_from_signed_bytes_le_big() {
638        // 2^128 as signed LE: 17 bytes
639        let mut bytes = vec![0u8; 17];
640        bytes[16] = 1;
641        let big = Int::from_le_signed_bytes(&bytes);
642        assert!(big.as_i128().is_none());
643        assert!(!big.is_negative());
644        assert!(!big.is_zero());
645        assert_eq!(big.to_string(), "340282366920938463463374607431768211456");
646    }
647
648    #[test]
649    fn uint_from_bytes_le_big() {
650        let mut bytes = vec![0u8; 17];
651        bytes[16] = 1;
652        let big = UInt::from_le_bytes(&bytes);
653        assert!(big.as_u128().is_none());
654        assert!(!big.is_zero());
655        assert_eq!(big.to_string(), "340282366920938463463374607431768211456");
656    }
657
658    #[test]
659    fn int_neg_big() {
660        let mut bytes = vec![0u8; 17];
661        bytes[16] = 1;
662        let big = Int::from_le_signed_bytes(&bytes);
663        let neg = big.neg();
664        assert!(neg.is_negative());
665        assert_eq!(neg.to_string(), "-340282366920938463463374607431768211456");
666    }
667
668    #[test]
669    fn int_from_bytes_roundtrip() {
670        for v in [0i128, 1, -1, 42, -42, i128::MAX, i128::MIN] {
671            let int = Int::from(v);
672            let bytes = int.data.to_le_bytes();
673            let roundtripped = Int::from_le_signed_bytes(&bytes);
674            assert_eq!(int, roundtripped, "roundtrip failed for {v}");
675        }
676    }
677
678    // ===== Cross-representation comparison tests (AC5a) =====
679
680    #[test]
681    fn int_cross_repr_eq() {
682        let inline = Int::from(42i64);
683        let also_inline = Int::from(42i64);
684        assert_eq!(inline, also_inline);
685
686        // Two big values that are equal
687        let mut bytes = vec![0u8; 17];
688        bytes[16] = 1;
689        let big1 = Int::from_le_signed_bytes(&bytes);
690        let big2 = Int::from_le_signed_bytes(&bytes);
691        assert_eq!(big1, big2);
692
693        // Inline != heap
694        assert_ne!(inline, big1);
695    }
696
697    #[test]
698    fn int_cross_repr_ord() {
699        let small = Int::from(42i64);
700        let mut bytes = vec![0u8; 17];
701        bytes[16] = 1;
702        let big = Int::from_le_signed_bytes(&bytes);
703
704        // Inline < heap (positive)
705        assert!(small < big);
706        assert!(big > small);
707
708        // Negative heap < inline
709        let neg_big = Int::from_le_signed_bytes(&bytes).neg();
710        assert!(neg_big < small);
711        assert!(small > neg_big);
712    }
713
714    #[test]
715    fn int_cross_repr_hash_consistent() {
716        use std::collections::hash_map::DefaultHasher;
717        use std::hash::{Hash, Hasher};
718
719        let hash = |v: &Int| {
720            let mut h = DefaultHasher::new();
721            v.hash(&mut h);
722            h.finish()
723        };
724
725        // Equal values must have equal hashes
726        let a = Int::from(42i64);
727        let b = Int::from(42i64);
728        assert_eq!(hash(&a), hash(&b));
729
730        let mut bytes = vec![0u8; 17];
731        bytes[16] = 1;
732        let c = Int::from_le_signed_bytes(&bytes);
733        let d = Int::from_le_signed_bytes(&bytes);
734        assert_eq!(hash(&c), hash(&d));
735    }
736
737    #[test]
738    fn int_ion_eq_and_ion_data_hash() {
739        use crate::ion_data::{IonDataHash, IonEq};
740        use std::collections::hash_map::DefaultHasher;
741        use std::hash::Hasher;
742
743        let a = Int::from(42i64);
744        let b = Int::from(42i64);
745        assert!(a.ion_eq(&b));
746
747        let ion_hash = |v: &Int| {
748            let mut h = DefaultHasher::new();
749            v.ion_data_hash(&mut h);
750            h.finish()
751        };
752        assert_eq!(ion_hash(&a), ion_hash(&b));
753    }
754
755    #[test]
756    fn uint_cross_repr_ord() {
757        let small = UInt::from(42u64);
758        let mut bytes = vec![0u8; 17];
759        bytes[16] = 1;
760        let big = UInt::from_le_bytes(&bytes);
761        assert!(small < big);
762        assert!(big > small);
763    }
764}