Add {f32,f64}::approx_unchecked_to<Int> unsafe methods

As discussed in https://github.com/rust-lang/rust/issues/10184

Currently, casting a floating point number to an integer with `as` is Undefined Behavior if the value is out of range. `-Z saturating-float-casts` fixes this soundness hole by making `as` “saturate” to the maximum or minimum value of the integer type (or zero for `NaN`), but has measurable negative performance impact in some benchmarks. There is some consensus in that thread for enabling saturation by default anyway, but provide an `unsafe fn` alternative for users who know through some other mean that their values are in range.
This commit is contained in:
Simon Sapin
2019-11-28 15:24:26 +01:00
parent f442797c17
commit cba479f75c
7 changed files with 139 additions and 3 deletions

View File

@@ -40,6 +40,11 @@
#![stable(feature = "rust1", since = "1.0.0")]
mod num;
#[unstable(feature = "convert_float_to_int", issue = "67057")]
pub use num::FloatToInt;
/// The identity function.
///
/// Two things are important to note about this function:

View File

@@ -0,0 +1,38 @@
mod private {
/// This trait being unreachable from outside the crate
/// prevents other implementations of the `FloatToInt` trait,
/// which allows potentially adding more trait methods after the trait is `#[stable]`.
#[unstable(feature = "convert_float_to_int", issue = "67057")]
pub trait Sealed {}
}
/// Supporting trait for inherent methods of `f32` and `f64` such as `round_unchecked_to`.
/// Typically doesnt need to be used directly.
#[unstable(feature = "convert_float_to_int", issue = "67057")]
pub trait FloatToInt<Int>: private::Sealed + Sized {
#[cfg(not(bootstrap))]
#[unstable(feature = "float_approx_unchecked_to", issue = "67058")]
#[doc(hidden)]
unsafe fn approx_unchecked(self) -> Int;
}
macro_rules! impl_float_to_int {
( $Float: ident => $( $Int: ident )+ ) => {
#[unstable(feature = "convert_float_to_int", issue = "67057")]
impl private::Sealed for $Float {}
$(
#[unstable(feature = "convert_float_to_int", issue = "67057")]
impl FloatToInt<$Int> for $Float {
#[cfg(not(bootstrap))]
#[doc(hidden)]
#[inline]
unsafe fn approx_unchecked(self) -> $Int {
crate::intrinsics::float_to_int_approx_unchecked(self)
}
}
)+
}
}
impl_float_to_int!(f32 => u8 u16 u32 u64 u128 usize i8 i16 i32 i64 i128 isize);
impl_float_to_int!(f64 => u8 u16 u32 u64 u128 usize i8 i16 i32 i64 i128 isize);

View File

@@ -1144,6 +1144,11 @@ extern "rust-intrinsic" {
/// May assume inputs are finite.
pub fn frem_fast<T>(a: T, b: T) -> T;
/// Convert with LLVMs fptoui/fptosi, which may return undef for values out of range
/// https://github.com/rust-lang/rust/issues/10184
#[cfg(not(bootstrap))]
pub fn float_to_int_approx_unchecked<Float, Int>(value: Float) -> Int;
/// Returns the number of bits set in an integer type `T`
pub fn ctpop<T>(x: T) -> T;

View File

@@ -7,9 +7,10 @@
#![stable(feature = "rust1", since = "1.0.0")]
#[cfg(not(bootstrap))]
use crate::convert::FloatToInt;
#[cfg(not(test))]
use crate::intrinsics;
use crate::mem;
use crate::num::FpCategory;
@@ -400,6 +401,35 @@ impl f32 {
intrinsics::minnumf32(self, other)
}
/// Rounds toward zero and converts to any primitive integer type,
/// assuming that the value is finite and fits in that type.
///
/// ```
/// #![feature(float_approx_unchecked_to)]
///
/// let value = 4.6_f32;
/// let rounded = unsafe { value.approx_unchecked_to::<u16>() };
/// assert_eq!(rounded, 4);
///
/// let value = -128.9_f32;
/// let rounded = unsafe { value.approx_unchecked_to::<i8>() };
/// assert_eq!(rounded, std::i8::MIN);
/// ```
///
/// # Safety
///
/// The value must:
///
/// * Not be `NaN`
/// * Not be infinite
/// * Be representable in the return type `Int`, after truncating off its fractional part
#[cfg(not(bootstrap))]
#[unstable(feature = "float_approx_unchecked_to", issue = "67058")]
#[inline]
pub unsafe fn approx_unchecked_to<Int>(self) -> Int where Self: FloatToInt<Int> {
FloatToInt::<Int>::approx_unchecked(self)
}
/// Raw transmutation to `u32`.
///
/// This is currently identical to `transmute::<f32, u32>(self)` on all platforms.

View File

@@ -7,9 +7,10 @@
#![stable(feature = "rust1", since = "1.0.0")]
#[cfg(not(bootstrap))]
use crate::convert::FloatToInt;
#[cfg(not(test))]
use crate::intrinsics;
use crate::mem;
use crate::num::FpCategory;
@@ -413,6 +414,35 @@ impl f64 {
intrinsics::minnumf64(self, other)
}
/// Rounds toward zero and converts to any primitive integer type,
/// assuming that the value is finite and fits in that type.
///
/// ```
/// #![feature(float_approx_unchecked_to)]
///
/// let value = 4.6_f32;
/// let rounded = unsafe { value.approx_unchecked_to::<u16>() };
/// assert_eq!(rounded, 4);
///
/// let value = -128.9_f32;
/// let rounded = unsafe { value.approx_unchecked_to::<i8>() };
/// assert_eq!(rounded, std::i8::MIN);
/// ```
///
/// # Safety
///
/// The value must:
///
/// * Not be `NaN`
/// * Not be infinite
/// * Be representable in the return type `Int`, after truncating off its fractional part
#[cfg(not(bootstrap))]
#[unstable(feature = "float_approx_unchecked_to", issue = "67058")]
#[inline]
pub unsafe fn approx_unchecked_to<Int>(self) -> Int where Self: FloatToInt<Int> {
FloatToInt::<Int>::approx_unchecked(self)
}
/// Raw transmutation to `u64`.
///
/// This is currently identical to `transmute::<f64, u64>(self)` on all platforms.