1 | //! Tests of `num_traits::cast`. |
2 | |
3 | #![cfg_attr (not(feature = "std" ), no_std)] |
4 | |
5 | use num_traits::cast::*; |
6 | use num_traits::Bounded; |
7 | |
8 | use core::{f32, f64}; |
9 | use core::{i128, i16, i32, i64, i8, isize}; |
10 | use core::{u128, u16, u32, u64, u8, usize}; |
11 | |
12 | use core::fmt::Debug; |
13 | use core::mem; |
14 | use core::num::Wrapping; |
15 | |
16 | #[test] |
17 | fn to_primitive_float() { |
18 | let f32_toolarge = 1e39f64; |
19 | assert_eq!(f32_toolarge.to_f32(), Some(f32::INFINITY)); |
20 | assert_eq!((-f32_toolarge).to_f32(), Some(f32::NEG_INFINITY)); |
21 | assert_eq!((f32::MAX as f64).to_f32(), Some(f32::MAX)); |
22 | assert_eq!((-f32::MAX as f64).to_f32(), Some(-f32::MAX)); |
23 | assert_eq!(f64::INFINITY.to_f32(), Some(f32::INFINITY)); |
24 | assert_eq!((f64::NEG_INFINITY).to_f32(), Some(f32::NEG_INFINITY)); |
25 | assert!((f64::NAN).to_f32().map_or(false, |f| f.is_nan())); |
26 | } |
27 | |
28 | #[test] |
29 | fn wrapping_to_primitive() { |
30 | macro_rules! test_wrapping_to_primitive { |
31 | ($($t:ty)+) => { |
32 | $({ |
33 | let i: $t = 0; |
34 | let w = Wrapping(i); |
35 | assert_eq!(i.to_u8(), w.to_u8()); |
36 | assert_eq!(i.to_u16(), w.to_u16()); |
37 | assert_eq!(i.to_u32(), w.to_u32()); |
38 | assert_eq!(i.to_u64(), w.to_u64()); |
39 | assert_eq!(i.to_usize(), w.to_usize()); |
40 | assert_eq!(i.to_i8(), w.to_i8()); |
41 | assert_eq!(i.to_i16(), w.to_i16()); |
42 | assert_eq!(i.to_i32(), w.to_i32()); |
43 | assert_eq!(i.to_i64(), w.to_i64()); |
44 | assert_eq!(i.to_isize(), w.to_isize()); |
45 | assert_eq!(i.to_f32(), w.to_f32()); |
46 | assert_eq!(i.to_f64(), w.to_f64()); |
47 | })+ |
48 | }; |
49 | } |
50 | |
51 | test_wrapping_to_primitive!(usize u8 u16 u32 u64 isize i8 i16 i32 i64); |
52 | } |
53 | |
54 | #[test] |
55 | fn wrapping_is_toprimitive() { |
56 | fn require_toprimitive<T: ToPrimitive>(_: &T) {} |
57 | require_toprimitive(&Wrapping(42)); |
58 | } |
59 | |
60 | #[test] |
61 | fn wrapping_is_fromprimitive() { |
62 | fn require_fromprimitive<T: FromPrimitive>(_: &T) {} |
63 | require_fromprimitive(&Wrapping(42)); |
64 | } |
65 | |
66 | #[test] |
67 | fn wrapping_is_numcast() { |
68 | fn require_numcast<T: NumCast>(_: &T) {} |
69 | require_numcast(&Wrapping(42)); |
70 | } |
71 | |
72 | #[test] |
73 | fn as_primitive() { |
74 | let x: f32 = (1.625f64).as_(); |
75 | assert_eq!(x, 1.625f32); |
76 | |
77 | let x: f32 = (3.14159265358979323846f64).as_(); |
78 | assert_eq!(x, 3.1415927f32); |
79 | |
80 | let x: u8 = (768i16).as_(); |
81 | assert_eq!(x, 0); |
82 | } |
83 | |
84 | #[test] |
85 | fn float_to_integer_checks_overflow() { |
86 | // This will overflow an i32 |
87 | let source: f64 = 1.0e+123f64; |
88 | |
89 | // Expect the overflow to be caught |
90 | assert_eq!(cast::<f64, i32>(source), None); |
91 | } |
92 | |
93 | #[test] |
94 | fn cast_to_int_checks_overflow() { |
95 | let big_f: f64 = 1.0e123; |
96 | let normal_f: f64 = 1.0; |
97 | let small_f: f64 = -1.0e123; |
98 | assert_eq!(None, cast::<f64, isize>(big_f)); |
99 | assert_eq!(None, cast::<f64, i8>(big_f)); |
100 | assert_eq!(None, cast::<f64, i16>(big_f)); |
101 | assert_eq!(None, cast::<f64, i32>(big_f)); |
102 | assert_eq!(None, cast::<f64, i64>(big_f)); |
103 | |
104 | assert_eq!(Some(normal_f as isize), cast::<f64, isize>(normal_f)); |
105 | assert_eq!(Some(normal_f as i8), cast::<f64, i8>(normal_f)); |
106 | assert_eq!(Some(normal_f as i16), cast::<f64, i16>(normal_f)); |
107 | assert_eq!(Some(normal_f as i32), cast::<f64, i32>(normal_f)); |
108 | assert_eq!(Some(normal_f as i64), cast::<f64, i64>(normal_f)); |
109 | |
110 | assert_eq!(None, cast::<f64, isize>(small_f)); |
111 | assert_eq!(None, cast::<f64, i8>(small_f)); |
112 | assert_eq!(None, cast::<f64, i16>(small_f)); |
113 | assert_eq!(None, cast::<f64, i32>(small_f)); |
114 | assert_eq!(None, cast::<f64, i64>(small_f)); |
115 | } |
116 | |
117 | #[test] |
118 | fn cast_to_unsigned_int_checks_overflow() { |
119 | let big_f: f64 = 1.0e123; |
120 | let normal_f: f64 = 1.0; |
121 | let small_f: f64 = -1.0e123; |
122 | assert_eq!(None, cast::<f64, usize>(big_f)); |
123 | assert_eq!(None, cast::<f64, u8>(big_f)); |
124 | assert_eq!(None, cast::<f64, u16>(big_f)); |
125 | assert_eq!(None, cast::<f64, u32>(big_f)); |
126 | assert_eq!(None, cast::<f64, u64>(big_f)); |
127 | |
128 | assert_eq!(Some(normal_f as usize), cast::<f64, usize>(normal_f)); |
129 | assert_eq!(Some(normal_f as u8), cast::<f64, u8>(normal_f)); |
130 | assert_eq!(Some(normal_f as u16), cast::<f64, u16>(normal_f)); |
131 | assert_eq!(Some(normal_f as u32), cast::<f64, u32>(normal_f)); |
132 | assert_eq!(Some(normal_f as u64), cast::<f64, u64>(normal_f)); |
133 | |
134 | assert_eq!(None, cast::<f64, usize>(small_f)); |
135 | assert_eq!(None, cast::<f64, u8>(small_f)); |
136 | assert_eq!(None, cast::<f64, u16>(small_f)); |
137 | assert_eq!(None, cast::<f64, u32>(small_f)); |
138 | assert_eq!(None, cast::<f64, u64>(small_f)); |
139 | } |
140 | |
141 | #[test] |
142 | fn cast_to_i128_checks_overflow() { |
143 | let big_f: f64 = 1.0e123; |
144 | let normal_f: f64 = 1.0; |
145 | let small_f: f64 = -1.0e123; |
146 | assert_eq!(None, cast::<f64, i128>(big_f)); |
147 | assert_eq!(None, cast::<f64, u128>(big_f)); |
148 | |
149 | assert_eq!(Some(normal_f as i128), cast::<f64, i128>(normal_f)); |
150 | assert_eq!(Some(normal_f as u128), cast::<f64, u128>(normal_f)); |
151 | |
152 | assert_eq!(None, cast::<f64, i128>(small_f)); |
153 | assert_eq!(None, cast::<f64, u128>(small_f)); |
154 | } |
155 | |
156 | #[cfg (feature = "std" )] |
157 | fn dbg(args: ::core::fmt::Arguments<'_>) { |
158 | println!("{}" , args); |
159 | } |
160 | |
161 | #[cfg (not(feature = "std" ))] |
162 | fn dbg(_: ::core::fmt::Arguments) {} |
163 | |
164 | // Rust 1.8 doesn't handle cfg on macros correctly |
165 | macro_rules! dbg { ($($tok:tt)*) => { dbg(format_args!($($tok)*)) } } |
166 | |
167 | macro_rules! float_test_edge { |
168 | ($f:ident -> $($t:ident)+) => { $({ |
169 | dbg!("testing cast edge cases for {} -> {}" , stringify!($f), stringify!($t)); |
170 | |
171 | let small = if $t::MIN == 0 || mem::size_of::<$t>() < mem::size_of::<$f>() { |
172 | $t::MIN as $f - 1.0 |
173 | } else { |
174 | ($t::MIN as $f).raw_inc().floor() |
175 | }; |
176 | let fmin = small.raw_dec(); |
177 | dbg!(" testing min {} \n\tvs. {:.0} \n\tand {:.0}" , $t::MIN, fmin, small); |
178 | assert_eq!(Some($t::MIN), cast::<$f, $t>($t::MIN as $f)); |
179 | assert_eq!(Some($t::MIN), cast::<$f, $t>(fmin)); |
180 | assert_eq!(None, cast::<$f, $t>(small)); |
181 | |
182 | let (max, large) = if mem::size_of::<$t>() < mem::size_of::<$f>() { |
183 | ($t::MAX, $t::MAX as $f + 1.0) |
184 | } else { |
185 | let large = $t::MAX as $f; // rounds up! |
186 | let max = large.raw_dec() as $t; // the next smallest possible |
187 | assert_eq!(max.count_ones(), $f::MANTISSA_DIGITS); |
188 | (max, large) |
189 | }; |
190 | let fmax = large.raw_dec(); |
191 | dbg!(" testing max {} \n\tvs. {:.0} \n\tand {:.0}" , max, fmax, large); |
192 | assert_eq!(Some(max), cast::<$f, $t>(max as $f)); |
193 | assert_eq!(Some(max), cast::<$f, $t>(fmax)); |
194 | assert_eq!(None, cast::<$f, $t>(large)); |
195 | |
196 | dbg!(" testing non-finite values" ); |
197 | assert_eq!(None, cast::<$f, $t>($f::NAN)); |
198 | assert_eq!(None, cast::<$f, $t>($f::INFINITY)); |
199 | assert_eq!(None, cast::<$f, $t>($f::NEG_INFINITY)); |
200 | })+} |
201 | } |
202 | |
203 | trait RawOffset: Sized { |
204 | fn raw_inc(self) -> Self; |
205 | fn raw_dec(self) -> Self; |
206 | } |
207 | |
208 | impl RawOffset for f32 { |
209 | fn raw_inc(self) -> Self { |
210 | Self::from_bits(self.to_bits() + 1) |
211 | } |
212 | |
213 | fn raw_dec(self) -> Self { |
214 | Self::from_bits(self.to_bits() - 1) |
215 | } |
216 | } |
217 | |
218 | impl RawOffset for f64 { |
219 | fn raw_inc(self) -> Self { |
220 | Self::from_bits(self.to_bits() + 1) |
221 | } |
222 | |
223 | fn raw_dec(self) -> Self { |
224 | Self::from_bits(self.to_bits() - 1) |
225 | } |
226 | } |
227 | |
228 | #[test] |
229 | fn cast_float_to_int_edge_cases() { |
230 | float_test_edge!(f32 -> isize i8 i16 i32 i64); |
231 | float_test_edge!(f32 -> usize u8 u16 u32 u64); |
232 | float_test_edge!(f64 -> isize i8 i16 i32 i64); |
233 | float_test_edge!(f64 -> usize u8 u16 u32 u64); |
234 | } |
235 | |
236 | #[test] |
237 | fn cast_float_to_i128_edge_cases() { |
238 | float_test_edge!(f32 -> i128 u128); |
239 | float_test_edge!(f64 -> i128 u128); |
240 | } |
241 | |
242 | macro_rules! int_test_edge { |
243 | ($f:ident -> { $($t:ident)+ } with $BigS:ident $BigU:ident ) => { $({ |
244 | #[allow(arithmetic_overflow)] // https://github.com/rust-lang/rust/issues/109731 |
245 | fn test_edge() { |
246 | dbg!("testing cast edge cases for {} -> {}" , stringify!($f), stringify!($t)); |
247 | |
248 | match ($f::MIN as $BigS).cmp(&($t::MIN as $BigS)) { |
249 | Greater => { |
250 | assert_eq!(Some($f::MIN as $t), cast::<$f, $t>($f::MIN)); |
251 | } |
252 | Equal => { |
253 | assert_eq!(Some($t::MIN), cast::<$f, $t>($f::MIN)); |
254 | } |
255 | Less => { |
256 | let min = $t::MIN as $f; |
257 | assert_eq!(Some($t::MIN), cast::<$f, $t>(min)); |
258 | assert_eq!(None, cast::<$f, $t>(min - 1)); |
259 | } |
260 | } |
261 | |
262 | match ($f::MAX as $BigU).cmp(&($t::MAX as $BigU)) { |
263 | Greater => { |
264 | let max = $t::MAX as $f; |
265 | assert_eq!(Some($t::MAX), cast::<$f, $t>(max)); |
266 | assert_eq!(None, cast::<$f, $t>(max + 1)); |
267 | } |
268 | Equal => { |
269 | assert_eq!(Some($t::MAX), cast::<$f, $t>($f::MAX)); |
270 | } |
271 | Less => { |
272 | assert_eq!(Some($f::MAX as $t), cast::<$f, $t>($f::MAX)); |
273 | } |
274 | } |
275 | } |
276 | test_edge(); |
277 | })+} |
278 | } |
279 | |
280 | #[test] |
281 | fn cast_int_to_int_edge_cases() { |
282 | use core::cmp::Ordering::*; |
283 | |
284 | macro_rules! test_edge { |
285 | ($( $from:ident )+) => { $({ |
286 | int_test_edge!($from -> { isize i8 i16 i32 i64 } with i64 u64); |
287 | int_test_edge!($from -> { usize u8 u16 u32 u64 } with i64 u64); |
288 | })+} |
289 | } |
290 | |
291 | test_edge!(isize i8 i16 i32 i64); |
292 | test_edge!(usize u8 u16 u32 u64); |
293 | } |
294 | |
295 | #[test] |
296 | fn cast_int_to_128_edge_cases() { |
297 | use core::cmp::Ordering::*; |
298 | |
299 | macro_rules! test_edge { |
300 | ($( $t:ident )+) => { |
301 | $( |
302 | int_test_edge!($t -> { i128 u128 } with i128 u128); |
303 | )+ |
304 | int_test_edge!(i128 -> { $( $t )+ } with i128 u128); |
305 | int_test_edge!(u128 -> { $( $t )+ } with i128 u128); |
306 | } |
307 | } |
308 | |
309 | test_edge!(isize i8 i16 i32 i64 i128); |
310 | test_edge!(usize u8 u16 u32 u64 u128); |
311 | } |
312 | |
313 | #[test] |
314 | fn newtype_from_primitive() { |
315 | #[derive(PartialEq, Debug)] |
316 | struct New<T>(T); |
317 | |
318 | // minimal impl |
319 | impl<T: FromPrimitive> FromPrimitive for New<T> { |
320 | fn from_i64(n: i64) -> Option<Self> { |
321 | T::from_i64(n).map(New) |
322 | } |
323 | |
324 | fn from_u64(n: u64) -> Option<Self> { |
325 | T::from_u64(n).map(New) |
326 | } |
327 | } |
328 | |
329 | macro_rules! assert_eq_from { |
330 | ($( $from:ident )+) => {$( |
331 | assert_eq!(T::$from(Bounded::min_value()).map(New), |
332 | New::<T>::$from(Bounded::min_value())); |
333 | assert_eq!(T::$from(Bounded::max_value()).map(New), |
334 | New::<T>::$from(Bounded::max_value())); |
335 | )+} |
336 | } |
337 | |
338 | fn check<T: PartialEq + Debug + FromPrimitive>() { |
339 | assert_eq_from!(from_i8 from_i16 from_i32 from_i64 from_isize); |
340 | assert_eq_from!(from_u8 from_u16 from_u32 from_u64 from_usize); |
341 | assert_eq_from!(from_f32 from_f64); |
342 | } |
343 | |
344 | macro_rules! check { |
345 | ($( $ty:ty )+) => {$( check::<$ty>(); )+} |
346 | } |
347 | check!(i8 i16 i32 i64 isize); |
348 | check!(u8 u16 u32 u64 usize); |
349 | } |
350 | |
351 | #[test] |
352 | fn newtype_to_primitive() { |
353 | #[derive(PartialEq, Debug)] |
354 | struct New<T>(T); |
355 | |
356 | // minimal impl |
357 | impl<T: ToPrimitive> ToPrimitive for New<T> { |
358 | fn to_i64(&self) -> Option<i64> { |
359 | self.0.to_i64() |
360 | } |
361 | |
362 | fn to_u64(&self) -> Option<u64> { |
363 | self.0.to_u64() |
364 | } |
365 | } |
366 | |
367 | macro_rules! assert_eq_to { |
368 | ($( $to:ident )+) => {$( |
369 | assert_eq!(T::$to(&Bounded::min_value()), |
370 | New::<T>::$to(&New(Bounded::min_value()))); |
371 | assert_eq!(T::$to(&Bounded::max_value()), |
372 | New::<T>::$to(&New(Bounded::max_value()))); |
373 | )+} |
374 | } |
375 | |
376 | fn check<T: PartialEq + Debug + Bounded + ToPrimitive>() { |
377 | assert_eq_to!(to_i8 to_i16 to_i32 to_i64 to_isize); |
378 | assert_eq_to!(to_u8 to_u16 to_u32 to_u64 to_usize); |
379 | assert_eq_to!(to_f32 to_f64); |
380 | } |
381 | |
382 | macro_rules! check { |
383 | ($( $ty:ty )+) => {$( check::<$ty>(); )+} |
384 | } |
385 | check!(i8 i16 i32 i64 isize); |
386 | check!(u8 u16 u32 u64 usize); |
387 | } |
388 | |