4

Just out of curiosity, I'm using Compiler Explorer to see the assembly output of some simple C++ code.

Consider the following example

int main(void){
    double x = -5.3;
}

Assembly output

main:
        push    rbp
        mov     rbp, rsp
        movsd   xmm0, QWORD PTR .LC0[rip]
        movsd   QWORD PTR [rbp-8], xmm0
        mov     eax, 0
        pop     rbp
        ret
.LC0:
        .long   858993459
        .long   -1072352461

I would like to understand how to use

.LC0:
        .long   858993459
        .long   -1072352461

to get back my -5.3.

My uninformed guess is that I need to merge the bit patterns of the two 32 bit integers and to interpret it as the bit pattern of a double precision floating point number. But how, exactly? Must I interpret the pattern as an IEEE754 double precision? In what order?

Catskul
  • 17,916
  • 15
  • 84
  • 113
booNlatoT
  • 571
  • 3
  • 14
  • 4
    The first long (32 bits) is the lower part of the 64-bit double float and the second is the upper part. You can convert both values into hex and then join them together to get the 64-bit hex value. The 64-bit hex value will be in IEEE-754 format. In this case converting the first value is HEX `33333333` . The second one is `C0153333`. The 64-bit HEX value is `C015333333333333` which should be -5.3. – Michael Petch Aug 16 '18 at 18:42
  • 3
    You can use an online converter to convert the 64-bit encoded IEE754 value to a decimal floating point: https://babbage.cs.qc.cuny.edu/IEEE-754.old/64bit.html – Michael Petch Aug 16 '18 at 18:50
  • wikipedia as well as other places shows the breakdown/format of IEEE 754 floating point numbers so you can see how/where those bits go. easier to start with single precision then work your way up but at the same time its just more bits as you work your way up. the formats work the same way. – old_timer Aug 16 '18 at 20:29
  • in CE you can hover your mouse over the constants to see the hex values. Clang and ICC outputs are a lot more readable in this case, you should try those – phuclv Aug 17 '18 at 05:36

2 Answers2

4

But how, exactly? ...

Yes, this is an integer representation of the IEEE754 binary64 (aka double) bit pattern. GCC always prints FP constant this way because they are sometimes the result of constant-propagation, not FP literals that appear in the source. (Also it avoids any dependence on FP rounding in the assembler.)

gcc always uses decimal for integer constants in its asm output, which is pretty inconvenient for humans. (On the Godbolt compiler explorer, use the mouseover tooltip to get hex for any number.)

Clang's asm output is nicer, and includes a comment with the decimal value of the number:

    .quad   -4605718748921121997    # double -5.2999999999999998

In what order?

x86's float endianness matches its integer endianness: both are little-endian. (It's possible for this not to be the case, but all the modern mainstream architectures use the same endianness for integer and float, either big or little. Floating point Endianness?. And Endianness for floating point.)

So when loaded as a 64-bit IEEE-754 double, the low 32 bits in memory are the low 32 bits of the double.

As @MichaelPetch explains in comments, the first/low dword is 0x33333333, and the second/high dword is 0xC0153333. Thus the entire double has a bit-pattern of C015333333333333

For single-precision float, there's https://www.h-schmidt.net/FloatConverter/IEEE754.html. (It's pretty nice, it breaks down the bits into binary with checkboxes, as well as hex bit-pattern and decimal fraction. Great for learning about how FP exponent / significand works.)

For double-precision as well, see https://babbage.cs.qc.cuny.edu/IEEE-754.old/64bit.html. You can put in a bit-pattern and see the hex value.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • why it is converted to long, due to no floating point immediate in assembly or something else ? – srilakshmikanthanp Apr 28 '20 at 07:31
  • @Srilakshmikanthan: The compiler already knows what bit-pattern it wants, so it's easier and more efficient for it to just emit that as an integer in the asm. It also avoids any dependence on host floating-point format vs. target floating-point format. double -> string is slow, and so is string->double (for the assembler that has to parse this file). GAS does support floating-point literals but compiler-generated code chooses not to use them. (You said "immediate": that's a literal operand for a machine instruction. In this case it's just literal data for a `.quad` or `.long`) – Peter Cordes Apr 28 '20 at 07:45
  • What i did not understand is, there no floating point immediate assembly so floating point numbers are declared in global ( https://stackoverflow.com/questions/47946389/how-to-move-a-floating-point-constant-value-into-an-xmm-register ).If use integer in c convert into asm it contains no global variable for int but for float the global variable is present so what i assumed is that for all float point variables there is an global varible due to no floating point immediate like (movsd xmm0,12.12) , is this right? – srilakshmikanthanp Apr 28 '20 at 08:01
  • @Srilakshmikanthan: Oh, yes that's correct. x86 has no immediate-source FP instructions. (Fun fact: I think ARM has some). Some assemblers have syntax to let you do `mov rax, 3.14` to get a double-precision bit-pattern into RAX, so then you could do `movq xmm0, rax`. And of course with any assembler you could use the integer bit pattern for `mov rax, 0xC015333333333333`. But that's generally less efficient than loading a float/double from `.rodata`. – Peter Cordes Apr 28 '20 at 08:08
  • Thanks a lot and i have doubt with another one thing in this line `movsd xmm0, QWORD PTR .LC0[rip]` what is the relation between `rip` and `.LC0` i have seen some question regard to this but i did not understand and why it is not like that `movsd xmm0, QWORD PTR [.LC0]` it is wrong? – srilakshmikanthanp Apr 28 '20 at 08:23
  • @Srilakshmikanthan: That's another different question. Did you try googling it? `site:stackoverflow.com RIP static data` found several Q&As, including [Why are global variables in x86-64 accessed relative to the instruction pointer?](//stackoverflow.com/q/56262889) (why RIP-relative addressing modes are used) and [How do RIP-relative variable references like "\[RIP + \_a\]" in x86-64 GAS Intel-syntax work?](//stackoverflow.com/q/54745872) (what the syntax means), and other versions of the same Q: [Why does this MOVSS instruction use RIP-relative addressing?](//stackoverflow.com/q/44967075) – Peter Cordes Apr 28 '20 at 08:28
0
#include <iostream>
typedef struct{
    union{
        double decimal;
        struct{
            int a;
            int b;
        }v;
    };
}Double2Int_t;
int main(){
    int a1=858993459;
    int a2=-1072352461;
    double value=-5.3;
    Double2Int_t decimal;
    decimal.decimal=value;
    std::cout<<decimal.v.a<<" "
            <<decimal.v.b<<std::endl;
    Double2Int_t decimal2;
    decimal2.v.a=a1;
    decimal2.v.b=a2;
    std::cout<<decimal2.decimal<<std::endl;
    return 0;
}

杨佩文
  • 49
  • 1