Skip to main content

easy_cast/
impl_int.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License in the LICENSE-APACHE file or at:
4//     https://www.apache.org/licenses/LICENSE-2.0
5
6//! Integer impls.
7//!
8//! See also `impl_basic` which inherits integer impls from From.
9
10use crate::{Approx, CastTo, ConvExact, ConvTo, Error, Exact, RangeError};
11use core::convert::Infallible;
12use core::mem::size_of;
13
14macro_rules! impl_via_as_neg_check {
15    ($x:ty: $y:ty) => {
16        impl ConvExact<$x> for $y {
17            type Error = RangeError;
18
19            #[inline]
20            fn conv_exact(x: $x) -> $y {
21                #[cfg(any(debug_assertions, feature = "assert_int"))]
22                assert!(
23                    x >= 0,
24                    "cast x: {} to {}: expected x >= 0, found x = {}",
25                    stringify!($x), stringify!($y), x
26                );
27                x as $y
28            }
29            #[inline]
30            fn try_conv_exact(x: $x) -> Result<Self, RangeError> {
31                if x >= 0 {
32                    Ok(x as $y)
33                } else {
34                    Err(RangeError)
35                }
36            }
37        }
38    };
39    ($x:ty: $y:ty, $($yy:ty),+) => {
40        impl_via_as_neg_check!($x: $y);
41        impl_via_as_neg_check!($x: $($yy),+);
42    };
43}
44
45impl_via_as_neg_check!(i8: u8, u16, u32, u64, u128);
46impl_via_as_neg_check!(i16: u16, u32, u64, u128);
47impl_via_as_neg_check!(i32: u32, u64, u128);
48impl_via_as_neg_check!(i64: u64, u128);
49impl_via_as_neg_check!(i128: u128);
50
51// Assumption: $y::MAX is representable as $x
52macro_rules! impl_via_as_max_check {
53    ($x:ty: $y:tt) => {
54        impl ConvExact<$x> for $y {
55            type Error = RangeError;
56
57            #[inline]
58            fn conv_exact(x: $x) -> $y {
59                #[cfg(any(debug_assertions, feature = "assert_int"))]
60                assert!(
61                    x <= $y::MAX as $x,
62                    "cast x: {} to {}: expected x <= {}, found x = {}",
63                    stringify!($x), stringify!($y), $y::MAX, x
64                );
65                x as $y
66            }
67            #[inline]
68            fn try_conv_exact(x: $x) -> Result<Self, RangeError> {
69                if x <= $y::MAX as $x {
70                    Ok(x as $y)
71                } else {
72                    Err(RangeError)
73                }
74            }
75        }
76    };
77    ($x:ty: $y:tt, $($yy:tt),+) => {
78        impl_via_as_max_check!($x: $y);
79        impl_via_as_max_check!($x: $($yy),+);
80    };
81}
82
83impl_via_as_max_check!(u8: i8);
84impl_via_as_max_check!(u16: i8, i16, u8);
85impl_via_as_max_check!(u32: i8, i16, i32, u8, u16);
86impl_via_as_max_check!(u64: i8, i16, i32, i64, u8, u16, u32);
87impl_via_as_max_check!(u128: i8, i16, i32, i64, i128);
88impl_via_as_max_check!(u128: u8, u16, u32, u64);
89
90// Assumption: $y::MAX and $y::MIN are representable as $x
91macro_rules! impl_via_as_range_check {
92    ($x:ty: $y:tt) => {
93        impl ConvExact<$x> for $y {
94            type Error = RangeError;
95
96            #[inline]
97            fn conv_exact(x: $x) -> $y {
98                #[cfg(any(debug_assertions, feature = "assert_int"))]
99                assert!(
100                    $y::MIN as $x <= x && x <= $y::MAX as $x,
101                    "cast x: {} to {}: expected {} <= x <= {}, found x = {}",
102                    stringify!($x), stringify!($y), $y::MIN, $y::MAX, x
103                );
104                x as $y
105            }
106            #[inline]
107            fn try_conv_exact(x: $x) -> Result<Self, RangeError> {
108                if $y::MIN as $x <= x && x <= $y::MAX as $x {
109                    Ok(x as $y)
110                } else {
111                    Err(RangeError)
112                }
113            }
114        }
115    };
116    ($x:ty: $y:tt, $($yy:tt),+) => {
117        impl_via_as_range_check!($x: $y);
118        impl_via_as_range_check!($x: $($yy),+);
119    };
120}
121
122impl_via_as_range_check!(i16: i8, u8);
123impl_via_as_range_check!(i32: i8, i16, u8, u16);
124impl_via_as_range_check!(i64: i8, i16, i32, u8, u16, u32);
125impl_via_as_range_check!(i128: i8, i16, i32, i64, u8, u16, u32, u64);
126
127macro_rules! impl_int_generic {
128    ($x:tt: $y:tt) => {
129        impl ConvExact<$x> for $y {
130            type Error = RangeError;
131
132            #[allow(unused_comparisons)]
133            #[inline]
134            fn conv_exact(x: $x) -> $y {
135                let src_is_signed = $x::MIN != 0;
136                let dst_is_signed = $y::MIN != 0;
137                if size_of::<$x>() < size_of::<$y>() {
138                    if !dst_is_signed {
139                        #[cfg(any(debug_assertions, feature = "assert_int"))]
140                        assert!(
141                            x >= 0,
142                            "cast x: {} to {}: expected x >= 0, found x = {}",
143                            stringify!($x), stringify!($y), x
144                        );
145                    }
146                } else if size_of::<$x>() == size_of::<$y>() {
147                    if dst_is_signed {
148                        #[cfg(any(debug_assertions, feature = "assert_int"))]
149                        assert!(
150                            x <= $y::MAX as $x,
151                            "cast x: {} to {}: expected x <= {}, found x = {}",
152                            stringify!($x), stringify!($y), $y::MAX, x
153                        );
154                    } else if src_is_signed {
155                        #[cfg(any(debug_assertions, feature = "assert_int"))]
156                        assert!(
157                            x >= 0,
158                            "cast x: {} to {}: expected x >= 0, found x = {}",
159                            stringify!($x), stringify!($y), x
160                        );
161                    }
162                } else {
163                    // src size > dst size
164                    if src_is_signed {
165                        #[cfg(any(debug_assertions, feature = "assert_int"))]
166                        assert!(
167                            $y::MIN as $x <= x && x <= $y::MAX as $x,
168                            "cast x: {} to {}: expected {} <= x <= {}, found x = {}",
169                            stringify!($x), stringify!($y), $y::MIN, $y::MAX, x
170                        );
171                    } else {
172                        #[cfg(any(debug_assertions, feature = "assert_int"))]
173                        assert!(
174                            x <= $y::MAX as $x,
175                            "cast x: {} to {}: expected x <= {}, found x = {}",
176                            stringify!($x), stringify!($y), $y::MAX, x
177                        );
178                    }
179                }
180                x as $y
181            }
182            #[allow(unused_comparisons)]
183            #[inline]
184            fn try_conv_exact(x: $x) -> Result<Self, Self::Error> {
185                let src_is_signed = $x::MIN != 0;
186                let dst_is_signed = $y::MIN != 0;
187                if size_of::<$x>() < size_of::<$y>() {
188                    if dst_is_signed || x >= 0 {
189                        return Ok(x as $y);
190                    }
191                } else if size_of::<$x>() == size_of::<$y>() {
192                    if dst_is_signed {
193                        if x <= $y::MAX as $x {
194                            return Ok(x as $y);
195                        }
196                    } else if src_is_signed {
197                        if x >= 0 {
198                            return Ok(x as $y);
199                        }
200                    } else {
201                        // types are identical (e.g. usize == u64)
202                        return Ok(x as $y);
203                    }
204                } else {
205                    // src size > dst size
206                    if src_is_signed {
207                        if $y::MIN as $x <= x && x <= $y::MAX as $x {
208                            return Ok(x as $y);
209                        }
210                    } else {
211                        if x <= $y::MAX as $x {
212                            return Ok(x as $y);
213                        }
214                    }
215                }
216                Err(RangeError)
217            }
218        }
219    };
220    ($x:tt: $y:tt, $($yy:tt),+) => {
221        impl_int_generic!($x: $y);
222        impl_int_generic!($x: $($yy),+);
223    };
224}
225
226impl_int_generic!(i8: usize);
227impl_int_generic!(i16: usize);
228impl_int_generic!(i32: isize, usize);
229impl_int_generic!(i64: isize, usize);
230impl_int_generic!(i128: isize, usize);
231impl_int_generic!(u16: isize);
232impl_int_generic!(u32: isize, usize);
233impl_int_generic!(u64: isize, usize);
234impl_int_generic!(u128: isize, usize);
235impl_int_generic!(isize: i8, i16, i32, i64, i128);
236impl_int_generic!(usize: i8, i16, i32, i64, i128, isize);
237impl_int_generic!(isize: u8, u16, u32, u64, u128, usize);
238impl_int_generic!(usize: u8, u16, u32, u64, u128);
239
240macro_rules! impl_via_digits_check {
241    ($x:ty: $y:tt) => {
242        impl ConvTo<$x, Exact> for $y {
243            type Error = Error;
244
245            #[inline]
246            fn conv_to(_: Exact, x: $x) -> Self {
247                if cfg!(any(debug_assertions, feature = "assert_digits")) {
248                    x.try_cast_to(Exact).unwrap_or_else(|_| {
249                        panic!(
250                            "cast x: {} to {}: inexact for x = {x}",
251                            stringify!($x), stringify!($y)
252                        )
253                    })
254                } else {
255                    x as $y
256                }
257            }
258            #[inline]
259            fn try_conv_to(_: Exact, x: $x) -> Result<Self, Error> {
260                let src_ty_bits = (size_of::<$x>() * 8) as u32;
261                let src_digits = src_ty_bits.saturating_sub(x.leading_zeros() + x.trailing_zeros());
262                let dst_digits = $y::MANTISSA_DIGITS;
263                if src_digits <= dst_digits {
264                    Ok(x as $y)
265                } else {
266                    Err(Error::Inexact)
267                }
268            }
269        }
270    };
271    ($x:ty: $y:tt, $($yy:tt),+) => {
272        impl_via_digits_check!($x: $y);
273        impl_via_digits_check!($x: $($yy),+);
274    };
275}
276
277macro_rules! impl_via_digits_check_signed {
278    ($x:ty: $y:tt) => {
279        impl ConvTo<$x, Exact> for $y {
280            type Error = Error;
281
282            #[inline]
283            fn conv_to(_: Exact, x: $x) -> Self {
284                if cfg!(any(debug_assertions, feature = "assert_digits")) {
285                    x.try_cast_to(Exact).unwrap_or_else(|_| {
286                        panic!(
287                            "cast x: {} to {}: inexact for x = {x}",
288                            stringify!($x), stringify!($y)
289                        )
290                    })
291                } else {
292                    x as $y
293                }
294            }
295            #[inline]
296            fn try_conv_to(_: Exact, x: $x) -> Result<Self, Error> {
297                let src_ty_bits = (size_of::<$x>() * 8) as u32;
298                let src_digits = x.checked_abs()
299                    .map(|y| src_ty_bits.saturating_sub(y.leading_zeros() + y.trailing_zeros()))
300                    .unwrap_or(1 /*MIN has one binary digit in float repr*/);
301                let dst_digits = $y::MANTISSA_DIGITS;
302                if src_digits <= dst_digits {
303                    Ok(x as $y)
304                } else {
305                    Err(Error::Inexact)
306                }
307            }
308        }
309    };
310    ($x:ty: $y:tt, $($yy:tt),+) => {
311        impl_via_digits_check_signed!($x: $y);
312        impl_via_digits_check_signed!($x: $($yy),+);
313    };
314}
315
316impl_via_digits_check!(u32: f32);
317impl_via_digits_check!(u64: f32, f64);
318impl_via_digits_check!(u128: f64);
319impl_via_digits_check!(usize: f32, f64);
320
321impl_via_digits_check_signed!(i32: f32);
322impl_via_digits_check_signed!(i64: f32, f64);
323impl_via_digits_check_signed!(i128: f32, f64);
324impl_via_digits_check_signed!(isize: f32, f64);
325
326impl ConvTo<u128, Exact> for f32 {
327    type Error = Error;
328
329    #[inline]
330    fn conv_to(_: Exact, x: u128) -> Self {
331        if cfg!(any(debug_assertions, feature = "assert_digits")) {
332            x.try_cast_to(Exact)
333                .unwrap_or_else(|_| panic!("cast x: u128 to f32: inexact for x = {x}"))
334        } else {
335            x as f32
336        }
337    }
338    #[inline]
339    fn try_conv_to(_: Exact, x: u128) -> Result<Self, Error> {
340        if x < 0xffff_ff80_0000_0000_0000_0000_0000_0000_u128 {
341            let src_digits = 128u32.saturating_sub(x.leading_zeros() + x.trailing_zeros());
342            if src_digits <= f32::MANTISSA_DIGITS {
343                Ok(x as f32)
344            } else {
345                Err(Error::Inexact)
346            }
347        } else {
348            Err(Error::Range)
349        }
350    }
351}
352
353macro_rules! impl_approx {
354    ($x:ty: $y:tt) => {
355        impl ConvTo<$x, Approx> for $y {
356            type Error = Infallible;
357
358            #[inline]
359            fn conv_to(_: Approx, x: $x) -> Self {
360                x as $y
361            }
362            #[inline]
363            fn try_conv_to(_: Approx, x: $x) -> Result<Self, Infallible> {
364                Ok(x as $y)
365            }
366        }
367
368        // Note: `as` numeric int-to-float casts round to nearest
369        #[cfg(any(feature = "std", feature = "libm"))]
370        impl ConvTo<$x, crate::Nearest> for $y {
371            type Error = Infallible;
372
373            #[inline]
374            fn conv_to(_: crate::Nearest, x: $x) -> Self {
375                x as $y
376            }
377            #[inline]
378            fn try_conv_to(_: crate::Nearest, x: $x) -> Result<Self, Infallible> {
379                Ok(x as $y)
380            }
381        }
382    };
383    ($x:ty: $y:tt, $($yy:tt),+) => {
384        impl_approx!($x: $y);
385        impl_approx!($x: $($yy),+);
386    };
387}
388
389impl_approx!(u32: f32);
390impl_approx!(u64: f32, f64);
391impl_approx!(u128: f32, f64);
392impl_approx!(usize: f32, f64);
393
394impl_approx!(i32: f32);
395impl_approx!(i64: f32, f64);
396impl_approx!(i128: f32, f64);
397impl_approx!(isize: f32, f64);