Change:
Given this code (): It should print . In release mode, that happens, but in debug mode, it prints . The value is mostly irrelevant, it would also work for . cc cc tracking issue
is undefined. It ends up being as in LLVM. This issue is in a similar vein as the float to integer casts. All we really need is a decision on how to handle this. For u128-f32 it seems pretty obvious to simply produce an infinity.
If it is currently UB then maybe needs to be reopened.
to f32 converts just fine.
Yes, because the maximum value of a u64 fits in a f32 fine.
Cross-linking
Don't forget .
Hi! Sorry if this is a stupid suggestion… but as someone who uses u128 (in several cryptographic libraries) and doesn't use floats, I wonder how many users actually need safe casting to floats? Is this something one would do in graphics development? If there isn't much use for it, perhaps a simple fix would be to disallow casts, i.e. they should have to first cast to .
If a new -cast rule is going to be introduced, please make it generic so that it can be naturally extensible to something like or GMP types
Rust is a general purpose programming language and it already has a syntax for doing conversions that one could expect to work on all the primitive types, so it not supporting -casts for only this particular conversion would make the language inconsistent. Furthermore, consider a code like this: Now this macro works just fine for every single integer and float combo. Forbidding it for i/u128 would extend the discrepancy to macros as well. I hope that’s enough of demonstration of a need to keep functionality consistent.
The consistency issue, esp. w.r.t. macros, makes perfect sense. Thanks for taking the time to point that out.
currently generates this LLVM IR: which, on x8664, i686 and aarch64, becomes a call to the compilerrt function Directly calling the function, instead of generating the instruction, seems to be able to hide the UB? The casts to +∞.
It makes no sense to do that over just adding the relevant branches into the IR, because it inhibits pretty much every optimisation with the values involved.
Yes we could do this producing producing (unfortunately the function will always be called, and the cold branch check remains)
gives with . Could the range check placed there as a first step, to prevent a naked being generated? Or just add a lint/error if produces an ? Or wait for miri?
LLVM will want some solution that’s more general and we want to avoid having custom patches in our LLVM, so I do not think that would work. Interesting option, but does not help with code like which is still very UB, even if it does not produce a literal at the moment after the optimisations.
Ignoring LLVM and C/C++ for the moment, IEEE 754 section 5.4.1 "Arithmetic operations" defines and says: Integer to floating-point conversions typically use "to nearest" rounding. IEEE 754 section 4.3.1 "Rounding-direction attributes to nearest" defines "no nearest" and says: fits that condition when converted to . So by IEEE 754 rules, for what that's worth, the result is defined and has the value ∞.
Marking P-medium to match other similar bugs.
has been merged, so there's now an opt-in solution to this issue (). Presumably we'll want to make this behavior the default at some point in the future &ndash; does anyone have opinions on when that should be and what prerequisites there are (if any)?
I'd say we should do a public call, asking people to test the feature and report back. Then if there are no major issues after idk, 2-3 weeks (some people only read the weekly newsletter), we could flip the defaults and allow an opt out. It could stay this way on the nightly channel and if there continue to be no issues, we can let it ride the trains and include it in the next beta to be to the next stable (without an opt out, as it would require a -C option and you won't be able to remove the opt-out again). If we hear about issues while the feature is in the beta channel, we can disable it on the beta channel as well.
Huh, that's a bit more scrutiny than I would have expected. To be clear, in the context of this issue I am only talking about u128 -f32 casts, the float -int direction covered by can and should be rolled out independently. With that in mind, do you think the change to u128 -f32 casts is more likely to cause regressions than the average bug fix?
I've file a PR () to just make the u128-f32 behavior the default, refocusing the -Z flag to just cover float-int casts. As with any other change, we'll have up to six weeks in nightly and six weeks of beta to find and fix any regressions there may be.
FYI overflowing s/uitofp casts are no longer considered undefined as of So we should be able to drop the extra range checking code somewhere in the future.

Discussion:
tls_model: Option<String> = (None, parse_opt_string, [TRACKED], "choose the TLS model to use (rustc --print tls-models for details)"), saturating_float_casts: bool = (false, parse_bool, [TRACKED], <del> "make casts between integers and floats safe: clip out-of-range inputs to the min/max  integer or to infinity respectively, and turn `NAN` into 0 when casting to integers"), </del> <ins> "make float->int casts UB-free: numbers outside the integer type's range are clipped to  the max/min integer respectively, and NaN is mapped to 0"), </ins> } pub fn default_lib_output() -> CrateType {

// It's only u128 -> f32 that can cause overflows (i.e., should yield infinity). // LLVM's uitofp produces undef in those cases, so we manually check for that case. let is_u128_to_f32 = !signed && int_ty.int_width() == 128 && float_ty.float_width() == 32; <del> if is_u128_to_f32 && bcx.sess().opts.debugging_opts.saturating_float_casts { </del> <ins> if is_u128_to_f32 { </ins> // All inputs greater or equal to (f32::MAX + 0.5 ULP) are rounded to infinity, // and for everything else LLVM's uitofp works just fine. let max = C_big_integral(int_ty, MAX_F32_PLUS_HALF_ULP);

} #[no_mangle] <del> pub fn f64_to_u8(x: f32) -> u16 { </del> <ins> pub fn f64_to_u16(x: f64) -> u16 { // CHECK: fptoui </ins> // CHECK-NOT: fcmp // CHECK-NOT: icmp // CHECK-NOT: select x as u16 } <del> // CHECK-LABEL: @i32_to_f64 #[no_mangle] pub fn i32_to_f64(x: i32) -> f64 { // CHECK: sitofp // CHECK-NOT: fcmp // CHECK-NOT: icmp // CHECK-NOT: select x as f64 } // CHECK-LABEL: @u128_to_f32 #[no_mangle] pub fn u128_to_f32(x: u128) -> f32 { // CHECK: uitofp // CHECK-NOT: fcmp // CHECK-NOT: icmp // CHECK-NOT: select x as f32 } </del>

