Check overflow when casting floats to integers.

This change adds some new macro rules used when converting from floats
to integers. There are two macro rule variants, one for signed ints, one
for unsigned ints.

Among other things, this change specifically addresses the overflow case
documented in https://github.com/rust-num/num-traits/issues/12
This commit is contained in:
Dan Barella 2018-01-27 15:41:06 -05:00 committed by Josh Stone
parent aa36cdb206
commit f99aa0e181
1 changed files with 84 additions and 12 deletions

View File

@ -238,35 +238,93 @@ macro_rules! impl_to_primitive_float_to_float {
)
}
macro_rules! impl_to_primitive_float_to_signed_int {
($SrcT:ident, $DstT:ident, $slf:expr) => (
if size_of::<$SrcT>() <= size_of::<$DstT>() {
Some($slf as $DstT)
} else {
// Make sure the value is in range for the cast.
let n = $slf as $DstT;
let max_value: $DstT = ::std::$DstT::MAX;
if -max_value as $DstT <= n && n <= max_value as $DstT {
Some($slf as $DstT)
} else {
None
}
}
)
}
macro_rules! impl_to_primitive_float_to_unsigned_int {
($SrcT:ident, $DstT:ident, $slf:expr) => (
if size_of::<$SrcT>() <= size_of::<$DstT>() {
Some($slf as $DstT)
} else {
// Make sure the value is in range for the cast.
let n = $slf as $DstT;
let max_value: $DstT = ::std::$DstT::MAX;
if n <= max_value as $DstT {
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_signed_int!($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_signed_int!($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_signed_int!($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_signed_int!($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_signed_int!($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_unsigned_int!($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_unsigned_int!($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_unsigned_int!($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_unsigned_int!($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_unsigned_int!($T, u64, *self)
}
#[inline]
fn to_f32(&self) -> Option<f32> { impl_to_primitive_float_to_float!($T, f32, *self) }
fn to_f32(&self) -> Option<f32> {
impl_to_primitive_float_to_float!($T, f32, *self)
}
#[inline]
fn to_f64(&self) -> Option<f64> { impl_to_primitive_float_to_float!($T, f64, *self) }
fn to_f64(&self) -> Option<f64> {
impl_to_primitive_float_to_float!($T, f64, *self)
}
}
)
}
@ -591,3 +649,17 @@ fn as_primitive() {
let x: u8 = (768i16).as_();
assert_eq!(x, 0);
}
#[test]
fn float_to_integer_checks_overflow() {
// This will overflow an i32
let source: f64 = 1.0e+123f64;
// Expect the overflow to be caught
assert_eq!(source.to_i32(), None);
// Specifically make sure we didn't silently let the overflow through
// (This line is mostly for humans -- the robots should catch any problem
// on the line above).
assert_ne!(source.to_i32(), Some(-2147483648));
}