vortex_scalar/bigint/
bigcast.rs

1use crate::i256;
2
3/// Checked conversion from one primitive type to another.
4///
5/// This is meant to mirror the `ToPrimitive` trait from `num-traits` but with awareness of `i256`.
6pub trait ToPrimitive: num_traits::ToPrimitive {
7    /// Converts the value of `self` to an `i256`. If the value cannot be
8    /// represented by an `i256`, then `None` is returned.
9    fn to_i256(&self) -> Option<i256>;
10}
11
12// Implementation for primitive types that already implement ToPrimitive from num-traits.
13macro_rules! impl_toprimitive_lossless {
14    ($typ:ty) => {
15        impl ToPrimitive for $typ {
16            #[inline]
17            fn to_i256(&self) -> Option<i256> {
18                Some(i256::from_i128(*self as i128))
19            }
20        }
21    };
22}
23
24// unsigned, except for u128, all losslessly cast into i128
25impl_toprimitive_lossless!(u8);
26impl_toprimitive_lossless!(u16);
27impl_toprimitive_lossless!(u32);
28impl_toprimitive_lossless!(u64);
29
30// signed all losslessly cast into i128
31impl_toprimitive_lossless!(i8);
32impl_toprimitive_lossless!(i16);
33impl_toprimitive_lossless!(i32);
34impl_toprimitive_lossless!(i64);
35impl_toprimitive_lossless!(i128);
36
37// u128 -> i256 always lossless
38impl ToPrimitive for u128 {
39    fn to_i256(&self) -> Option<i256> {
40        Some(i256::from_parts(*self, 0))
41    }
42}
43
44// identity
45impl ToPrimitive for i256 {
46    fn to_i256(&self) -> Option<i256> {
47        Some(*self)
48    }
49}
50
51/// Checked numeric casts up to and including i256 support.
52///
53/// This is meant as a more inclusive version of `NumCast` from the `num-traits` crate.
54pub trait BigCast: Sized + ToPrimitive {
55    /// Cast the value `n` to Self using the relevant `ToPrimitive` method. If the value cannot
56    /// be represented by Self, `None` is returned.
57    fn from<T: ToPrimitive>(n: T) -> Option<Self>;
58}
59
60macro_rules! impl_big_cast {
61    ($T:ty, $conv:ident) => {
62        impl BigCast for $T {
63            fn from<T: ToPrimitive>(n: T) -> Option<Self> {
64                n.$conv()
65            }
66        }
67    };
68}
69
70impl_big_cast!(u8, to_u8);
71impl_big_cast!(u16, to_u16);
72impl_big_cast!(u32, to_u32);
73impl_big_cast!(u64, to_u64);
74impl_big_cast!(u128, to_u128);
75impl_big_cast!(i8, to_i8);
76impl_big_cast!(i16, to_i16);
77impl_big_cast!(i32, to_i32);
78impl_big_cast!(i64, to_i64);
79impl_big_cast!(i128, to_i128);
80impl_big_cast!(i256, to_i256);
81
82#[cfg(test)]
83mod tests {
84    use std::fmt::Debug;
85
86    use rstest::rstest;
87
88    use crate::{BigCast, i256};
89
90    // All BigCast types must losslessly round-trip themselves
91    #[rstest]
92    #[case(u8::MAX)]
93    #[case(u16::MAX)]
94    #[case(u32::MAX)]
95    #[case(u64::MAX)]
96    #[case(u128::MAX)]
97    #[case(i8::MAX)]
98    #[case(i16::MAX)]
99    #[case(i32::MAX)]
100    #[case(i64::MAX)]
101    #[case(i128::MAX)]
102    #[case(i256::MAX)]
103    fn test_big_cast_identity<T: BigCast + Eq + Debug + Copy>(#[case] n: T) {
104        assert_eq!(<T as BigCast>::from(n).unwrap(), n);
105    }
106
107    macro_rules! test_big_cast_overflow {
108        ($name:ident, $src:ty => $dst:ty, $max:expr, $one:expr) => {
109            #[test]
110            fn $name() {
111                // lossless upcast of max
112                let v = <$dst as BigCast>::from($max).unwrap();
113                // Downcast must be lossless.
114                assert_eq!(<$src as BigCast>::from(v), Some($max));
115
116                // add one -> out of the bounds of the lower type
117                let v = v + $one;
118                assert_eq!(<$src as BigCast>::from(v), None);
119            }
120        };
121    }
122
123    test_big_cast_overflow!(test_i8_overflow, i8 => i16, i8::MAX, 1i16);
124    test_big_cast_overflow!(test_i16_overflow, i16 => i32, i16::MAX, 1i32);
125    test_big_cast_overflow!(test_i32_overflow, i32 => i64, i32::MAX, 1i64);
126    test_big_cast_overflow!(test_i64_overflow, i64 => i128, i64::MAX, 1i128);
127    test_big_cast_overflow!(test_i128_overflow, i128 => i256, i128::MAX, i256::ONE);
128}