Skip to content

Commit

Permalink
Allow large f64-to-f32 to saturate to infinity
Browse files Browse the repository at this point in the history
The implementation of `<f64 as ToPrimitive>::to_f32` was written at a
time when float-to-float overflow was though to be undefined behavior,
per rust-lang/rust#15536, but this was later determined to be fine.
Casting a large `f64` to `f32` just results in an infinity with the
matching sign. The sign gives more information than if `to_f32` just
returns `None`, so now we let these infinities through as a result.

See also rust-num/num-bigint#163 and rust-num/num-rational#83.
  • Loading branch information
cuviper committed Aug 28, 2020
1 parent 3196236 commit 234e855
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 25 deletions.
53 changes: 29 additions & 24 deletions src/cast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,16 @@ use core::{i128, u128};
use core::{i16, i32, i64, i8, isize};
use core::{u16, u32, u64, u8, usize};

use float::FloatCore;

/// A generic trait for converting a value to a number.
///
/// A value can be represented by the target type when it lies within
/// the range of scalars supported by the target type.
/// For example, a negative integer cannot be represented by an unsigned
/// integer type, and an `f64` with a very high magnitude might not be
/// convertible to an `f32`.
/// integer type, and an `i64` with a very high magnitude might not be
/// convertible to an `i32`.
/// On the other hand, conversions with possible precision loss or truncation
/// (e.g. an `f32` with a decimal part to an integer type) are admitted.
/// are admitted, like an `f32` with a decimal part to an integer type, or
/// even a large `f64` saturating to `f32` infinity.
pub trait ToPrimitive {
/// Converts the value of `self` to an `isize`. If the value cannot be
/// represented by an `isize`, then `None` is returned.
Expand Down Expand Up @@ -102,23 +101,29 @@ pub trait ToPrimitive {
///
/// This method is only available with feature `i128` enabled on Rust >= 1.26.
///
/// The default implementation converts through `to_u64()`. Types implementing
/// The default implementation converts through `to_u64()`. Types implementing
/// this trait should override this method if they can represent a greater range.
#[inline]
#[cfg(has_i128)]
fn to_u128(&self) -> Option<u128> {
self.to_u64().map(From::from)
}

/// Converts the value of `self` to an `f32`. If the value cannot be
/// represented by an `f32`, then `None` is returned.
/// Converts the value of `self` to an `f32`. Overflows may map to positive
/// or negative inifinity, otherwise `None` is returned if the value cannot
/// be represented by an `f32`.
#[inline]
fn to_f32(&self) -> Option<f32> {
self.to_f64().as_ref().and_then(ToPrimitive::to_f32)
}

/// Converts the value of `self` to an `f64`. If the value cannot be
/// represented by an `f64`, then `None` is returned.
/// Converts the value of `self` to an `f64`. Overflows may map to positive
/// or negative inifinity, otherwise `None` is returned if the value cannot
/// be represented by an `f64`.
///
/// The default implementation tries to convert through `to_i64()`, and
/// failing that through `to_u64()`. Types implementing this trait should
/// override this method if they can represent a greater range.
#[inline]
fn to_f64(&self) -> Option<f64> {
match self.to_i64() {
Expand Down Expand Up @@ -279,14 +284,8 @@ macro_rules! impl_to_primitive_float_to_float {
($SrcT:ident : $( fn $method:ident -> $DstT:ident ; )*) => {$(
#[inline]
fn $method(&self) -> Option<$DstT> {
// Only finite values that are reducing size need to worry about overflow.
if size_of::<$SrcT>() > size_of::<$DstT>() && FloatCore::is_finite(*self) {
let n = *self as f64;
if n < $DstT::MIN as f64 || n > $DstT::MAX as f64 {
return None;
}
}
// We can safely cast NaN, +-inf, and finite values in range.
// We can safely cast all values, whether NaN, +-inf, or finite.
// Finite values that are reducing size may saturate to +-inf.
Some(*self as $DstT)
}
)*}
Expand Down Expand Up @@ -404,10 +403,11 @@ impl_to_primitive_float!(f64);
/// A value can be represented by the target type when it lies within
/// the range of scalars supported by the target type.
/// For example, a negative integer cannot be represented by an unsigned
/// integer type, and an `f64` with a very high magnitude might not be
/// convertible to an `f32`.
/// integer type, and an `i64` with a very high magnitude might not be
/// convertible to an `i32`.
/// On the other hand, conversions with possible precision loss or truncation
/// (e.g. an `f32` with a decimal part to an integer type) are admitted.
/// are admitted, like an `f32` with a decimal part to an integer type, or
/// even a large `f64` saturating to `f32` infinity.
pub trait FromPrimitive: Sized {
/// Converts an `isize` to return an optional value of this type. If the
/// value cannot be represented by this type, then `None` is returned.
Expand Down Expand Up @@ -508,6 +508,10 @@ pub trait FromPrimitive: Sized {

/// Converts a `f64` to return an optional value of this type. If the
/// value cannot be represented by this type, then `None` is returned.
///
/// The default implementation tries to convert through `from_i64()`, and
/// failing that through `from_u64()`. Types implementing this trait should
/// override this method if they can represent a greater range.
#[inline]
fn from_f64(n: f64) -> Option<Self> {
match n.to_i64() {
Expand Down Expand Up @@ -692,10 +696,11 @@ pub trait NumCast: Sized + ToPrimitive {
/// A value can be represented by the target type when it lies within
/// the range of scalars supported by the target type.
/// For example, a negative integer cannot be represented by an unsigned
/// integer type, and an `f64` with a very high magnitude might not be
/// convertible to an `f32`.
/// integer type, and an `i64` with a very high magnitude might not be
/// convertible to an `i32`.
/// On the other hand, conversions with possible precision loss or truncation
/// (e.g. an `f32` with a decimal part to an integer type) are admitted.
/// are admitted, like an `f32` with a decimal part to an integer type, or
/// even a large `f64` saturating to `f32` infinity.
fn from<T: ToPrimitive>(n: T) -> Option<Self>;
}

Expand Down
3 changes: 2 additions & 1 deletion tests/cast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ use core::num::Wrapping;
#[test]
fn to_primitive_float() {
let f32_toolarge = 1e39f64;
assert_eq!(f32_toolarge.to_f32(), None);
assert_eq!(f32_toolarge.to_f32(), Some(f32::INFINITY));
assert_eq!((-f32_toolarge).to_f32(), Some(f32::NEG_INFINITY));
assert_eq!((f32::MAX as f64).to_f32(), Some(f32::MAX));
assert_eq!((-f32::MAX as f64).to_f32(), Some(-f32::MAX));
assert_eq!(f64::INFINITY.to_f32(), Some(f32::INFINITY));
Expand Down

0 comments on commit 234e855

Please sign in to comment.