Obtaining bit representation of a float in C

2020-02-29 19:32发布

问题:

I'm trying to use unions to obtain the bit representation of float values, my code is currently as follows:

union ufloat {
  float f;
  unsigned u;
};

int main( ) {       

   union ufloat u1;
   u1.f = 3.14159f;
   printf("u1.u : %f\n", u1.u);

However anything I try to print gets printed as 0.0000000, instead of as bits (such as 0001 0110, or something similar), what is wrong in my code?

Note that preferebly I would like to use unions to achieve this.

回答1:

There are a large number of ways to accomplish this. Understand that what you are really trying to do is simply output the bits in memory that make up a float. Which in virtually all x86 type implementations are stored in IEEE-754 Single Precision Floating-Point Format. On x86 that is 32-bits of data. That is what allows a 'peek' at the bits while casting the float to unsigned (both are 32-bits, and bit-operations are defined for the unsigned type) For implementations other than x86, or even on x86 itself, a better choice for unsigned would be the exact length type of uint32_t provided by stdint.h. There can be no ambiguity in size that way.

Now, the cast itself isn't technically the problem, it is the access of the value though dereferncing the different type (a.k.a type-punning) where you run afoul of the strict-aliasing rule (Section 6.5 (7) of the C11 Standard). The union of the float and uint32_t types give you a valid way of looking at the float bits through an unsigned type window. (you are looking at the same bits either way, it's just how you access them and tell the compiler how they should be interpreted)

That said, you can glean good information from all of the answers here. You can write functions to access and store the bit representation of the float values in a string for later use, or output the bit values to the screen. As an exercise in playing with floating-point values a year or so back, I wrote a little function to output the bits in an annotated way that allowed easy identification of the sign, normalized exponent, and mantissa. You can adapt it or another of the answers routines to handle your needs. The short example is:

#include <stdio.h>
#include <stdint.h>
#include <limits.h> /* for CHAR_BIT */

/** formatted output of ieee-754 representation of float */
void show_ieee754 (float f)
{
    union {
        float f;
        uint32_t u;
    } fu = { .f = f };
    int i = sizeof f * CHAR_BIT;

    printf ("  ");
    while (i--)
        printf ("%d ", (fu.u >> i) & 0x1);

    putchar ('\n');
    printf (" |- - - - - - - - - - - - - - - - - - - - - - "
            "- - - - - - - - - -|\n");
    printf (" |s|      exp      |                  mantissa"
            "                   |\n\n");
}

int main (void) {

    float f = 3.14159f;

    printf ("\nIEEE-754 Single-Precision representation of: %f\n\n", f);
    show_ieee754 (f);

    return 0;
}

Example Use/Output

$ ./bin/floatbits

IEEE-754 Single-Precision representation of: 3.141590

  0 1 0 0 0 0 0 0 0 1 0 0 1 0 0 1 0 0 0 0 1 1 1 1 1 1 0 1 0 0 0 0
 |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -|
 |s|      exp      |                  mantissa                   |

Look things over and let me know if you have any questions.



回答2:

You could write a simple print_bits-function and use an array of unsigned characters to read out the "raw memory representation" of a float:

void print_bits(unsigned char x)
{
    int i;
    for (i = 8 * sizeof(x) - 1; i >= 0; i--) {
        (x & (1 << i)) ? putchar('1') : putchar('0');
    }
}

typedef float ftype;

union ufloat {
    ftype f;
    unsigned char bytes[sizeof(ftype)];
};

int main( ) {
    union ufloat u1;
    u1.f = .1234;

    for (int i=0; i<sizeof(ftype); i++) {
        unsigned char b = u1.bytes[i];
        print_bits(b);putchar('-');
    }
    return 0;
}

Not sure if the union is actually required (I suppose you introduced this because of alignment issues and UB); When using an array of unsigned char, alignment should not be an issue.



回答3:

There is no format specifier for binary output; generally hexadecimal (base 16) is used for convenience because a single hex digit represents exactly 4 binary digits. There is a format specifier for hexadecimal (%x or %X).

printf( "u1.u : %4X\n", u1.u ) ; 

Alternatively you can generate a binary string representation with itoa() (non-standard, but commonly implemented function).

#include <limits.h>
#include <stdlib.h>
#include <stdio.h>

...

char b[sizeof(float) * CHAR_BIT + 1] = "" ;
printf( "u1.u : %s\n", itoa( u1.u, b, 2 ) ) ; 

The problem with this is that it does not include the leading zeroes, and in the binary floating point representation all bits are significant. It is possible to deal with that, but somewhat cumbersome:

#define BITS (sizeof(float) * CHAR_BIT + 1) ; 
char b[BITS] = itoa( u1.u, b, 2 ) ;
printf( "u1.u : " ) ;
for( int i = 0; i < BITS - strlen(b); i++ )
{
    putchar( '0' ) ;
} 
printf( "%s\n", b ) ; 

Note that in the above examples, the same implicit assumption as in the original question is made that unsigned is at least as large as a float and used the same byte-ordering (older ARM devices for example use a "cross-endian" floating point format!). I have made no attempt at portability in that respect. Ultimately if all you want to do is inspect the memory layout of a float then inspection in a debugger would be the simplest and most compiler implementation independent approach perhaps.



回答4:

To convert any variable/object to a string that encodes the binary, see how to print memory bits in c


print ... as bits (such as 0001 0110, or something similar),

Something similar: Use "%a" to print a float, converted to a double showing its significant in hexadecimal and exponent in decimal power of 2. @Jonathan Leffler

printf("%a\n", 3.14159f);
// sample output
0x1.921fap+1


回答5:

#include <stdio.h>

union
{
    float f;
    unsigned int u;
} myun;

int main ( void )
{
    unsigned int ra;

    printf("%p %lu\n",&myun.f,sizeof(myun.f));
    printf("%p %lu\n",&myun.u,sizeof(myun.u));
    myun.f=3.14159F;
    printf("0x%08X\n",myun.u);
    for(ra=0x80000000;ra;ra>>=1)
    {
        if(ra&myun.u) printf("1"); else printf("0");
    }
    printf("\n");

    for(ra=0x80000000;ra;ra>>=1)
    {
        if(ra==0x40000000) printf(" ");
        if(ra==0x00400000) printf(" ");
        if(ra&myun.u) printf("1"); else printf("0");
    }
    printf("\n");

    return(0);
}

0x601044 4
0x601044 4
0x40490FD0
01000000010010010000111111010000
0 10000000 10010010000111111010000