diff --git a/library/core/src/convert/mod.rs b/library/core/src/convert/mod.rs index ae8458c199503..7cc21b2857464 100644 --- a/library/core/src/convert/mod.rs +++ b/library/core/src/convert/mod.rs @@ -41,6 +41,8 @@ use crate::marker::PointeeSized; mod num; +#[unstable(feature = "float_conversions", issue = "159913")] +pub use num::FloatToFloat; #[unstable(feature = "convert_float_to_int", issue = "67057")] pub use num::FloatToInt; #[unstable(feature = "integer_casts", issue = "157388")] diff --git a/library/core/src/convert/num.rs b/library/core/src/convert/num.rs index 1125f437c1538..67072c800e75e 100644 --- a/library/core/src/convert/num.rs +++ b/library/core/src/convert/num.rs @@ -7,6 +7,14 @@ pub impl(self) trait FloatToInt: Sized { #[unstable(feature = "convert_float_to_int", issue = "67057")] #[doc(hidden)] unsafe fn to_int_unchecked(self) -> Int; + + #[unstable(feature = "float_conversions", issue = "159913")] + #[doc(hidden)] + fn to_int_saturating(self) -> Int; + + #[unstable(feature = "float_conversions", issue = "159913")] + #[doc(hidden)] + fn to_int_checked(self) -> Option; } macro_rules! impl_float_to_int { @@ -19,6 +27,24 @@ macro_rules! impl_float_to_int { // SAFETY: the safety contract must be upheld by the caller. unsafe { crate::intrinsics::float_to_int_unchecked(self) } } + #[inline] + fn to_int_saturating(self) -> $Int { + // `as` already saturates and maps `NaN` to zero. + self as $Int + } + #[inline] + fn to_int_checked(self) -> Option<$Int> { + // `as` truncates toward zero and these bounds are exact for + // that: `MAX + 1` rounds up to the first out-of-range value, + // and the `- MIN` offset keeps the low comparison exact even + // when `MIN - 1` is not representable. `NaN` and infinities + // fail both comparisons. + if self - (<$Int>::MIN as $Float) > -1.0 && self < <$Int>::MAX as $Float + 1.0 { + Some(self as $Int) + } else { + None + } + } } )+ } @@ -29,6 +55,34 @@ impl_float_to_int!(f32 => u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i12 impl_float_to_int!(f64 => u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize); impl_float_to_int!(f128 => u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize); +/// Supporting trait for the inherent `cast` method converting between float types. +/// Typically doesn’t need to be used directly. +#[unstable(feature = "float_conversions", issue = "159913")] +pub impl(self) trait FloatToFloat: Sized { + #[unstable(feature = "float_conversions", issue = "159913")] + #[doc(hidden)] + fn cast(self) -> Flt; +} + +macro_rules! impl_float_to_float { + ($Float:ty => $($Flt:ty),+) => { + $( + #[unstable(feature = "float_conversions", issue = "159913")] + impl FloatToFloat<$Flt> for $Float { + #[inline] + fn cast(self) -> $Flt { + self as $Flt + } + } + )+ + } +} + +impl_float_to_float!(f16 => f16, f32, f64, f128); +impl_float_to_float!(f32 => f16, f32, f64, f128); +impl_float_to_float!(f64 => f16, f32, f64, f128); +impl_float_to_float!(f128 => f16, f32, f64, f128); + /// Implement `From` for integers macro_rules! impl_from_bool { ($($int:ty)*) => {$( diff --git a/library/core/src/num/f128.rs b/library/core/src/num/f128.rs index 4875835695e69..ef10f66fd5bf4 100644 --- a/library/core/src/num/f128.rs +++ b/library/core/src/num/f128.rs @@ -11,7 +11,7 @@ #![unstable(feature = "f128", issue = "116909")] -use crate::convert::FloatToInt; +use crate::convert::{FloatToFloat, FloatToInt}; use crate::num::FpCategory; use crate::panic::const_assert; use crate::{intrinsics, mem}; @@ -1028,6 +1028,100 @@ impl f128 { unsafe { FloatToInt::::to_int_unchecked(self) } } + /// Converts to the target float type, rounding as defined in IEEE 754. + /// + /// This is equivalent to `self as Flt`. Narrowing to a smaller type can + /// produce an infinity. + /// + /// ``` + /// #![feature(float_conversions, f128)] + /// # #[cfg(target_has_reliable_f128)] { + /// + /// let x = 1.5_f128; + /// assert_eq!(x.cast::(), 1.5_f64); + /// # } + /// ``` + #[unstable(feature = "float_conversions", issue = "159913")] + #[must_use = "this returns the result of the operation, without modifying the original"] + #[inline] + pub fn cast(self) -> Flt + where + Self: FloatToFloat, + { + FloatToFloat::::cast(self) + } + + /// Rounds toward zero and converts to any primitive integer type, saturating + /// at the type's boundaries and mapping `NaN` to zero. + /// + /// This is equivalent to `self as Int`. + /// + /// ``` + /// #![feature(float_conversions, f128)] + /// # #[cfg(target_has_reliable_f128)] { + /// + /// assert_eq!(4.6_f128.to_int_saturating::(), 4); + /// assert_eq!(f128::NAN.to_int_saturating::(), 0); + /// # } + /// ``` + #[unstable(feature = "float_conversions", issue = "159913")] + #[must_use = "this returns the result of the operation, without modifying the original"] + #[inline] + pub fn to_int_saturating(self) -> Int + where + Self: FloatToInt, + { + FloatToInt::::to_int_saturating(self) + } + + /// Rounds toward zero and converts to any primitive integer type, returning + /// `None` if the value is `NaN`, infinite, or does not fit in the target type. + /// + /// ``` + /// #![feature(float_conversions, f128)] + /// # #[cfg(target_has_reliable_f128)] { + /// + /// assert_eq!(4.6_f128.to_int_checked::(), Some(4)); + /// assert_eq!(f128::NAN.to_int_checked::(), None); + /// # } + /// ``` + #[unstable(feature = "float_conversions", issue = "159913")] + #[must_use = "this returns the result of the operation, without modifying the original"] + #[inline] + pub fn to_int_checked(self) -> Option + where + Self: FloatToInt, + { + FloatToInt::::to_int_checked(self) + } + + /// Rounds toward zero and converts to any primitive integer type. + /// + /// This is equivalent to `self.to_int_checked().unwrap()`. + /// + /// # Panics + /// + /// Panics if the value is `NaN`, infinite, or does not fit in the target type. + /// + /// ``` + /// #![feature(float_conversions, f128)] + /// # #[cfg(target_has_reliable_f128)] { + /// + /// assert_eq!(4.6_f128.to_int_strict::(), 4); + /// # } + /// ``` + #[unstable(feature = "float_conversions", issue = "159913")] + #[must_use = "this returns the result of the operation, without modifying the original"] + #[inline] + #[track_caller] + pub fn to_int_strict(self) -> Int + where + Self: FloatToInt, + { + self.to_int_checked::() + .expect("the value cannot be represented in the target integer type") + } + /// Raw transmutation to `u128`. /// /// This is currently identical to `transmute::(self)` on all platforms. diff --git a/library/core/src/num/f16.rs b/library/core/src/num/f16.rs index 186945db9ae92..52810d1994a0c 100644 --- a/library/core/src/num/f16.rs +++ b/library/core/src/num/f16.rs @@ -11,7 +11,7 @@ #![unstable(feature = "f16", issue = "116909")] -use crate::convert::FloatToInt; +use crate::convert::{FloatToFloat, FloatToInt}; use crate::num::FpCategory; #[cfg(not(test))] use crate::num::imp::libm; @@ -1024,6 +1024,100 @@ impl f16 { unsafe { FloatToInt::::to_int_unchecked(self) } } + /// Converts to the target float type, rounding as defined in IEEE 754. + /// + /// This is equivalent to `self as Flt`. Narrowing to a smaller type can + /// produce an infinity. + /// + /// ``` + /// #![feature(float_conversions, f16)] + /// # #[cfg(target_has_reliable_f16)] { + /// + /// let x = 1.5_f16; + /// assert_eq!(x.cast::(), 1.5_f32); + /// # } + /// ``` + #[unstable(feature = "float_conversions", issue = "159913")] + #[must_use = "this returns the result of the operation, without modifying the original"] + #[inline] + pub fn cast(self) -> Flt + where + Self: FloatToFloat, + { + FloatToFloat::::cast(self) + } + + /// Rounds toward zero and converts to any primitive integer type, saturating + /// at the type's boundaries and mapping `NaN` to zero. + /// + /// This is equivalent to `self as Int`. + /// + /// ``` + /// #![feature(float_conversions, f16)] + /// # #[cfg(target_has_reliable_f16)] { + /// + /// assert_eq!(4.6_f16.to_int_saturating::(), 4); + /// assert_eq!(f16::NAN.to_int_saturating::(), 0); + /// # } + /// ``` + #[unstable(feature = "float_conversions", issue = "159913")] + #[must_use = "this returns the result of the operation, without modifying the original"] + #[inline] + pub fn to_int_saturating(self) -> Int + where + Self: FloatToInt, + { + FloatToInt::::to_int_saturating(self) + } + + /// Rounds toward zero and converts to any primitive integer type, returning + /// `None` if the value is `NaN`, infinite, or does not fit in the target type. + /// + /// ``` + /// #![feature(float_conversions, f16)] + /// # #[cfg(target_has_reliable_f16)] { + /// + /// assert_eq!(4.6_f16.to_int_checked::(), Some(4)); + /// assert_eq!(f16::NAN.to_int_checked::(), None); + /// # } + /// ``` + #[unstable(feature = "float_conversions", issue = "159913")] + #[must_use = "this returns the result of the operation, without modifying the original"] + #[inline] + pub fn to_int_checked(self) -> Option + where + Self: FloatToInt, + { + FloatToInt::::to_int_checked(self) + } + + /// Rounds toward zero and converts to any primitive integer type. + /// + /// This is equivalent to `self.to_int_checked().unwrap()`. + /// + /// # Panics + /// + /// Panics if the value is `NaN`, infinite, or does not fit in the target type. + /// + /// ``` + /// #![feature(float_conversions, f16)] + /// # #[cfg(target_has_reliable_f16)] { + /// + /// assert_eq!(4.6_f16.to_int_strict::(), 4); + /// # } + /// ``` + #[unstable(feature = "float_conversions", issue = "159913")] + #[must_use = "this returns the result of the operation, without modifying the original"] + #[inline] + #[track_caller] + pub fn to_int_strict(self) -> Int + where + Self: FloatToInt, + { + self.to_int_checked::() + .expect("the value cannot be represented in the target integer type") + } + /// Raw transmutation to `u16`. /// /// This is currently identical to `transmute::(self)` on all platforms. diff --git a/library/core/src/num/f32.rs b/library/core/src/num/f32.rs index 97bf54ecde47a..e4221363b17e2 100644 --- a/library/core/src/num/f32.rs +++ b/library/core/src/num/f32.rs @@ -11,7 +11,7 @@ #![stable(feature = "rust1", since = "1.0.0")] -use crate::convert::FloatToInt; +use crate::convert::{FloatToFloat, FloatToInt}; use crate::num::FpCategory; use crate::panic::const_assert; use crate::{cfg_select, intrinsics, mem}; @@ -1217,6 +1217,99 @@ impl f32 { unsafe { FloatToInt::::to_int_unchecked(self) } } + /// Converts to the target float type, rounding as defined in IEEE 754. + /// + /// This is equivalent to `self as Flt`. Narrowing to a smaller type can + /// produce an infinity. + /// + /// ``` + /// #![feature(float_conversions)] + /// + /// let x = 1.5_f32; + /// assert_eq!(x.cast::(), 1.5_f64); + /// ``` + #[unstable(feature = "float_conversions", issue = "159913")] + #[must_use = "this returns the result of the operation, \ + without modifying the original"] + #[inline] + pub fn cast(self) -> Flt + where + Self: FloatToFloat, + { + FloatToFloat::::cast(self) + } + + /// Rounds toward zero and converts to any primitive integer type, saturating + /// at the type's boundaries and mapping `NaN` to zero. + /// + /// This is equivalent to `self as Int`. + /// + /// ``` + /// #![feature(float_conversions)] + /// + /// assert_eq!(255.5_f32.to_int_saturating::(), 255); + /// assert_eq!(300.0_f32.to_int_saturating::(), 255); + /// assert_eq!((-1.0_f32).to_int_saturating::(), 0); + /// assert_eq!(f32::NAN.to_int_saturating::(), 0); + /// ``` + #[unstable(feature = "float_conversions", issue = "159913")] + #[must_use = "this returns the result of the operation, \ + without modifying the original"] + #[inline] + pub fn to_int_saturating(self) -> Int + where + Self: FloatToInt, + { + FloatToInt::::to_int_saturating(self) + } + + /// Rounds toward zero and converts to any primitive integer type, returning + /// `None` if the value is `NaN`, infinite, or does not fit in the target type. + /// + /// ``` + /// #![feature(float_conversions)] + /// + /// assert_eq!(255.5_f32.to_int_checked::(), Some(255)); + /// assert_eq!(256.0_f32.to_int_checked::(), None); + /// assert_eq!(f32::NAN.to_int_checked::(), None); + /// ``` + #[unstable(feature = "float_conversions", issue = "159913")] + #[must_use = "this returns the result of the operation, \ + without modifying the original"] + #[inline] + pub fn to_int_checked(self) -> Option + where + Self: FloatToInt, + { + FloatToInt::::to_int_checked(self) + } + + /// Rounds toward zero and converts to any primitive integer type. + /// + /// This is equivalent to `self.to_int_checked().unwrap()`. + /// + /// # Panics + /// + /// Panics if the value is `NaN`, infinite, or does not fit in the target type. + /// + /// ``` + /// #![feature(float_conversions)] + /// + /// assert_eq!(255.5_f32.to_int_strict::(), 255); + /// ``` + #[unstable(feature = "float_conversions", issue = "159913")] + #[must_use = "this returns the result of the operation, \ + without modifying the original"] + #[inline] + #[track_caller] + pub fn to_int_strict(self) -> Int + where + Self: FloatToInt, + { + self.to_int_checked::() + .expect("the value cannot be represented in the target integer type") + } + /// Raw transmutation to `u32`. /// /// This is currently identical to `transmute::(self)` on all platforms. diff --git a/library/core/src/num/f64.rs b/library/core/src/num/f64.rs index 15ad61fe3e6e3..f56cbb7497b8b 100644 --- a/library/core/src/num/f64.rs +++ b/library/core/src/num/f64.rs @@ -11,7 +11,7 @@ #![stable(feature = "rust1", since = "1.0.0")] -use crate::convert::FloatToInt; +use crate::convert::{FloatToFloat, FloatToInt}; use crate::num::FpCategory; use crate::panic::const_assert; use crate::{intrinsics, mem}; @@ -1198,6 +1198,95 @@ impl f64 { unsafe { FloatToInt::::to_int_unchecked(self) } } + /// Converts to the target float type, rounding as defined in IEEE 754. + /// + /// This is equivalent to `self as Flt`. Narrowing to a smaller type can + /// produce an infinity. + /// + /// ``` + /// #![feature(float_conversions)] + /// + /// let x = 1.5_f64; + /// assert_eq!(x.cast::(), 1.5_f32); + /// ``` + #[unstable(feature = "float_conversions", issue = "159913")] + #[must_use = "this returns the result of the operation, without modifying the original"] + #[inline] + pub fn cast(self) -> Flt + where + Self: FloatToFloat, + { + FloatToFloat::::cast(self) + } + + /// Rounds toward zero and converts to any primitive integer type, saturating + /// at the type's boundaries and mapping `NaN` to zero. + /// + /// This is equivalent to `self as Int`. + /// + /// ``` + /// #![feature(float_conversions)] + /// + /// assert_eq!(255.5_f64.to_int_saturating::(), 255); + /// assert_eq!(300.0_f64.to_int_saturating::(), 255); + /// assert_eq!((-1.0_f64).to_int_saturating::(), 0); + /// assert_eq!(f64::NAN.to_int_saturating::(), 0); + /// ``` + #[unstable(feature = "float_conversions", issue = "159913")] + #[must_use = "this returns the result of the operation, without modifying the original"] + #[inline] + pub fn to_int_saturating(self) -> Int + where + Self: FloatToInt, + { + FloatToInt::::to_int_saturating(self) + } + + /// Rounds toward zero and converts to any primitive integer type, returning + /// `None` if the value is `NaN`, infinite, or does not fit in the target type. + /// + /// ``` + /// #![feature(float_conversions)] + /// + /// assert_eq!(255.5_f64.to_int_checked::(), Some(255)); + /// assert_eq!(256.0_f64.to_int_checked::(), None); + /// assert_eq!(f64::NAN.to_int_checked::(), None); + /// ``` + #[unstable(feature = "float_conversions", issue = "159913")] + #[must_use = "this returns the result of the operation, without modifying the original"] + #[inline] + pub fn to_int_checked(self) -> Option + where + Self: FloatToInt, + { + FloatToInt::::to_int_checked(self) + } + + /// Rounds toward zero and converts to any primitive integer type. + /// + /// This is equivalent to `self.to_int_checked().unwrap()`. + /// + /// # Panics + /// + /// Panics if the value is `NaN`, infinite, or does not fit in the target type. + /// + /// ``` + /// #![feature(float_conversions)] + /// + /// assert_eq!(255.5_f64.to_int_strict::(), 255); + /// ``` + #[unstable(feature = "float_conversions", issue = "159913")] + #[must_use = "this returns the result of the operation, without modifying the original"] + #[inline] + #[track_caller] + pub fn to_int_strict(self) -> Int + where + Self: FloatToInt, + { + self.to_int_checked::() + .expect("the value cannot be represented in the target integer type") + } + /// Raw transmutation to `u64`. /// /// This is currently identical to `transmute::(self)` on all platforms. diff --git a/library/coretests/tests/lib.rs b/library/coretests/tests/lib.rs index 92fccea38bac9..4f4dbafea5c18 100644 --- a/library/coretests/tests/lib.rs +++ b/library/coretests/tests/lib.rs @@ -53,6 +53,7 @@ #![feature(extern_types)] #![feature(f16)] #![feature(f128)] +#![feature(float_conversions)] #![feature(float_exact_integer_constants)] #![feature(float_gamma)] #![feature(float_minimum_maximum)] diff --git a/library/coretests/tests/num/float_conversions.rs b/library/coretests/tests/num/float_conversions.rs new file mode 100644 index 0000000000000..d75dae96abb0c --- /dev/null +++ b/library/coretests/tests/num/float_conversions.rs @@ -0,0 +1,117 @@ +// Tests for the `float_conversions` methods (ACP rust-lang/libs-team#810): +// `cast`, `to_int_saturating`, `to_int_checked`, and `to_int_strict`. + +#[test] +fn cast_widen_narrow() { + assert_eq!(1.5_f32.cast::(), 1.5_f64); + assert_eq!(0.5_f64.cast::(), 0.5_f32); + // Narrowing a value out of the target range produces an infinity. + assert_eq!(1e300_f64.cast::(), f32::INFINITY); + assert_eq!((-1e300_f64).cast::(), f32::NEG_INFINITY); + // Same-type cast is the identity. + assert_eq!(3.25_f64.cast::(), 3.25_f64); + // Narrowing rounds to the nearest representable value. + assert_eq!(0.1_f64.cast::(), 0.1_f32); +} + +#[test] +fn saturating_basics() { + assert_eq!(255.9_f32.to_int_saturating::(), 255); + assert_eq!(300.0_f32.to_int_saturating::(), 255); + assert_eq!((-1.0_f32).to_int_saturating::(), 0); + assert_eq!(f32::NAN.to_int_saturating::(), 0); + assert_eq!(f32::INFINITY.to_int_saturating::(), 255); + assert_eq!(f32::NEG_INFINITY.to_int_saturating::(), i8::MIN); + assert_eq!(f64::NAN.to_int_saturating::(), 0); + // Large finite values saturate at the target boundary. + assert_eq!(1e18_f64.to_int_saturating::(), i32::MAX); + assert_eq!((-1e18_f64).to_int_saturating::(), i32::MIN); +} + +#[test] +fn checked_truncates_then_bounds() { + // Truncation toward zero happens before the bounds check. + assert_eq!(255.5_f64.to_int_checked::(), Some(255)); + assert_eq!(255.9_f64.to_int_checked::(), Some(255)); + assert_eq!(256.0_f64.to_int_checked::(), None); + // A negative fraction truncates toward zero and fits. + assert_eq!((-0.5_f64).to_int_checked::(), Some(0)); + assert_eq!((-1.0_f64).to_int_checked::(), None); + // Non-finite is always None. + assert_eq!(f64::NAN.to_int_checked::(), None); + assert_eq!(f64::INFINITY.to_int_checked::(), None); + assert_eq!(f64::NEG_INFINITY.to_int_checked::(), None); +} + +#[test] +fn checked_signed_boundaries() { + assert_eq!((-128.0_f64).to_int_checked::(), Some(-128)); + assert_eq!((-128.9_f64).to_int_checked::(), Some(-128)); + assert_eq!((-129.0_f64).to_int_checked::(), None); + assert_eq!(127.0_f64.to_int_checked::(), Some(127)); + assert_eq!(127.9_f64.to_int_checked::(), Some(127)); + assert_eq!(128.0_f64.to_int_checked::(), None); +} + +#[test] +fn checked_exact_power_of_two_bounds() { + // i32::MIN is exactly representable and must be accepted. + assert_eq!((i32::MIN as f64).to_int_checked::(), Some(i32::MIN)); + // 2^31 as f32 is exact and one past i32::MAX, so it is rejected... + assert_eq!((2147483648.0_f32).to_int_checked::(), None); + // ...while the largest f32 below 2^31 is accepted. + assert_eq!((2147483520.0_f32).to_int_checked::(), Some(2147483520)); +} + +#[test] +fn strict_matches_checked() { + assert_eq!(255.5_f64.to_int_strict::(), 255); + assert_eq!((-128.0_f64).to_int_strict::(), -128); +} + +#[test] +#[should_panic] +fn strict_panics_on_nan() { + let _ = f64::NAN.to_int_strict::(); +} + +#[test] +#[should_panic] +fn strict_panics_on_overflow() { + let _ = 256.0_f64.to_int_strict::(); +} + +#[cfg(target_has_reliable_f16)] +#[test] +fn f16_into_wide_int_accepts_all_finite() { + // Every finite f16 fits in i128, so the bounds are +/-inf and accept all + // finite values; only non-finite is rejected. + assert_eq!(f16::MAX.to_int_checked::(), Some(f16::MAX as i128)); + assert_eq!((-f16::MAX).to_int_checked::(), Some(-f16::MAX as i128)); + assert_eq!(f16::INFINITY.to_int_checked::(), None); + assert_eq!(f16::NAN.to_int_checked::(), None); + assert_eq!(4.6_f16.to_int_saturating::(), 4); + assert_eq!(1.5_f16.cast::(), 1.5_f32); +} + +// Truncation-then-check must not depend on a libm `trunc`, which is unreliable +// for f16/f128 on some targets. These exercise the fractional checked path. +#[cfg(target_has_reliable_f16)] +#[test] +fn f16_checked_fractional() { + assert_eq!(4.6_f16.to_int_checked::(), Some(4)); + assert_eq!(255.5_f16.to_int_checked::(), Some(255)); + assert_eq!(256.0_f16.to_int_checked::(), None); + assert_eq!((-0.5_f16).to_int_checked::(), Some(0)); + assert_eq!(4.6_f16.to_int_strict::(), 4); +} + +#[cfg(target_has_reliable_f128)] +#[test] +fn f128_checked_fractional() { + assert_eq!(4.6_f128.to_int_checked::(), Some(4)); + assert_eq!(255.5_f128.to_int_checked::(), Some(255)); + assert_eq!(256.0_f128.to_int_checked::(), None); + assert_eq!((-0.5_f128).to_int_checked::(), Some(0)); + assert_eq!(4.6_f128.to_int_strict::(), 4); +} diff --git a/library/coretests/tests/num/mod.rs b/library/coretests/tests/num/mod.rs index 90666fc0b1ad2..8c628c7dd0b7d 100644 --- a/library/coretests/tests/num/mod.rs +++ b/library/coretests/tests/num/mod.rs @@ -26,6 +26,7 @@ mod carryless_mul; mod cast; mod const_from; mod dec2flt; +mod float_conversions; mod float_ieee754_flt2dec_dec2flt; mod float_iter_sum_identity; mod floats;