Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What happens when casting a big float to a int?

Tags:

rust

I was wondering what would happen when I cast a very large float value to an integer. This is an example I wrote:

fn main() {
    let x = 82747650246702476024762_f32;//-1_i16;
    let y = x as u8;
    let z = x as i32;
    println!("{} {} {}", x, y, z);
}

and the output is:

$ ./casts                                     
82747650000000000000000 0 -2147483648

Obviously the float wouldn't fit in any of the integers, but since Rust so strongly advertises that it is safe, I would have expected an error of some kind. These operations use the llvm fptosi and fptoui instructions, which produce a so called poison value if the value doesn't fit within the type it has been casted to. This may produce undefined behavior, which is very bad, especially when writing Rust code.

How can I be sure my float to int casts don't result in undefined behavior in Rust? And why would Rust even allow this (as it is known for creating safe code)?

like image 332
Ian Rehwinkel Avatar asked Apr 10 '20 16:04

Ian Rehwinkel


People also ask

What will happen if you cast a float to an integer?

Casting a float to an integer truncates the value, so if you have 3.999998 , and you cast it to an integer , you get 3 .

Can a float be cast into an int?

A float value can be converted to an int value no larger than the input by using the math. floor() function, whereas it can also be converted to an int value which is the smallest integer greater than the input using math.

What will happen if you cast a float to an integer Python?

Converting Floats to Integers Python also has a built-in function to convert floats to integers: int() . In this case, 390.8 will be converted to 390 . When converting floats to integers with the int() function, Python cuts off the decimal and remaining numbers of a float to create an integer.

What does casting to an int do?

Casting in python is therefore done using constructor functions: int() - constructs an integer number from an integer literal, a float literal (by removing all decimals), or a string literal (providing the string represents a whole number)


1 Answers

In Rust 1.44 and earlier, if you use as to cast a floating-point number to an integer type and the floating-point number does not fit¹ in the target type, the result is an undefined value², and most things that you can do with it cause undefined behavior.

This serious issue (#10184) was fixed in Rust 1.45. Since that release, float-to-integer casts saturate instead (that is, values that are too large or small are converted to T::MAX or T::MIN, respectively; NaN is converted to 0).

In older versions of Rust, you can enable the new, safe behavior with the -Z saturating-float-casts flag. Note that saturating casts may be slightly slower since they have to check the type bounds first. If you really need to avoid the check, the standard library provides to_int_unchecked. Since the behavior is undefined when the number is out of range, you must use unsafe.

(There used to be a similar issue for certain integer-to-float casts, but it was resolved by making such casts always saturating. This change was not considered a performance regression and there is no way to opt in to the old behavior.)

Related questions

  • Can casting in safe Rust ever lead to a runtime error?

¹ "Fit" here means either NaN, or a number of such large magnitude that it cannot be approximated by the smaller type. 8.7654321_f64 will still be truncated to 8 by an as u8 cast, even though the value cannot be represented exactly by the destination type -- loss of precision does not cause undefined behavior, only being out of range does.

² A "poison" value in LLVM, as you correctly note in the question, but Rust itself does not distinguish between undef and poison values.

like image 149
trent Avatar answered Oct 13 '22 16:10

trent