0

Let's say we have some 128bit floating point number, for example x = 2.6 (1.3 * 2^1 ieee-754). I put in in union like this:

union flt {
        long double flt;
        int64_t byte8[OCTALC];
    } d;
d = x;

Then i run this to get it hexadecimal representation in memory:

void print_bytes(void *ptr, int size) 
{
    unsigned char *p = ptr;
    int i;
    for (i=0; i<size; i++) {
        printf("%02hhX ", p[i]);
    }
    printf("\n");
}

// some where in the code
print_bytes(&d.byte8[0], 16);

And i get something like

66 66 66 66 66 66 66 A6 00 40 00 00 00 00 00 00

So by assumption i expect to see one of the leading bits(the left ones) to be 1(because exponent of 2.6 is 1) but in fact i see right bits to be 1(like it treating value big-endian). If i flip sign the output changes to:

66 66 66 66 66 66 66 A6 00 C0 00 00 00 00 00 00

So it seems like sign bit is righter than i thought. And if you count the bytes it seems like there is only 10 bytes used remaining 6 is like truncated or something. I trying to find out why this happens any help?

DLWHI
  • 71
  • 5
  • Long double is 10 bytes on your platform, 80 bits. Who told you it was 16 bytes? I guess you are probably using an x86 processor in 32-bit mode because that is the only architecture which has the weird 10-byte format. – user253751 May 24 '22 at 15:56
  • And yes, it's little-endian – user253751 May 24 '22 at 15:57
  • 1
    print `sizeof(long double)` – stark May 24 '22 at 16:00
  • 2
    @user253751 I'm guessing `sizeof` told them it was 16 bytes. In my experience, that "weird 10-byte format" is used in x86-64 mode, too, albeit padded out to 16 bytes. – Steve Summit May 24 '22 at 16:33
  • @stark I'm not DLWHI, but I see the same results on my machine, and `sizeof(long double)` is definitely 16. – Steve Summit May 24 '22 at 16:34

2 Answers2

3

You have a number of misconceptions.

First of all, you don't have a 128-bit floating point number. long double is probably a float in the x86 extended precision format on an x86-64. This is an 80 bit (10 byte) value, which is padded to 16 bytes. (I suspect this is for alignment purposes.)

And of course, it's going to be in little-endian byte order (since this is an x86/x86-64). This doesn't refer to the order of the bits in each byte, it refers to the order of the bytes in the whole.

And finally, the exponent is biased. An exponent of 1 isn't stored as 1. It's stored as 1+0x3FFF. This allows for negative exponents.


So we get the following:

66 66 66 66 66 66 66 A6 00 40 00 00 00 00 00 00

Demo on Compiler Explorer

If we remove the padding and reverse the bytes to better match the image in the Wikipedia page, we get

4000A666666666666666

This translates to

+0x1.4CCCCCCCCCCCCCCC × 2^(0x4000-0x3FFF)

(0xA66...6 = 0b1010 0110 0110...0110 ⇒ 0b1.0100 1100 1100...110[0] = 0x1.4CC...C)

or

+1.29999999999999999995663191310057982263970188796520233154296875 × 2^1

Decimal conversion obtained using

perl -Mv5.10 -e'
   use Math::BigFloat;
   Math::BigFloat->div_scale( 1000 );
   say
      Math::BigFloat->from_hex(  "4CCCCCCCCCCCCCCC" ) /
      Math::BigFloat->from_hex( "10000000000000000" )
'

or

perl -Mv5.10 -e'
   use Math::BigFloat;
   Math::BigFloat->div_scale( 1000 );
   say
      Math::BigFloat->from_hex( "A666666666666666" ) /
      Math::BigFloat->from_hex( "8000000000000000" )
'
ikegami
  • 367,544
  • 15
  • 269
  • 518
  • 1
    I'm guessing you got `00 68 66` instead of `66 66 66` for the same reason I did, namely that I first said `long double ld = 2.6;`. I couldn't remember the right suffix for a long double constant, and I was too lazy to look it up, so I changed it to `ld = 26; ld /= 10;`, and then I got `66 66 66`, just like the OP. – Steve Summit May 24 '22 at 16:37
  • But the "misconception" that `long double` is 128 bits is rather borne out by the fact that `sizeof` reports 16 for it! – Steve Summit May 24 '22 at 16:39
  • @Steve Summit oh right, fixed. (`2.6L`) /// Yes. As mentioned, I suspect this is for alignment purposes. Just like `sizeof( struct { int; char } )` will be larger than `sizeof(int)+sizeof(char)` – ikegami May 24 '22 at 16:50
  • @Steve Summit, yeah, I noticed it was wrong and fixed it. With IEEE single and double precision, the integer "1" is implied (not stored). But it is stored with this format. – ikegami May 24 '22 at 17:00
  • 1
    The x87 registers really are just 80 bits, and the load and store instructions really are 10 bytes in size. The 6 bytes of padding are purely the compiler's decision. Everything works without any particular alignment, though it can help for performance. – Nate Eldredge May 24 '22 at 17:01
  • @Nate Eldredge Removed mention of register size. /// Re "*Everything works without any particular alignment*", On the hardware in question, yes. /// Re "*though it can help for performance*", That is a purpose of alignment. – ikegami May 24 '22 at 17:02
1

You've been bamboozled by some very strange aspects of the way extended-precision floating-point is typically implemented in C on Intel architectures. So don't feel too bad. :-)

What you're seeing is that although sizeof(long double) may be 16 (== 128 bits), deep down inside what you're really getting is the 80-bit Intel extended format. It's being padded out with 6 bytes, which in your case happen to be 0. So, yes, "the sign bit is righter than you thought".

I see the same thing on my machine, and it's something I've always wondered about. It seems like a real waste, doesn't it? I used to think it was for some kind of compatibility with machines which actually do have 128-bit long doubles. But that can't be it, because this 0-padded 16-byte format is not binary-compatible with true IEEE 128-bit floating point, among other things because the padding is on the wrong end.

Steve Summit
  • 45,437
  • 7
  • 70
  • 103
  • [x86 extended precision format](https://en.wikipedia.org/wiki/Extended_precision#x86_extended_precision_format) is a x86-specific thing with its very own motivations. Basically, the idea is to have operations on 64-bit double precision without *losing* precision. – DevSolar May 24 '22 at 16:17