// option. This file may not be copied, modified, or distributed // except according to those terms. <ins> // Tests saturating float->int casts. See u128-as-f32.rs for the opposite direction. </ins> // compile-flags: -Z saturating-float-casts #![feature(test, i128, i128_type, stmt_expr_attributes)]

// nextDown(f32::MAX) = 2^128 - 2 * 2^104 const SECOND_LARGEST_F32: f32 = 340282326356119256160033759537265639424.; test_c!(SECOND_LARGEST_F32, f32 -> u128, 0xfffffe00000000000000000000000000); <del> // int->float: // f32::MAX - 0.5 ULP and smaller should be rounded down test_c!(0xfffffe00000000000000000000000000, u128 -> f32, SECOND_LARGEST_F32); test_c!(0xfffffe7fffffffffffffffffffffffff, u128 -> f32, SECOND_LARGEST_F32); test_c!(0xfffffe80000000000000000000000000, u128 -> f32, SECOND_LARGEST_F32); // numbers within < 0.5 ULP of f32::MAX it should be rounded to f32::MAX test_c!(0xfffffe80000000000000000000000001, u128 -> f32, f32::MAX); test_c!(0xfffffeffffffffffffffffffffffffff, u128 -> f32, f32::MAX); test_c!(0xffffff00000000000000000000000000, u128 -> f32, f32::MAX); test_c!(0xffffff00000000000000000000000001, u128 -> f32, f32::MAX); test_c!(0xffffff7fffffffffffffffffffffffff, u128 -> f32, f32::MAX); // f32::MAX + 0.5 ULP and greater should be rounded to infinity test_c!(0xffffff80000000000000000000000000, u128 -> f32, f32::INFINITY); test_c!(0xffffff80000000f00000000000000000, u128 -> f32, f32::INFINITY); test_c!(0xffffff87ffffffffffffffff00000001, u128 -> f32, f32::INFINITY); // u128->f64 should not be affected by the u128->f32 checks test_c!(0xffffff80000000000000000000000000, u128 -> f64, 340282356779733661637539395458142568448.0); test_c!(u128::MAX, u128 -> f64, 340282366920938463463374607431768211455.0); </del> } }

<ins> // Copyright 2017 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your // option. This file may not be copied, modified, or distributed // except according to those terms. // ignore-emscripten u128 not supported #![feature(test, i128, i128_type)] #![deny(overflowing_literals)] extern crate test; use std::f32; use std::u128; use test::black_box; macro_rules! test { ($val:expr, $src_ty:ident -> $dest_ty:ident, $expected:expr) => ({ { const X: $src_ty = $val; const Y: $dest_ty = X as $dest_ty; assert_eq!(Y, $expected, "const eval {} -> {}", stringify!($src_ty), stringify!($dest_ty)); } // black_box disables constant evaluation to test run-time conversions: assert_eq!(black_box::<$src_ty>($val) as $dest_ty, $expected, "run-time {} -> {}", stringify!($src_ty), stringify!($dest_ty)); }); } pub fn main() { // nextDown(f32::MAX) = 2^128 - 2 * 2^104 const SECOND_LARGEST_F32: f32 = 340282326356119256160033759537265639424.; // f32::MAX - 0.5 ULP and smaller should be rounded down test!(0xfffffe00000000000000000000000000, u128 -> f32, SECOND_LARGEST_F32); test!(0xfffffe7fffffffffffffffffffffffff, u128 -> f32, SECOND_LARGEST_F32); test!(0xfffffe80000000000000000000000000, u128 -> f32, SECOND_LARGEST_F32); // numbers within < 0.5 ULP of f32::MAX it should be rounded to f32::MAX test!(0xfffffe80000000000000000000000001, u128 -> f32, f32::MAX); test!(0xfffffeffffffffffffffffffffffffff, u128 -> f32, f32::MAX); test!(0xffffff00000000000000000000000000, u128 -> f32, f32::MAX); test!(0xffffff00000000000000000000000001, u128 -> f32, f32::MAX); test!(0xffffff7fffffffffffffffffffffffff, u128 -> f32, f32::MAX); // f32::MAX + 0.5 ULP and greater should be rounded to infinity test!(0xffffff80000000000000000000000000, u128 -> f32, f32::INFINITY); test!(0xffffff80000000f00000000000000000, u128 -> f32, f32::INFINITY); test!(0xffffff87ffffffffffffffff00000001, u128 -> f32, f32::INFINITY); // u128->f64 should not be affected by the u128->f32 checks test!(0xffffff80000000000000000000000000, u128 -> f64, 340282356779733661637539395458142568448.0); test!(u128::MAX, u128 -> f64, 340282366920938463463374607431768211455.0); } </ins>