Skip to main content

easy_cast/
impl_float.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//! Impls for ConvFloat
7
8use super::*;
9
10impl Conv<f32> for f64 {
11    fn try_conv(x: f32) -> Result<Self> {
12        match x.is_nan() {
13            false => Ok(x as f64),
14            true => Err(Error::Range),
15        }
16    }
17
18    #[inline]
19    fn conv(x: f32) -> f64 {
20        fn trap_nan(x: f32) {
21            if x.is_nan() {
22                panic!("cast float-to-float: NaN")
23            }
24        }
25
26        if cfg!(any(debug_assertions, feature = "assert_float")) {
27            trap_nan(x)
28        }
29
30        x as f64
31    }
32}
33
34#[allow(clippy::manual_range_contains)]
35impl ConvApprox<f64> for f32 {
36    fn try_conv_approx(x: f64) -> Result<f32> {
37        match x.is_nan() {
38            false => Ok(x as f32),
39            true => Err(Error::Range),
40        }
41    }
42
43    #[inline]
44    fn conv_approx(x: f64) -> f32 {
45        fn trap_nan(x: f64) {
46            if x.is_nan() {
47                panic!("cast float-to-float: NaN")
48            }
49        }
50
51        if cfg!(any(debug_assertions, feature = "assert_float")) {
52            trap_nan(x)
53        }
54
55        x as f32
56    }
57}
58
59#[cfg(all(not(feature = "std"), feature = "libm"))]
60trait FloatRound {
61    fn round(self) -> Self;
62    fn floor(self) -> Self;
63    fn ceil(self) -> Self;
64}
65#[cfg(all(not(feature = "std"), feature = "libm"))]
66impl FloatRound for f32 {
67    fn round(self) -> Self {
68        libm::roundf(self)
69    }
70    fn floor(self) -> Self {
71        libm::floorf(self)
72    }
73    fn ceil(self) -> Self {
74        libm::ceilf(self)
75    }
76}
77#[cfg(all(not(feature = "std"), feature = "libm"))]
78impl FloatRound for f64 {
79    fn round(self) -> Self {
80        libm::round(self)
81    }
82    fn floor(self) -> Self {
83        libm::floor(self)
84    }
85    fn ceil(self) -> Self {
86        libm::ceil(self)
87    }
88}
89
90#[cfg(any(feature = "std", feature = "libm"))]
91macro_rules! impl_float {
92    ($x:ty: $y:tt) => {
93        impl ConvFloat<$x> for $y {
94            #[inline]
95            fn conv_trunc(x: $x) -> $y {
96                if cfg!(any(debug_assertions, feature = "assert_float")) {
97                    Self::try_conv_trunc(x).unwrap_or_else(|_| {
98                        panic!(
99                            "cast x: {} to {} (trunc): range error for x = {}",
100                            stringify!($x), stringify!($y), x
101                        )
102                    })
103                } else {
104                    x as $y
105                }
106            }
107            #[inline]
108            fn conv_nearest(x: $x) -> $y {
109                if cfg!(any(debug_assertions, feature = "assert_float")) {
110                    Self::try_conv_nearest(x).unwrap_or_else(|_| {
111                        panic!(
112                            "cast x: {} to {} (nearest): range error for x = {}",
113                            stringify!($x), stringify!($y), x
114                        )
115                    })
116                } else {
117                    x.round() as $y
118                }
119            }
120            #[inline]
121            fn conv_floor(x: $x) -> $y {
122                if cfg!(any(debug_assertions, feature = "assert_float")) {
123                    Self::try_conv_floor(x).unwrap_or_else(|_| {
124                        panic!(
125                            "cast x: {} to {} (floor): range error for x = {}",
126                            stringify!($x), stringify!($y), x
127                        )
128                    })
129                } else {
130                    x.floor() as $y
131                }
132            }
133            #[inline]
134            fn conv_ceil(x: $x) -> $y {
135                if cfg!(any(debug_assertions, feature = "assert_float")) {
136                    Self::try_conv_ceil(x).unwrap_or_else(|_| {
137                        panic!(
138                            "cast x: {} to {} (ceil): range error for x = {}",
139                            stringify!($x), stringify!($y), x
140                        )
141                    })
142                } else {
143                    x.ceil() as $y
144                }
145            }
146
147            #[inline]
148            fn try_conv_trunc(x: $x) -> Result<Self> {
149                // Tested: these limits work for $x=f32 and all $y except u128
150                const LBOUND: $x = $y::MIN as $x - 1.0;
151                const UBOUND: $x = $y::MAX as $x + 1.0;
152                if x > LBOUND && x < UBOUND {
153                    Ok(x as $y)
154                } else {
155                    Err(Error::Range)
156                }
157            }
158            #[inline]
159            fn try_conv_nearest(x: $x) -> Result<Self> {
160                // Tested: these limits work for $x=f32 and all $y except u128
161                const LBOUND: $x = $y::MIN as $x;
162                const UBOUND: $x = $y::MAX as $x + 1.0;
163                let x = x.round();
164                if (LBOUND..UBOUND).contains(&x) {
165                    Ok(x as $y)
166                } else {
167                    Err(Error::Range)
168                }
169            }
170            #[inline]
171            fn try_conv_floor(x: $x) -> Result<Self> {
172                // Tested: these limits work for $x=f32 and all $y except u128
173                const LBOUND: $x = $y::MIN as $x;
174                const UBOUND: $x = $y::MAX as $x + 1.0;
175                let x = x.floor();
176                if (LBOUND..UBOUND).contains(&x) {
177                    Ok(x as $y)
178                } else {
179                    Err(Error::Range)
180                }
181            }
182            #[inline]
183            fn try_conv_ceil(x: $x) -> Result<Self> {
184                // Tested: these limits work for $x=f32 and all $y except u128
185                const LBOUND: $x = $y::MIN as $x;
186                const UBOUND: $x = $y::MAX as $x + 1.0;
187                let x = x.ceil();
188                if (LBOUND..UBOUND).contains(&x) {
189                    Ok(x as $y)
190                } else {
191                    Err(Error::Range)
192                }
193            }
194        }
195
196        impl ConvApprox<$x> for $y {
197            #[inline]
198            fn try_conv_approx(x: $x) -> Result<Self> {
199                ConvFloat::<$x>::try_conv_trunc(x)
200            }
201            #[inline]
202            fn conv_approx(x: $x) -> Self {
203                ConvFloat::<$x>::conv_trunc(x)
204            }
205        }
206    };
207    ($x:ty: $y:tt, $($yy:tt),+) => {
208        impl_float!($x: $y);
209        impl_float!($x: $($yy),+);
210    };
211}
212
213// Assumption: usize < 128-bit
214#[cfg(any(feature = "std", feature = "libm"))]
215impl_float!(f32: i8, i16, i32, i64, i128, isize);
216#[cfg(any(feature = "std", feature = "libm"))]
217impl_float!(f32: u8, u16, u32, u64, usize);
218#[cfg(any(feature = "std", feature = "libm"))]
219impl_float!(f64: i8, i16, i32, i64, i128, isize);
220#[cfg(any(feature = "std", feature = "libm"))]
221impl_float!(f64: u8, u16, u32, u64, u128, usize);
222
223#[cfg(any(feature = "std", feature = "libm"))]
224impl ConvFloat<f32> for u128 {
225    #[inline]
226    fn conv_trunc(x: f32) -> u128 {
227        if cfg!(any(debug_assertions, feature = "assert_float")) {
228            Self::try_conv_trunc(x).unwrap_or_else(|_| {
229                panic!(
230                    "cast x: f32 to u128 (trunc/floor): range error for x = {}",
231                    x
232                )
233            })
234        } else {
235            x as u128
236        }
237    }
238    #[inline]
239    fn conv_nearest(x: f32) -> u128 {
240        if cfg!(any(debug_assertions, feature = "assert_float")) {
241            Self::try_conv_nearest(x).unwrap_or_else(|_| {
242                panic!("cast x: f32 to u128 (nearest): range error for x = {}", x)
243            })
244        } else {
245            x.round() as u128
246        }
247    }
248    #[inline]
249    fn conv_floor(x: f32) -> u128 {
250        ConvFloat::conv_trunc(x)
251    }
252    #[inline]
253    fn conv_ceil(x: f32) -> u128 {
254        if cfg!(any(debug_assertions, feature = "assert_float")) {
255            Self::try_conv_ceil(x)
256                .unwrap_or_else(|_| panic!("cast x: f32 to u128 (ceil): range error for x = {}", x))
257        } else {
258            x.ceil() as u128
259        }
260    }
261
262    #[inline]
263    fn try_conv_trunc(x: f32) -> Result<Self> {
264        // Note: f32::MAX < u128::MAX
265        if x >= 0.0 && x.is_finite() {
266            Ok(x as u128)
267        } else {
268            Err(Error::Range)
269        }
270    }
271    #[inline]
272    fn try_conv_nearest(x: f32) -> Result<Self> {
273        let x = x.round();
274        if x >= 0.0 && x.is_finite() {
275            Ok(x as u128)
276        } else {
277            Err(Error::Range)
278        }
279    }
280    #[inline]
281    fn try_conv_floor(x: f32) -> Result<Self> {
282        Self::try_conv_trunc(x)
283    }
284    #[inline]
285    fn try_conv_ceil(x: f32) -> Result<Self> {
286        let x = x.ceil();
287        if x >= 0.0 && x.is_finite() {
288            Ok(x as u128)
289        } else {
290            Err(Error::Range)
291        }
292    }
293}
294
295#[cfg(any(feature = "std", feature = "libm"))]
296impl ConvApprox<f32> for u128 {
297    #[inline]
298    fn try_conv_approx(x: f32) -> Result<Self> {
299        ConvFloat::<f32>::try_conv_trunc(x)
300    }
301    #[inline]
302    fn conv_approx(x: f32) -> Self {
303        ConvFloat::<f32>::conv_trunc(x)
304    }
305}