vortex_scalar/bigint/
bigcast.rs

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