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//! Floating-point impls
7
8use crate::{Approx, ConvExact, ConvTo, Error, Exact, RangeError, Trunc};
9#[cfg(any(feature = "std", feature = "libm"))]
10use crate::{Ceil, Floor, Nearest};
11
12impl ConvExact<f32> for f64 {
13    type Error = RangeError;
14
15    fn try_conv_exact(x: f32) -> Result<Self, RangeError> {
16        match x.is_nan() {
17            false => Ok(x as f64),
18            true => Err(RangeError),
19        }
20    }
21
22    #[inline]
23    fn conv_exact(x: f32) -> f64 {
24        fn trap_nan(x: f32) {
25            if x.is_nan() {
26                panic!("cast float-to-float: NaN")
27            }
28        }
29
30        if cfg!(any(debug_assertions, feature = "assert_float")) {
31            trap_nan(x)
32        }
33
34        x as f64
35    }
36}
37
38impl ConvTo<f64, Approx> for f32 {
39    type Error = RangeError;
40
41    fn try_conv_to(_: Approx, x: f64) -> Result<f32, Self::Error> {
42        match x.is_nan() {
43            false => Ok(x as f32),
44            true => Err(RangeError),
45        }
46    }
47
48    #[inline]
49    fn conv_to(_: Approx, x: f64) -> f32 {
50        fn trap_nan(x: f64) {
51            if x.is_nan() {
52                panic!("cast float-to-float: NaN")
53            }
54        }
55
56        if cfg!(any(debug_assertions, feature = "assert_float")) {
57            trap_nan(x)
58        }
59
60        x as f32
61    }
62}
63
64impl ConvTo<f64, Exact> for f32 {
65    type Error = Error;
66
67    fn try_conv_to(_: Exact, x: f64) -> Result<f32, Self::Error> {
68        match x.is_nan() {
69            false => {
70                let y = x as f32;
71                if <f64 as ConvExact<f32>>::try_conv_exact(y) == Ok(x) {
72                    Ok(y)
73                } else {
74                    Err(Error::Inexact)
75                }
76            }
77            true => Err(Error::Range),
78        }
79    }
80
81    #[inline]
82    fn conv_to(_: Exact, x: f64) -> f32 {
83        if cfg!(any(debug_assertions, feature = "assert_float")) {
84            f32::try_conv_to(Exact, x).unwrap_or_else(|e| panic!("cast float-to-float: {e}"))
85        } else {
86            x as f32
87        }
88    }
89}
90
91#[cfg(all(not(feature = "std"), feature = "libm"))]
92trait FloatRound {
93    fn round(self) -> Self;
94    fn floor(self) -> Self;
95    fn ceil(self) -> Self;
96}
97#[cfg(all(not(feature = "std"), feature = "libm"))]
98impl FloatRound for f32 {
99    fn round(self) -> Self {
100        libm::roundf(self)
101    }
102    fn floor(self) -> Self {
103        libm::floorf(self)
104    }
105    fn ceil(self) -> Self {
106        libm::ceilf(self)
107    }
108}
109#[cfg(all(not(feature = "std"), feature = "libm"))]
110impl FloatRound for f64 {
111    fn round(self) -> Self {
112        libm::round(self)
113    }
114    fn floor(self) -> Self {
115        libm::floor(self)
116    }
117    fn ceil(self) -> Self {
118        libm::ceil(self)
119    }
120}
121
122macro_rules! impl_float {
123    ($x:ty: $y:tt) => {
124        impl ConvTo<$x, Trunc> for $y {
125            type Error = RangeError;
126
127            #[inline]
128            fn try_conv_to(_: Trunc, x: $x) -> Result<Self, RangeError> {
129                // Tested: these limits work for $x=f32 and all $y except u128
130                const LBOUND: $x = $y::MIN as $x - 1.0;
131                const UBOUND: $x = $y::MAX as $x + 1.0;
132                if x > LBOUND && x < UBOUND {
133                    Ok(x as $y)
134                } else {
135                    Err(RangeError)
136                }
137            }
138
139            #[inline]
140            fn conv_to(_: Trunc, x: $x) -> Self {
141                if cfg!(any(debug_assertions, feature = "assert_float")) {
142                    <$y>::try_conv_to(Trunc, x).unwrap_or_else(|_| {
143                        panic!(
144                            "cast x: {} to {} (trunc): range error for x = {}",
145                            stringify!($x), stringify!($y), x
146                        )
147                    })
148                } else {
149                    x as $y
150                }
151            }
152        }
153
154        #[cfg(any(feature = "std", feature = "libm"))]
155        impl ConvTo<$x, Nearest> for $y {
156            type Error = RangeError;
157
158            #[inline]
159            fn try_conv_to(_: Nearest, x: $x) -> Result<Self, RangeError> {
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(RangeError)
168                }
169            }
170
171            #[inline]
172            fn conv_to(_: Nearest, x: $x) -> Self {
173                if cfg!(any(debug_assertions, feature = "assert_float")) {
174                    <$y>::try_conv_to(Nearest, x).unwrap_or_else(|_| {
175                        panic!(
176                            "cast x: {} to {} (nearest): range error for x = {}",
177                            stringify!($x), stringify!($y), x
178                        )
179                    })
180                } else {
181                    x.round() as $y
182                }
183            }
184        }
185
186        #[cfg(any(feature = "std", feature = "libm"))]
187        impl ConvTo<$x, Floor> for $y {
188            type Error = RangeError;
189
190            #[inline]
191            fn try_conv_to(_: Floor, x: $x) -> Result<Self, RangeError> {
192                // Tested: these limits work for $x=f32 and all $y except u128
193                const LBOUND: $x = $y::MIN as $x;
194                const UBOUND: $x = $y::MAX as $x + 1.0;
195                let x = x.floor();
196                if (LBOUND..UBOUND).contains(&x) {
197                    Ok(x as $y)
198                } else {
199                    Err(RangeError)
200                }
201            }
202
203            #[inline]
204            fn conv_to(_: Floor, x: $x) -> Self {
205                if cfg!(any(debug_assertions, feature = "assert_float")) {
206                    <$y>::try_conv_to(Floor, x).unwrap_or_else(|_| {
207                        panic!(
208                            "cast x: {} to {} (floor): range error for x = {}",
209                            stringify!($x), stringify!($y), x
210                        )
211                    })
212                } else {
213                    x.floor() as $y
214                }
215            }
216        }
217
218        #[cfg(any(feature = "std", feature = "libm"))]
219        impl ConvTo<$x, Ceil> for $y {
220            type Error = RangeError;
221
222            #[inline]
223            fn try_conv_to(_: Ceil, x: $x) -> Result<Self, RangeError> {
224                // Tested: these limits work for $x=f32 and all $y except u128
225                const LBOUND: $x = $y::MIN as $x;
226                const UBOUND: $x = $y::MAX as $x + 1.0;
227                let x = x.ceil();
228                if (LBOUND..UBOUND).contains(&x) {
229                    Ok(x as $y)
230                } else {
231                    Err(RangeError)
232                }
233            }
234
235            #[inline]
236            fn conv_to(_: Ceil, x: $x) -> Self {
237                if cfg!(any(debug_assertions, feature = "assert_float")) {
238                    <$y>::try_conv_to(Ceil, x).unwrap_or_else(|_| {
239                        panic!(
240                            "cast x: {} to {} (ceil): range error for x = {}",
241                            stringify!($x), stringify!($y), x
242                        )
243                    })
244                } else {
245                    x.ceil() as $y
246                }
247            }
248        }
249
250        impl ConvTo<$x, Approx> for $y {
251            type Error = RangeError;
252
253            #[inline]
254            fn try_conv_to(_: Approx, x: $x) -> Result<Self, Self::Error> {
255                Self::try_conv_to(Trunc, x)
256            }
257            #[inline]
258            fn conv_to(_: Approx, x: $x) -> Self {
259                Self::conv_to(Trunc, x)
260            }
261        }
262    };
263    ($x:ty: $y:tt, $($yy:tt),+) => {
264        impl_float!($x: $y);
265        impl_float!($x: $($yy),+);
266    };
267}
268
269// Assumption: usize < 128-bit
270impl_float!(f32: i8, i16, i32, i64, i128, isize);
271impl_float!(f32: u8, u16, u32, u64, usize);
272impl_float!(f64: i8, i16, i32, i64, i128, isize);
273impl_float!(f64: u8, u16, u32, u64, u128, usize);
274
275impl ConvTo<f32, Trunc> for u128 {
276    type Error = RangeError;
277
278    #[inline]
279    fn try_conv_to(_: Trunc, x: f32) -> Result<Self, RangeError> {
280        // Note: f32::MAX < u128::MAX
281        if x >= 0.0 && x.is_finite() {
282            Ok(x as u128)
283        } else {
284            Err(RangeError)
285        }
286    }
287
288    #[inline]
289    fn conv_to(_: Trunc, x: f32) -> u128 {
290        if cfg!(any(debug_assertions, feature = "assert_float")) {
291            <u128>::try_conv_to(Trunc, x).unwrap_or_else(|_| {
292                panic!(
293                    "cast x: f32 to u128 (trunc/floor): range error for x = {}",
294                    x
295                )
296            })
297        } else {
298            x as u128
299        }
300    }
301}
302
303#[cfg(any(feature = "std", feature = "libm"))]
304impl ConvTo<f32, Nearest> for u128 {
305    type Error = RangeError;
306
307    #[inline]
308    fn try_conv_to(_: Nearest, x: f32) -> Result<Self, RangeError> {
309        let x = x.round();
310        if x >= 0.0 && x.is_finite() {
311            Ok(x as u128)
312        } else {
313            Err(RangeError)
314        }
315    }
316
317    #[inline]
318    fn conv_to(_: Nearest, x: f32) -> u128 {
319        if cfg!(any(debug_assertions, feature = "assert_float")) {
320            <u128>::try_conv_to(Nearest, x).unwrap_or_else(|_| {
321                panic!("cast x: f32 to u128 (nearest): range error for x = {}", x)
322            })
323        } else {
324            x.round() as u128
325        }
326    }
327}
328
329#[cfg(any(feature = "std", feature = "libm"))]
330impl ConvTo<f32, Floor> for u128 {
331    type Error = RangeError;
332
333    #[inline]
334    fn try_conv_to(_: Floor, x: f32) -> Result<Self, RangeError> {
335        Self::try_conv_to(Trunc, x)
336    }
337
338    #[inline]
339    fn conv_to(_: Floor, x: f32) -> u128 {
340        Self::conv_to(Trunc, x)
341    }
342}
343
344#[cfg(any(feature = "std", feature = "libm"))]
345impl ConvTo<f32, Ceil> for u128 {
346    type Error = RangeError;
347
348    #[inline]
349    fn try_conv_to(_: Ceil, x: f32) -> Result<Self, RangeError> {
350        let x = x.ceil();
351        if x >= 0.0 && x.is_finite() {
352            Ok(x as u128)
353        } else {
354            Err(RangeError)
355        }
356    }
357
358    #[inline]
359    fn conv_to(_: Ceil, x: f32) -> u128 {
360        if cfg!(any(debug_assertions, feature = "assert_float")) {
361            u128::try_conv_to(Ceil, x)
362                .unwrap_or_else(|_| panic!("cast x: f32 to u128 (ceil): range error for x = {}", x))
363        } else {
364            x.ceil() as u128
365        }
366    }
367}
368
369impl ConvTo<f32, Approx> for u128 {
370    type Error = RangeError;
371
372    #[inline]
373    fn try_conv_to(_: Approx, x: f32) -> Result<Self, Self::Error> {
374        Self::try_conv_to(Trunc, x)
375    }
376    #[inline]
377    fn conv_to(_: Approx, x: f32) -> Self {
378        Self::conv_to(Trunc, x)
379    }
380}