| 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 | |