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