ethnum/int/
convert.rs

1//! Module contains conversions for [`I256`] to and from primimitive types.
2
3use super::I256;
4use crate::{error::tfie, uint::U256};
5use core::num::TryFromIntError;
6
7macro_rules! impl_from {
8    ($($t:ty),* $(,)?) => {$(
9        impl From<$t> for I256 {
10            #[inline]
11            fn from(value: $t) -> Self {
12                value.as_i256()
13            }
14        }
15    )*};
16}
17
18impl_from! {
19    bool,
20    i8, i16, i32, i64, i128,
21    u8, u16, u32, u64, u128,
22}
23
24impl TryFrom<U256> for I256 {
25    type Error = TryFromIntError;
26
27    fn try_from(value: U256) -> Result<Self, Self::Error> {
28        if value > I256::MAX.as_u256() {
29            return Err(tfie());
30        }
31        Ok(value.as_i256())
32    }
33}
34
35/// This trait defines `as` conversions (casting) from primitive types to
36/// [`I256`].
37///
38/// [`I256`]: struct.I256.html
39///
40/// # Examples
41///
42/// Casting a floating point value to an integer is a saturating operation,
43/// with `NaN` converting to `0`. So:
44///
45/// ```
46/// # use ethnum::{I256, AsI256};
47/// assert_eq!((-1i32).as_i256(), -I256::ONE);
48/// assert_eq!(u32::MAX.as_i256(), 0xffffffff);
49///
50/// assert_eq!(-13.37f64.as_i256(), -13);
51/// assert_eq!(42.0f64.as_i256(), 42);
52/// assert_eq!(
53///     f32::MAX.as_i256(),
54///     0xffffff00000000000000000000000000u128.as_i256(),
55/// );
56/// assert_eq!(
57///     f32::MIN.as_i256(),
58///     -0xffffff00000000000000000000000000u128.as_i256(),
59/// );
60///
61/// assert_eq!(f64::NEG_INFINITY.as_i256(), I256::MIN);
62/// assert_eq!((-2.0f64.powi(256)).as_i256(), I256::MIN);
63/// assert_eq!(f64::INFINITY.as_i256(), I256::MAX);
64/// assert_eq!(2.0f64.powi(256).as_i256(), I256::MAX);
65/// assert_eq!(f64::NAN.as_i256(), 0);
66/// ```
67pub trait AsI256 {
68    /// Perform an `as` conversion to a [`I256`].
69    ///
70    /// [`I256`]: struct.I256.html
71    #[allow(clippy::wrong_self_convention)]
72    fn as_i256(self) -> I256;
73}
74
75impl AsI256 for I256 {
76    #[inline]
77    fn as_i256(self) -> I256 {
78        self
79    }
80}
81
82impl AsI256 for U256 {
83    #[inline]
84    fn as_i256(self) -> I256 {
85        U256::as_i256(self)
86    }
87}
88
89macro_rules! impl_as_i256 {
90    ($($t:ty),* $(,)?) => {$(
91        impl AsI256 for $t {
92            #[inline]
93            fn as_i256(self) -> I256 {
94                #[allow(unused_comparisons)]
95                let hi = if self >= 0 { 0 } else { !0 };
96                I256::from_words(hi, self as _)
97            }
98        }
99    )*};
100}
101
102impl_as_i256! {
103    i8, i16, i32, i64, i128,
104    u8, u16, u32, u64, u128,
105    isize, usize,
106}
107
108impl AsI256 for bool {
109    #[inline]
110    fn as_i256(self) -> I256 {
111        I256::new(self as _)
112    }
113}
114
115macro_rules! impl_as_i256_float {
116    ($($t:ty [$b:ty]),* $(,)?) => {$(
117        impl AsI256 for $t {
118            #[inline]
119            fn as_i256(self) -> I256 {
120                // The conversion follows roughly the same rules as converting
121                // `f64` to other primitive integer types:
122                // - `NaN` => `0`
123                // - `(-∞, I256::MIN]` => `I256::MIN`
124                // - `(I256::MIN, I256::MAX]` => `value as I256`
125                // - `(I256::MAX, +∞)` => `I256::MAX`
126
127                const M: $b = (<$t>::MANTISSA_DIGITS - 1) as _;
128                const MAN_MASK: $b = !(!0 << M);
129                const MAN_ONE: $b = 1 << M;
130                const EXP_MASK: $b = !0 >> <$t>::MANTISSA_DIGITS;
131                const EXP_OFFSET: $b = EXP_MASK / 2;
132                const ABS_MASK: $b = !0 >> 1;
133                const SIG_MASK: $b = !ABS_MASK;
134
135                let abs = <$t>::from_bits(self.to_bits() & ABS_MASK);
136                let sign = -(((self.to_bits() & SIG_MASK) >> (<$b>::BITS - 2)) as i128)
137                    .wrapping_sub(1); // if self >= 0. { 1 } else { -1 }
138                if abs >= 1.0 {
139                    let bits = abs.to_bits();
140                    let exponent = ((bits >> M) & EXP_MASK) - EXP_OFFSET;
141                    let mantissa = (bits & MAN_MASK) | MAN_ONE;
142                    if exponent <= M {
143                        (I256::from(mantissa >> (M - exponent))) * sign
144                    } else if exponent < 255 {
145                        (I256::from(mantissa) << (exponent - M)) * sign
146                    } else if sign > 0 {
147                        I256::MAX
148                    } else {
149                        I256::MIN
150                    }
151                } else {
152                    I256::ZERO
153                }
154            }
155        }
156    )*};
157}
158
159impl_as_i256_float! {
160    f32[u32], f64[u64],
161}
162
163macro_rules! impl_try_into {
164    ($($t:ty),* $(,)?) => {$(
165        impl TryFrom<I256> for $t {
166            type Error = TryFromIntError;
167
168            #[inline]
169            fn try_from(x: I256) -> Result<Self, Self::Error> {
170                if x >= <$t>::MIN.as_i256() && x <= <$t>::MAX.as_i256() {
171                    Ok(*x.low() as _)
172                } else {
173                    Err(tfie())
174                }
175            }
176        }
177    )*};
178}
179
180impl_try_into! {
181    i8, i16, i32, i64, i128,
182    u8, u16, u32, u64, u128,
183    isize, usize,
184}
185
186macro_rules! impl_into_float {
187    ($($t:ty => $f:ident),* $(,)?) => {$(
188        impl From<I256> for $t {
189            #[inline]
190            fn from(x: I256) -> $t {
191                x.$f()
192            }
193        }
194    )*};
195}
196
197impl_into_float! {
198    f32 => as_f32, f64 => as_f64,
199}
200
201#[cfg(test)]
202mod tests {
203    use crate::I256;
204    use core::str::FromStr as _;
205
206    #[test]
207    fn checked_conversion() {
208        assert_eq!(i32::try_from(I256::new(-10)).unwrap(), -10);
209        assert_eq!(i32::try_from(I256::new(10)).unwrap(), 10);
210        assert!(i32::try_from(I256::MIN).is_err());
211        assert!(i32::try_from(I256::MAX).is_err());
212    }
213
214    #[test]
215    fn github_issue_44() {
216        // A big number.
217        let lhs_i256 = I256::from(i128::MAX);
218
219        // Adding 19 zeros.
220        let scaled_lhs = lhs_i256 * I256::from_str("10000000000000000000").unwrap();
221
222        // One and 18 zeros.
223        let rhs_i256 = I256::from_str("-1000000000000000000").unwrap();
224
225        // So result is basically -i128::MAX and one zero.
226        let result = scaled_lhs.checked_div(rhs_i256).unwrap();
227
228        assert!(i128::try_from(result).is_err());
229    }
230}