Skip to content

Commit 62528d8

Browse files
authored
Rollup merge of rust-lang#66841 - SimonSapin:float_round_unchecked_to, r=rkruppe
Add `{f32,f64}::approx_unchecked_to<Int>` unsafe methods As discussed in rust-lang#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. <del>The “fit” wording is copied from https://llvm.org/docs/LangRef.html#fptoui-to-instruction, but I’m not certain what it means exactly. Presumably this is after rounding towards zero, and the doc-test with `i8::MIN` seems to confirm this.</del> Clang presumably uses those LLVM intrinsics to implement C and C++ casts, whose respective standard specify that the value *after truncating to keep its integral part* must be representable in the target type.
2 parents 2bd35c0 + a213ff8 commit 62528d8

File tree

8 files changed

+471
-333
lines changed

8 files changed

+471
-333
lines changed

src/libcore/convert.rs src/libcore/convert/mod.rs

+5
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@
4040
4141
#![stable(feature = "rust1", since = "1.0.0")]
4242

43+
mod num;
44+
45+
#[unstable(feature = "convert_float_to_int", issue = "67057")]
46+
pub use num::FloatToInt;
47+
4348
/// The identity function.
4449
///
4550
/// Two things are important to note about this function:

src/libcore/convert/num.rs

+369
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,369 @@
1+
use super::{From, TryFrom};
2+
use crate::num::TryFromIntError;
3+
4+
mod private {
5+
/// This trait being unreachable from outside the crate
6+
/// prevents other implementations of the `FloatToInt` trait,
7+
/// which allows potentially adding more trait methods after the trait is `#[stable]`.
8+
#[unstable(feature = "convert_float_to_int", issue = "67057")]
9+
pub trait Sealed {}
10+
}
11+
12+
/// Supporting trait for inherent methods of `f32` and `f64` such as `round_unchecked_to`.
13+
/// Typically doesn’t need to be used directly.
14+
#[unstable(feature = "convert_float_to_int", issue = "67057")]
15+
pub trait FloatToInt<Int>: private::Sealed + Sized {
16+
#[cfg(not(bootstrap))]
17+
#[unstable(feature = "float_approx_unchecked_to", issue = "67058")]
18+
#[doc(hidden)]
19+
unsafe fn approx_unchecked(self) -> Int;
20+
}
21+
22+
macro_rules! impl_float_to_int {
23+
( $Float: ident => $( $Int: ident )+ ) => {
24+
#[unstable(feature = "convert_float_to_int", issue = "67057")]
25+
impl private::Sealed for $Float {}
26+
$(
27+
#[unstable(feature = "convert_float_to_int", issue = "67057")]
28+
impl FloatToInt<$Int> for $Float {
29+
#[cfg(not(bootstrap))]
30+
#[doc(hidden)]
31+
#[inline]
32+
unsafe fn approx_unchecked(self) -> $Int {
33+
crate::intrinsics::float_to_int_approx_unchecked(self)
34+
}
35+
}
36+
)+
37+
}
38+
}
39+
40+
impl_float_to_int!(f32 => u8 u16 u32 u64 u128 usize i8 i16 i32 i64 i128 isize);
41+
impl_float_to_int!(f64 => u8 u16 u32 u64 u128 usize i8 i16 i32 i64 i128 isize);
42+
43+
// Conversion traits for primitive integer and float types
44+
// Conversions T -> T are covered by a blanket impl and therefore excluded
45+
// Some conversions from and to usize/isize are not implemented due to portability concerns
46+
macro_rules! impl_from {
47+
($Small: ty, $Large: ty, #[$attr:meta], $doc: expr) => {
48+
#[$attr]
49+
#[doc = $doc]
50+
impl From<$Small> for $Large {
51+
#[inline]
52+
fn from(small: $Small) -> $Large {
53+
small as $Large
54+
}
55+
}
56+
};
57+
($Small: ty, $Large: ty, #[$attr:meta]) => {
58+
impl_from!($Small,
59+
$Large,
60+
#[$attr],
61+
concat!("Converts `",
62+
stringify!($Small),
63+
"` to `",
64+
stringify!($Large),
65+
"` losslessly."));
66+
}
67+
}
68+
69+
macro_rules! impl_from_bool {
70+
($target: ty, #[$attr:meta]) => {
71+
impl_from!(bool, $target, #[$attr], concat!("Converts a `bool` to a `",
72+
stringify!($target), "`. The resulting value is `0` for `false` and `1` for `true`
73+
values.
74+
75+
# Examples
76+
77+
```
78+
assert_eq!(", stringify!($target), "::from(true), 1);
79+
assert_eq!(", stringify!($target), "::from(false), 0);
80+
```"));
81+
};
82+
}
83+
84+
// Bool -> Any
85+
impl_from_bool! { u8, #[stable(feature = "from_bool", since = "1.28.0")] }
86+
impl_from_bool! { u16, #[stable(feature = "from_bool", since = "1.28.0")] }
87+
impl_from_bool! { u32, #[stable(feature = "from_bool", since = "1.28.0")] }
88+
impl_from_bool! { u64, #[stable(feature = "from_bool", since = "1.28.0")] }
89+
impl_from_bool! { u128, #[stable(feature = "from_bool", since = "1.28.0")] }
90+
impl_from_bool! { usize, #[stable(feature = "from_bool", since = "1.28.0")] }
91+
impl_from_bool! { i8, #[stable(feature = "from_bool", since = "1.28.0")] }
92+
impl_from_bool! { i16, #[stable(feature = "from_bool", since = "1.28.0")] }
93+
impl_from_bool! { i32, #[stable(feature = "from_bool", since = "1.28.0")] }
94+
impl_from_bool! { i64, #[stable(feature = "from_bool", since = "1.28.0")] }
95+
impl_from_bool! { i128, #[stable(feature = "from_bool", since = "1.28.0")] }
96+
impl_from_bool! { isize, #[stable(feature = "from_bool", since = "1.28.0")] }
97+
98+
// Unsigned -> Unsigned
99+
impl_from! { u8, u16, #[stable(feature = "lossless_int_conv", since = "1.5.0")] }
100+
impl_from! { u8, u32, #[stable(feature = "lossless_int_conv", since = "1.5.0")] }
101+
impl_from! { u8, u64, #[stable(feature = "lossless_int_conv", since = "1.5.0")] }
102+
impl_from! { u8, u128, #[stable(feature = "i128", since = "1.26.0")] }
103+
impl_from! { u8, usize, #[stable(feature = "lossless_int_conv", since = "1.5.0")] }
104+
impl_from! { u16, u32, #[stable(feature = "lossless_int_conv", since = "1.5.0")] }
105+
impl_from! { u16, u64, #[stable(feature = "lossless_int_conv", since = "1.5.0")] }
106+
impl_from! { u16, u128, #[stable(feature = "i128", since = "1.26.0")] }
107+
impl_from! { u32, u64, #[stable(feature = "lossless_int_conv", since = "1.5.0")] }
108+
impl_from! { u32, u128, #[stable(feature = "i128", since = "1.26.0")] }
109+
impl_from! { u64, u128, #[stable(feature = "i128", since = "1.26.0")] }
110+
111+
// Signed -> Signed
112+
impl_from! { i8, i16, #[stable(feature = "lossless_int_conv", since = "1.5.0")] }
113+
impl_from! { i8, i32, #[stable(feature = "lossless_int_conv", since = "1.5.0")] }
114+
impl_from! { i8, i64, #[stable(feature = "lossless_int_conv", since = "1.5.0")] }
115+
impl_from! { i8, i128, #[stable(feature = "i128", since = "1.26.0")] }
116+
impl_from! { i8, isize, #[stable(feature = "lossless_int_conv", since = "1.5.0")] }
117+
impl_from! { i16, i32, #[stable(feature = "lossless_int_conv", since = "1.5.0")] }
118+
impl_from! { i16, i64, #[stable(feature = "lossless_int_conv", since = "1.5.0")] }
119+
impl_from! { i16, i128, #[stable(feature = "i128", since = "1.26.0")] }
120+
impl_from! { i32, i64, #[stable(feature = "lossless_int_conv", since = "1.5.0")] }
121+
impl_from! { i32, i128, #[stable(feature = "i128", since = "1.26.0")] }
122+
impl_from! { i64, i128, #[stable(feature = "i128", since = "1.26.0")] }
123+
124+
// Unsigned -> Signed
125+
impl_from! { u8, i16, #[stable(feature = "lossless_int_conv", since = "1.5.0")] }
126+
impl_from! { u8, i32, #[stable(feature = "lossless_int_conv", since = "1.5.0")] }
127+
impl_from! { u8, i64, #[stable(feature = "lossless_int_conv", since = "1.5.0")] }
128+
impl_from! { u8, i128, #[stable(feature = "i128", since = "1.26.0")] }
129+
impl_from! { u16, i32, #[stable(feature = "lossless_int_conv", since = "1.5.0")] }
130+
impl_from! { u16, i64, #[stable(feature = "lossless_int_conv", since = "1.5.0")] }
131+
impl_from! { u16, i128, #[stable(feature = "i128", since = "1.26.0")] }
132+
impl_from! { u32, i64, #[stable(feature = "lossless_int_conv", since = "1.5.0")] }
133+
impl_from! { u32, i128, #[stable(feature = "i128", since = "1.26.0")] }
134+
impl_from! { u64, i128, #[stable(feature = "i128", since = "1.26.0")] }
135+
136+
// The C99 standard defines bounds on INTPTR_MIN, INTPTR_MAX, and UINTPTR_MAX
137+
// which imply that pointer-sized integers must be at least 16 bits:
138+
// https://port70.net/~nsz/c/c99/n1256.html#7.18.2.4
139+
impl_from! { u16, usize, #[stable(feature = "lossless_iusize_conv", since = "1.26.0")] }
140+
impl_from! { u8, isize, #[stable(feature = "lossless_iusize_conv", since = "1.26.0")] }
141+
impl_from! { i16, isize, #[stable(feature = "lossless_iusize_conv", since = "1.26.0")] }
142+
143+
// RISC-V defines the possibility of a 128-bit address space (RV128).
144+
145+
// CHERI proposes 256-bit “capabilities”. Unclear if this would be relevant to usize/isize.
146+
// https://www.cl.cam.ac.uk/research/security/ctsrd/pdfs/20171017a-cheri-poster.pdf
147+
// http://www.csl.sri.com/users/neumann/2012resolve-cheri.pdf
148+
149+
150+
// Note: integers can only be represented with full precision in a float if
151+
// they fit in the significand, which is 24 bits in f32 and 53 bits in f64.
152+
// Lossy float conversions are not implemented at this time.
153+
154+
// Signed -> Float
155+
impl_from! { i8, f32, #[stable(feature = "lossless_float_conv", since = "1.6.0")] }
156+
impl_from! { i8, f64, #[stable(feature = "lossless_float_conv", since = "1.6.0")] }
157+
impl_from! { i16, f32, #[stable(feature = "lossless_float_conv", since = "1.6.0")] }
158+
impl_from! { i16, f64, #[stable(feature = "lossless_float_conv", since = "1.6.0")] }
159+
impl_from! { i32, f64, #[stable(feature = "lossless_float_conv", since = "1.6.0")] }
160+
161+
// Unsigned -> Float
162+
impl_from! { u8, f32, #[stable(feature = "lossless_float_conv", since = "1.6.0")] }
163+
impl_from! { u8, f64, #[stable(feature = "lossless_float_conv", since = "1.6.0")] }
164+
impl_from! { u16, f32, #[stable(feature = "lossless_float_conv", since = "1.6.0")] }
165+
impl_from! { u16, f64, #[stable(feature = "lossless_float_conv", since = "1.6.0")] }
166+
impl_from! { u32, f64, #[stable(feature = "lossless_float_conv", since = "1.6.0")] }
167+
168+
// Float -> Float
169+
impl_from! { f32, f64, #[stable(feature = "lossless_float_conv", since = "1.6.0")] }
170+
171+
// no possible bounds violation
172+
macro_rules! try_from_unbounded {
173+
($source:ty, $($target:ty),*) => {$(
174+
#[stable(feature = "try_from", since = "1.34.0")]
175+
impl TryFrom<$source> for $target {
176+
type Error = TryFromIntError;
177+
178+
/// Try to create the target number type from a source
179+
/// number type. This returns an error if the source value
180+
/// is outside of the range of the target type.
181+
#[inline]
182+
fn try_from(value: $source) -> Result<Self, Self::Error> {
183+
Ok(value as $target)
184+
}
185+
}
186+
)*}
187+
}
188+
189+
// only negative bounds
190+
macro_rules! try_from_lower_bounded {
191+
($source:ty, $($target:ty),*) => {$(
192+
#[stable(feature = "try_from", since = "1.34.0")]
193+
impl TryFrom<$source> for $target {
194+
type Error = TryFromIntError;
195+
196+
/// Try to create the target number type from a source
197+
/// number type. This returns an error if the source value
198+
/// is outside of the range of the target type.
199+
#[inline]
200+
fn try_from(u: $source) -> Result<$target, TryFromIntError> {
201+
if u >= 0 {
202+
Ok(u as $target)
203+
} else {
204+
Err(TryFromIntError(()))
205+
}
206+
}
207+
}
208+
)*}
209+
}
210+
211+
// unsigned to signed (only positive bound)
212+
macro_rules! try_from_upper_bounded {
213+
($source:ty, $($target:ty),*) => {$(
214+
#[stable(feature = "try_from", since = "1.34.0")]
215+
impl TryFrom<$source> for $target {
216+
type Error = TryFromIntError;
217+
218+
/// Try to create the target number type from a source
219+
/// number type. This returns an error if the source value
220+
/// is outside of the range of the target type.
221+
#[inline]
222+
fn try_from(u: $source) -> Result<$target, TryFromIntError> {
223+
if u > (<$target>::max_value() as $source) {
224+
Err(TryFromIntError(()))
225+
} else {
226+
Ok(u as $target)
227+
}
228+
}
229+
}
230+
)*}
231+
}
232+
233+
// all other cases
234+
macro_rules! try_from_both_bounded {
235+
($source:ty, $($target:ty),*) => {$(
236+
#[stable(feature = "try_from", since = "1.34.0")]
237+
impl TryFrom<$source> for $target {
238+
type Error = TryFromIntError;
239+
240+
/// Try to create the target number type from a source
241+
/// number type. This returns an error if the source value
242+
/// is outside of the range of the target type.
243+
#[inline]
244+
fn try_from(u: $source) -> Result<$target, TryFromIntError> {
245+
let min = <$target>::min_value() as $source;
246+
let max = <$target>::max_value() as $source;
247+
if u < min || u > max {
248+
Err(TryFromIntError(()))
249+
} else {
250+
Ok(u as $target)
251+
}
252+
}
253+
}
254+
)*}
255+
}
256+
257+
macro_rules! rev {
258+
($mac:ident, $source:ty, $($target:ty),*) => {$(
259+
$mac!($target, $source);
260+
)*}
261+
}
262+
263+
// intra-sign conversions
264+
try_from_upper_bounded!(u16, u8);
265+
try_from_upper_bounded!(u32, u16, u8);
266+
try_from_upper_bounded!(u64, u32, u16, u8);
267+
try_from_upper_bounded!(u128, u64, u32, u16, u8);
268+
269+
try_from_both_bounded!(i16, i8);
270+
try_from_both_bounded!(i32, i16, i8);
271+
try_from_both_bounded!(i64, i32, i16, i8);
272+
try_from_both_bounded!(i128, i64, i32, i16, i8);
273+
274+
// unsigned-to-signed
275+
try_from_upper_bounded!(u8, i8);
276+
try_from_upper_bounded!(u16, i8, i16);
277+
try_from_upper_bounded!(u32, i8, i16, i32);
278+
try_from_upper_bounded!(u64, i8, i16, i32, i64);
279+
try_from_upper_bounded!(u128, i8, i16, i32, i64, i128);
280+
281+
// signed-to-unsigned
282+
try_from_lower_bounded!(i8, u8, u16, u32, u64, u128);
283+
try_from_lower_bounded!(i16, u16, u32, u64, u128);
284+
try_from_lower_bounded!(i32, u32, u64, u128);
285+
try_from_lower_bounded!(i64, u64, u128);
286+
try_from_lower_bounded!(i128, u128);
287+
try_from_both_bounded!(i16, u8);
288+
try_from_both_bounded!(i32, u16, u8);
289+
try_from_both_bounded!(i64, u32, u16, u8);
290+
try_from_both_bounded!(i128, u64, u32, u16, u8);
291+
292+
// usize/isize
293+
try_from_upper_bounded!(usize, isize);
294+
try_from_lower_bounded!(isize, usize);
295+
296+
#[cfg(target_pointer_width = "16")]
297+
mod ptr_try_from_impls {
298+
use super::TryFromIntError;
299+
use crate::convert::TryFrom;
300+
301+
try_from_upper_bounded!(usize, u8);
302+
try_from_unbounded!(usize, u16, u32, u64, u128);
303+
try_from_upper_bounded!(usize, i8, i16);
304+
try_from_unbounded!(usize, i32, i64, i128);
305+
306+
try_from_both_bounded!(isize, u8);
307+
try_from_lower_bounded!(isize, u16, u32, u64, u128);
308+
try_from_both_bounded!(isize, i8);
309+
try_from_unbounded!(isize, i16, i32, i64, i128);
310+
311+
rev!(try_from_upper_bounded, usize, u32, u64, u128);
312+
rev!(try_from_lower_bounded, usize, i8, i16);
313+
rev!(try_from_both_bounded, usize, i32, i64, i128);
314+
315+
rev!(try_from_upper_bounded, isize, u16, u32, u64, u128);
316+
rev!(try_from_both_bounded, isize, i32, i64, i128);
317+
}
318+
319+
#[cfg(target_pointer_width = "32")]
320+
mod ptr_try_from_impls {
321+
use super::TryFromIntError;
322+
use crate::convert::TryFrom;
323+
324+
try_from_upper_bounded!(usize, u8, u16);
325+
try_from_unbounded!(usize, u32, u64, u128);
326+
try_from_upper_bounded!(usize, i8, i16, i32);
327+
try_from_unbounded!(usize, i64, i128);
328+
329+
try_from_both_bounded!(isize, u8, u16);
330+
try_from_lower_bounded!(isize, u32, u64, u128);
331+
try_from_both_bounded!(isize, i8, i16);
332+
try_from_unbounded!(isize, i32, i64, i128);
333+
334+
rev!(try_from_unbounded, usize, u32);
335+
rev!(try_from_upper_bounded, usize, u64, u128);
336+
rev!(try_from_lower_bounded, usize, i8, i16, i32);
337+
rev!(try_from_both_bounded, usize, i64, i128);
338+
339+
rev!(try_from_unbounded, isize, u16);
340+
rev!(try_from_upper_bounded, isize, u32, u64, u128);
341+
rev!(try_from_unbounded, isize, i32);
342+
rev!(try_from_both_bounded, isize, i64, i128);
343+
}
344+
345+
#[cfg(target_pointer_width = "64")]
346+
mod ptr_try_from_impls {
347+
use super::TryFromIntError;
348+
use crate::convert::TryFrom;
349+
350+
try_from_upper_bounded!(usize, u8, u16, u32);
351+
try_from_unbounded!(usize, u64, u128);
352+
try_from_upper_bounded!(usize, i8, i16, i32, i64);
353+
try_from_unbounded!(usize, i128);
354+
355+
try_from_both_bounded!(isize, u8, u16, u32);
356+
try_from_lower_bounded!(isize, u64, u128);
357+
try_from_both_bounded!(isize, i8, i16, i32);
358+
try_from_unbounded!(isize, i64, i128);
359+
360+
rev!(try_from_unbounded, usize, u32, u64);
361+
rev!(try_from_upper_bounded, usize, u128);
362+
rev!(try_from_lower_bounded, usize, i8, i16, i32, i64);
363+
rev!(try_from_both_bounded, usize, i128);
364+
365+
rev!(try_from_unbounded, isize, u16, u32);
366+
rev!(try_from_upper_bounded, isize, u64, u128);
367+
rev!(try_from_unbounded, isize, i32, i64);
368+
rev!(try_from_both_bounded, isize, i128);
369+
}

src/libcore/intrinsics.rs

+5
Original file line numberDiff line numberDiff line change
@@ -1144,6 +1144,11 @@ extern "rust-intrinsic" {
11441144
/// May assume inputs are finite.
11451145
pub fn frem_fast<T>(a: T, b: T) -> T;
11461146

1147+
/// Convert with LLVM’s fptoui/fptosi, which may return undef for values out of range
1148+
/// https://github.com/rust-lang/rust/issues/10184
1149+
#[cfg(not(bootstrap))]
1150+
pub fn float_to_int_approx_unchecked<Float, Int>(value: Float) -> Int;
1151+
11471152

11481153
/// Returns the number of bits set in an integer type `T`
11491154
pub fn ctpop<T>(x: T) -> T;

0 commit comments

Comments
 (0)