-
Notifications
You must be signed in to change notification settings - Fork 18k
cmd/compile: Go round-trip fails float32->float64->float32 but C works with gcc 7.4.1 and 8.3.1 (-O0, ..., -O3, -Og, -Os) #36399
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
Comments
This is the same thing that happens in C:
Prints (using
Both Go and C use this pair:
So this is just the underlying behavior of the hardware. |
@randall77 holy cow, you already made progress in #36400! Thanks! I wrote sNaN preserving conversion func today for float16 x448/float16#4 because its Fromfloat32 function was written to produce identical results as AMD/Intel F16C instructions (not always desirable for CBOR encoding.) The new function in cbor-go/float16 (FromNaN32ps) converts NaN while preserving the signal/quiet bit. It's simple enough for Go to inline for float32 to float16 and could be modified to work with other sizes. Please let me know if you need anything but I get the impression you already tracked down the root cause! |
I think the fix for #36400 is easy, if a bit tedious as far as lines of code goes. I don't see any easy way to fix this one. I'm not convinced this problem deserves replacing all instances of float64->float32 conversion with something like You want it to preserve the signal/quiet bit, but what about the rest of the mantissa bits? We can't preserve them all. As far as I can tell, the For float32->float64
For float64->float32
Your test code above has the state of the signal/quiet bit backwards. The bit is 1 for quiet NaNs and 0 for signaling NaNs, so the test at line 19 should be reversed (when fixed, it will print that the signaling bit is lost in the float32->float64 direction also). I think it's perfectly reasonable to define conversions to drop the signaling bit (in both directions). It isn't really necessary, but it seems that's how it's currently implemented, on x86 at least. |
@randall77 I could've chosen better variable names and messages. However,
With gcc (using -O0, -Og, -O1, -O2, -O3, -Os, or no options) your C program always outputs:
Versions of gcc I tried:
Here's an improved reproducer in Go that prints:
EDITED: Fixed cut/paste bug in new reproducer. Set qforced64to32 = true if needed. Same mistake in randall77's modified example is my fault, not his!
|
Lots to figure out here... First, my C code. You're right, on linux with gcc the signal/quiet bit persists on float32->float64->float32. For float32->float64->float32, gcc does:
Even at -O0, it looks like gcc optimized out the conversion to float64 and back. For float32->float64->float32, clang does:
Just like Go does. Here's a reproducer that works with gcc also (at -O0 and -O1):
The code generated by gcc has this in f1:
and this in f2:
You're right, I was incorrect about the error in your test code. I got confused by what was happening with the constant propagation in the compiler. Let's leave that issue for #36400 .
This code shows Go sets the quiet bit in both conversions (32->64 and 64->32). |
I can get GCC to turn on the quiet bit, via #include <stdio.h>
#include <stdint.h>
__attribute__((noinline))
double d(double x) {
return x;
}
__attribute__((noinline))
float f(float x) {
return (float)d((double)x);
}
int main(int argc, char *argv[]) {
uint32_t x = 0x7f800001;
float f32 = *(float*)(&x);
float g32 = f(f32);
uint32_t y = *(uint32_t*)(&g32);
printf("%x %x %x\n", x, y, x^y);
} |
I see! Thanks for the followups. Do you kind folks know if Go always forces quiet bit = 1 for non-const floats when casting them on other architectures like ARMv8 and POWER8? BTW, I fixed a cut/paste error in the new reproducer by updating the comment. The 2nd |
I don't know for sure about other architectures, but I suspect they do as well. |
Change https://golang.org/cl/213477 mentions this issue: |
We store 32-bit floating point constants in a 64-bit field, by converting that 32-bit float to 64-bit float to store it, and convert it back to use it. That works for *almost* all floating-point constants. The exception is signaling NaNs. The round trip described above means we can't represent a 32-bit signaling NaN, because conversions strip the signaling bit. To fix this issue, just forbid NaNs as floating-point constants in SSA form. This shouldn't affect any real-world code, as people seldom constant-propagate NaNs (except in test code). Additionally, NaNs are somewhat underspecified (which of the many NaNs do you get when dividing 0/0?), so when cross-compiling there's a danger of using the compiler machine's NaN regime for some math, and the target machine's NaN regime for other math. Better to use the target machine's NaN regime always. This has been a bug since 1.10, and there's an easy workaround (declare a global varaible containing the signaling NaN pattern, and use that as the argument to math.Float32frombits) so we'll fix it in 1.15. Fixes #36400 Update #36399 Change-Id: Icf155e743281560eda2eed953d19a829552ccfda Reviewed-on: https://go-review.googlesource.com/c/go/+/213477 Run-TryBot: Keith Randall <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Josh Bleecher Snyder <[email protected]>
Tests show that all platforms force the quiet bit. |
I'm going to close this issue as working as intended. |
Thanks for looking into this and sharing your findings. Sounds like you made the right call. |
What version of Go are you using (
go version
)?Does this issue reproduce with the latest release?
yes
What operating system and processor architecture are you using (
go env
)?linux/amd64
go env
OutputWhat did you do?
More at fxamacker/cbor#93
Bug reproducer:
https://play.golang.org/p/kzLI9A07wRv
What did you expect to see?
Consistent behavior when casting from float64 to float32 and vice versa regarding the NaN signalling bit (quiet bit).
What did you see instead?
Inconsistent behavior when casting float with NaN signalling bit.
The text was updated successfully, but these errors were encountered: