< up >
2022-07-27 15:00:13 +0200 CEST

Inside a C float

Preface

This post was originally published in my old blog raphaelpour.de on 12.01.2018. This was a great introduction of how memory gets intrpreted within a type system and how to look behind the magic. I’ve added the part IEEE 754 components.

Article

Handling with floats can  be irritating while it is one of the non-integral datatypes. This means that the displayed value (e.g. via printf("%f",somefloat))  is different to the one actualy stored in memory. This is caused by IEEE754.

To get the real values we have to go inside the float. A cast wouldn’t do the job, because it would just turn a 3.1415 into 3[1]. C has a very nice keyword which can help us: union Instead of casting datatypes, we just map different variables with different datatypes to the exact same memory.

The following four-liner will do the trick for us.

union floatDbg{
    float f;
    unsigned char c[4];
};

Now we can access every byte of the float and get the real value. To know what the values are telling us, we would need to know the IEEE754 Algorithm. But that is another story.

union floatDbg dbg;
dbg.f = 13.37;

// decimal output
printf("%.2f -> %d %d %d %d\n", dbg.f, dbg.c[0], dbg.c[1], dbg.c[2], dbg.c[3]); 
// out: 13.37 -> 133 235 85 65

// hex output
printf("%.2f -> %x %x %x %x\n", dbg.f, dbg.c[0], dbg.c[1], dbg.c[2], dbg.c[3]);
// out: 13.37 -> 85 eb 55 41

IEEE 754 compnents

On my little endian machine, we can even get the actual components of the IEE754 float. Bitmasks help to limit the amount of bits we want to map into a struct field:

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

typedef union {
    float f;
    unsigned char c[4];
    struct {
      unsigned fraction:23;
      unsigned exponent:8;
      unsigned sign:1;
    };
}floatDbg;

void dump(floatDbg dbg);

int main(int argc, char* argv[]) {
  floatDbg dbg,dbg2;

  dbg.f = 13.37;

  printf("--[ raw ]--\n");
  printf("   float: %.2f\n", dbg.f);
  printf("   bytes: %02x %02x %02x %02x\n", dbg.c[3], dbg.c[2], dbg.c[1], dbg.c[0]);
  printf("     bin: %08b %08b %08b %08b\n", dbg.c[3], dbg.c[2], dbg.c[1], dbg.c[0]);
  printf("\n");
  printf("--[ iee754 components] --\n");
  printf("    sign: %d\n", dbg.sign);
  printf("exponent: %d (real=%d)\n", dbg.exponent-127, dbg.exponent);
  printf("fraction: %d\n", dbg.fraction);
}

/* out:
 * --[ raw ]--
 *    float: 13.37
 *    bytes: 41 55 eb 85
 *      bin: 01000001 01010101 11101011 10000101
 * 
 * --[ iee754 components] --
 *     sign: 0
 * exponent: 3 (real=130)
 * fraction: 5630853

[1] this turns only out to be true if you are living in the us state Indiana