Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix float to int cast overflow #135

Closed
wants to merge 2 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 71 additions & 10 deletions src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1136,30 +1136,43 @@ macro_rules! impl_to_primitive_float_to_float {
)
}

macro_rules! impl_to_primitive_float_to_integer {
($SrcT:ident, $DstT:ident, $slf:expr) => (
{
let t = $slf.trunc();
if t >= $DstT::MIN as $SrcT && t <= $DstT::MAX as $SrcT {
Some($slf as $DstT)
} else {
None
}
}
)
}

macro_rules! impl_to_primitive_float {
($T:ident) => (
impl ToPrimitive for $T {
#[inline]
fn to_isize(&self) -> Option<isize> { Some(*self as isize) }
fn to_isize(&self) -> Option<isize> { impl_to_primitive_float_to_integer!($T, isize, *self) }
#[inline]
fn to_i8(&self) -> Option<i8> { Some(*self as i8) }
fn to_i8(&self) -> Option<i8> { impl_to_primitive_float_to_integer!($T, i8, *self) }
#[inline]
fn to_i16(&self) -> Option<i16> { Some(*self as i16) }
fn to_i16(&self) -> Option<i16> { impl_to_primitive_float_to_integer!($T, i16, *self) }
#[inline]
fn to_i32(&self) -> Option<i32> { Some(*self as i32) }
fn to_i32(&self) -> Option<i32> { impl_to_primitive_float_to_integer!($T, i32, *self) }
#[inline]
fn to_i64(&self) -> Option<i64> { Some(*self as i64) }
fn to_i64(&self) -> Option<i64> { impl_to_primitive_float_to_integer!($T, i64, *self) }

#[inline]
fn to_usize(&self) -> Option<usize> { Some(*self as usize) }
fn to_usize(&self) -> Option<usize> { impl_to_primitive_float_to_integer!($T, usize, *self) }
#[inline]
fn to_u8(&self) -> Option<u8> { Some(*self as u8) }
fn to_u8(&self) -> Option<u8> { impl_to_primitive_float_to_integer!($T, u8, *self) }
#[inline]
fn to_u16(&self) -> Option<u16> { Some(*self as u16) }
fn to_u16(&self) -> Option<u16> { impl_to_primitive_float_to_integer!($T, u16, *self) }
#[inline]
fn to_u32(&self) -> Option<u32> { Some(*self as u32) }
fn to_u32(&self) -> Option<u32> { impl_to_primitive_float_to_integer!($T, u32, *self) }
#[inline]
fn to_u64(&self) -> Option<u64> { Some(*self as u64) }
fn to_u64(&self) -> Option<u64> { impl_to_primitive_float_to_integer!($T, u64, *self) }

#[inline]
fn to_f32(&self) -> Option<f32> { impl_to_primitive_float_to_float!($T, f32, *self) }
Expand Down Expand Up @@ -2448,3 +2461,51 @@ fn integer_decode_f64(f: f64) -> (u64, i16, i8) {

float_impl!(f32 integer_decode_f32);
float_impl!(f64 integer_decode_f64);

#[test]
fn test_cast_to_int() {
let big_f: f64 = 1.0e123;
let normal_f: f64 = 1.0;
let small_f: f64 = -1.0e123;
assert_eq!(None, cast::<f64, isize>(big_f));
assert_eq!(None, cast::<f64, i8>(big_f));
assert_eq!(None, cast::<f64, i16>(big_f));
assert_eq!(None, cast::<f64, i32>(big_f));
assert_eq!(None, cast::<f64, i64>(big_f));

assert_eq!(Some(normal_f as isize), cast::<f64, isize>(normal_f));
assert_eq!(Some(normal_f as i8), cast::<f64, i8>(normal_f));
assert_eq!(Some(normal_f as i16), cast::<f64, i16>(normal_f));
assert_eq!(Some(normal_f as i32), cast::<f64, i32>(normal_f));
assert_eq!(Some(normal_f as i64), cast::<f64, i64>(normal_f));

assert_eq!(None, cast::<f64, isize>(small_f));
assert_eq!(None, cast::<f64, i8>(small_f));
assert_eq!(None, cast::<f64, i16>(small_f));
assert_eq!(None, cast::<f64, i32>(small_f));
assert_eq!(None, cast::<f64, i64>(small_f));
}

#[test]
fn test_cast_to_unsigned_int() {
let big_f: f64 = 1.0e123;
let normal_f: f64 = 1.0;
let small_f: f64 = -1.0e123;
assert_eq!(None, cast::<f64, usize>(big_f));
assert_eq!(None, cast::<f64, u8>(big_f));
assert_eq!(None, cast::<f64, u16>(big_f));
assert_eq!(None, cast::<f64, u32>(big_f));
assert_eq!(None, cast::<f64, u64>(big_f));

assert_eq!(Some(normal_f as usize), cast::<f64, usize>(normal_f));
assert_eq!(Some(normal_f as u8), cast::<f64, u8>(normal_f));
assert_eq!(Some(normal_f as u16), cast::<f64, u16>(normal_f));
assert_eq!(Some(normal_f as u32), cast::<f64, u32>(normal_f));
assert_eq!(Some(normal_f as u64), cast::<f64, u64>(normal_f));

assert_eq!(None, cast::<f64, usize>(small_f));
assert_eq!(None, cast::<f64, u8>(small_f));
assert_eq!(None, cast::<f64, u16>(small_f));
assert_eq!(None, cast::<f64, u32>(small_f));
assert_eq!(None, cast::<f64, u64>(small_f));
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding tests! How about some edge cases near each MIN and MAX?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The MIN and MAX are different for every type! ...
But yeah, I guess I can do it.
BTW (Rust newbie here): is there a way to iterate over types? With macros or something?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, of course they're different -- that can be macro'ed too. But such edge case are where it's most important to have testing. I realize 64-bit integers are tricky though, since MIN/MAX can't even be fully represented in f64.