-
Notifications
You must be signed in to change notification settings - Fork 353
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support unsafe float-to-int casts #1264
Comments
The docs for the apfloat cast method says
So, it seems like our "as" implementation matches |
@hanna-kruppe is the expert here, but that sounds right to me. |
Yes, the behavior in that quote matches the runtime behavior with |
Hm... sounds like a reasonable interpretation, but it's a bit worrying that terminology doesn't match up. |
Miri float->int casts: be explicit that this is saturating r? @hanna-kruppe Cc rust-lang/miri#1264
Okay, with #1265 it seems like our cast story is correct (except for the NaN issue tracked at rust-lang/rust#69532). I confirmed locally that this matches behavior of rustc with What remains is the unsafe intrinsic, where the hard part is the UB checking. How do we detect if casting the float to the target int type overflowed or not? The safety docs for these methods say the input must "be representable in the return type We could also compare the input with |
I believe the simplest way (for miri) is:
The conversion to u128 has no equivalent for signed types but waves hand splitting the float into (sign, absolute value) should solve that. Should not be a conceptual challenge, this kind of trick just often leads to extra bookkeeping that can be annoying to spell out (which is why I hand-wave it here). |
Actually, just casting to the right bit width to begin with should work just fine (for unsigned types; again signed types need extra care). The cast needs to perform the integer range check anyway, and for reasons described above |
For signed casts, why would For |
So there will never be a problem where truncation to an integer leads to more rounding loss, or so? The truncation operation is always exactly representable? |
Oops, I did not see those on a glance because they're provided methods. Yes, either
According to the code, the only difference should be that casting
Right, this operation is exact. Conceptually, it just clears any bits of the significant that have "weight" smaller than 1. It's not quite so simple on the level of bit strings (because of the implicit leading 1 in normal numbers, the exponent sometimes has to change), but still you'll never get in the situation where you have to round off non-zero digits. |
Looking at the implementation, I'd say (argh GitHub, this is what we get for using separate repos, I guess 😠) |
As far as I can tell, that’s correct.
Uh, it seems to round up. The output for me is:
It’s more obvious in hexadecimal, and when also finding the previous representable fn main() {
println!("{:20x}", u64::MAX);
println!("{:20x}", u64::MAX as f64 as u128);
println!(
"{:20x}",
(0..u64::MAX)
.rev()
.find(|&n| n as f64 != u64::MAX as f64)
.unwrap()
);
} Output:
https://doc.rust-lang.org/nightly/reference/expressions/operator-expr.html#type-cast-expressions documents:
Rounding up causes an error of 1, but rounding down would cause an error of 1024 so the former is closest. |
As to the behavior of C11 standard https://port70.net/~nsz/c/c11/n1570.html#6.3.1.4
C++17 standard http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4659.pdf#section.7.10
It looks like truncation towards zero matches the existing Correctly implementing a check that a (truncated) float value can be represented can be tricky however, especially for large integer types. I’d recommend looking at the implementation of |
Oh so by looking at these flags I can actually skip the first truncation step? Or, maybe playing it safer, I could truncate and then make sure there are no flags at all? However, when we are casting to
Ouch, clearly I cannot read. Thanks for correcting me.
Yeah, that's the usual problem with the LLVM docs, you have to do a lot of guesswork if you want to know the UB conditions. :/ That's why I went for your Rust-side docs instead. Thanks for giving the sources for those!
That's what I was worried about, but apfloat's Open questions:
I don't think that actually helps for UB detection: that code saturates when the float exceeds |
After looking over it for a few minutes, this claim seems plausible but I'm not 100% sure about it. To me, removing fractional bits first seems easier to have confidence in, and performance is not really a concern here. There's also the open question of
No, you're right. The [f_min, f_max] range computed there is an under-approximation of the range where |
Sorry I don’t have a precise source for each of these but my understanding/recollection is:
Yes.
No. The integral part of negative zero can be represented as integer zero.
No.
Right, in an algorithm based on comparing they’ll behave the same as out-of-range finite values.
Oh you’re right, sorry. Scratch that recommendation :) |
There are a bunch of testcases for this at https://github.com/WebAssembly/testsuite/blob/master/conversions.wast (WebAssembly semantics seems to match ours). |
Hm, some of these use hexadecimal floating-point notation, such as |
Ah, I can convert them using godbolt ;) (and here's the |
You could also use https://crates.io/crates/hexf |
|
Well that doesn't help, I need to input them in the |
Ah you're not writing an unit test, my bad. |
I implemented the unchecked intrinsic in #1325. Reviews would be welcome, in particular for the testcases. :) |
With rust-lang/rust#66841, we have some unsafe intrinsics to float-to-int casts. We should implement those in Miri, with appropriate UB checking of course. So we need to figure out how to check, with apfloat, if the cast would go wrong.
We also should check that the float-to-int "as" casts in Miri match the planned safe behavior in Rust. I am not sure what those plans currently are, probably whatever(rust-lang/rust#70437, #1265)-Z saturating-float-casts
does. @eddyb thefrom_i128
/from_u128
methods in apfloat, are they saturating?The text was updated successfully, but these errors were encountered: