5

I wanted to see the float value 3.14159265 in IEEE754 format representation. So wrote this test code.

#include <stdio.h>

main()
{
int i;
float a = 3.141592654;

printf("a = %f\n", a);

// print test 1
printf("test 1 : a = %x\n", a);
printf("test 1 2nd time : a = %x\n", a);

// print test 2
char *pt = (char *)&a;
printf("test 2 :\n");
for(i=0;i<4;i++){
    printf("%x ", (char)(*pt++));
}
printf("\n");
}

When I run it, several times, it shows like this :

ckim@stph45:~/test5] test
a = 3.141593
test 1 : a = e0cd2000
test 1 2nd time : a = e0cd2000
test 2 :
ffffffdb f 49 40 
ckim@stph45:~/test5] test
a = 3.141593
test 1 : a = 520db000
test 1 2nd time : a = 520db000
test 2 :
ffffffdb f 49 40 
ckim@stph45:~/test5] test
a = 3.141593
test 1 : a = 3b373000
test 1 2nd time : a = 3b373000
test 2 :
ffffffdb f 49 40 
ckim@stph45:~/test5] test
a = 3.141593
test 1 : a = 18a6d000
test 1 2nd time : a = 18a6d000
test 2 :
ffffffdb f 49 40 

First question is : when using printf with %x, I expected to see 40490fdb which is 3.14159265 in IEEE754 format. But the value changes everytime I run it as you can see. What's wrong with this here? Maybe I'm missing something very basic.

Anoother question is, when I use character pointer and print the hex values as in test 2 above, how can I print 'db' instead of 'fffffdb'? (the value is shown sign extened). I think I've done these things before but can't remember it now. (My machine is little endian so LSB is shown first.)

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
Chan Kim
  • 5,177
  • 12
  • 57
  • 112

4 Answers4

8

What you are missing is how to obtain a valid look at the bits that make up the IEEE-754 single-precision floating point number as its equivalent unsigned int representation in memory. To do that while avoiding violating the strict aliasing rule (type punning pointers), you can use a union between float and unsigned (or better uint32_t for exact width type), e.g.

#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <math.h>

int main (void) {

    float pi = (float)M_PI;
    union {
        float f;
        uint32_t u;
    } f2u = { .f = pi };

    printf ("pi : %f\n   : 0x%" PRIx32 "\n", pi, f2u.u);

    return 0;
}

Using unsigned instead of uint32_t and understanding it may not be 32-bits on all hardware, the standard format string will suffice, e.g. printf ("pi : %f\n : 0x%0x\n", pi, f2u.u);. (you can drop the include of stdint.h and inttypes.h in that case)

Example Use/Output

This will allow you to get the hex representation of the bits that make up the floating-point number when interpreted as an unsigned type:

$ ./bin/float_hex
pi : 3.141593
   : 0x40490fdb
David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • Fine, adding a "char pt[4];" within the union also solves the char issue. –  Jul 21 '17 at 05:01
  • Very true - you get to make up the 32-bits any way you like as long as you stay within the rules. Only issue with `char [4]` is you are then left to cast to `unsigned` (or `uint32_t`) at some later point (keyboard rant deleted) `:)` – David C. Rankin Jul 21 '17 at 05:03
  • Thanks, using union is one method. I put my answer using `*(unsigned int*)&a` and that is the type of simple method I wanted. – Chan Kim Jul 21 '17 at 05:53
  • 1
    @Chan Kim: Your "simple method" is broken. – AnT stands with Russia Jul 21 '17 at 05:54
  • @ChanKim you are *type punning* a pointer which is not allowed in C (aside from `char`). Didn't you notice the **warnings** the compiler generated? You are violating Sections 6.5 (6) & (7) of the C11 Standard. – David C. Rankin Jul 21 '17 at 05:56
  • Ok, though my compiler doesn't complain about usnig pointer type casting.. I learned this things are deemed bad pracice and we should use reinterpret cast. and learned about using union in this case. Thanks. – Chan Kim Jul 21 '17 at 07:28
  • @ChanKim: There's no reinterpret_cast in C. C++ is a different language with different rules. – user2357112 Jul 21 '17 at 16:42
  • 1
    @chux `float a = 3.141592654; *(unsigned int*)&a` in the comment (6th above) – David C. Rankin Jul 21 '17 at 23:18
3

To be clear, OP's code did not fail due to "strict aliasing rule".

You can use char* for aliasing instead of your system's word. The rules allow an exception for char*

Code failed as "%x" expects an unsigned and code passed a float. This is undefined behavior (UB). As FP values are sometimes passed in a different manner than integers, this is not surprising.

float a = 3.141592654;   
printf("test 1 : a = %x\n", a); // bad code

OP's later code was almost right. Just needs to use the size of the object and hh in the specifier to only print the the value without its signed extension.

The order to print depends on endian-ness of the float. Using a union still is not endian correct as endian of of a float and an int may differ - though that is uncommon.

char *pt = (char *)&a;
//for(i=0;i<4;i++){
for(i=0;i<sizeof a;i++){
  //printf("%x ", (char)(*pt++));
  printf("%02hhx ", *pt++);
}

Additional issues exist for rare platforms that have more than 8 bits to a char.


The easy alternative is to use "%a" @user2357112 which will print the mantissa/significand in hexadecimal and the exponent in decimal powers-of-2.


To covert any object to a "hex" string, this code serves as an example to convert any object into a "binary" string.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
2

Use conversion specifier %a for hexfloat output.

thakis
  • 5,405
  • 1
  • 33
  • 33
0

I think the answer I was looking for is like this.

float a = 3.141592654;
printf("test 1 2nd time : a = %x\n", *(unsigned int*)&a);

Then I get

test 1 2nd time : a = 40490fdb
Chan Kim
  • 5,177
  • 12
  • 57
  • 112
  • 6
    No. `*(unsigned int*)&a` - that's a strict aliasing violation that is not guaranteed to work. The behavior is undefined. If you want to reinterpret memory - use a union as @David C. Rankin suggested. – AnT stands with Russia Jul 21 '17 at 05:52