I'm trying to show how floating point numbers are represented internally. I thought the easiest way would be to print the same floating point number once with %f and then again with %x, but the results surprised me. I can do it with a union and bit fields, but why doesn't the simpler way work? Here's the code:
#include<stdio.h>
int main(void)
{
union
{
float aFloat;
int anInt;
struct
{
unsigned int sig:23; /* significand without the most significant 1 */
unsigned int expo:8; /* biased exponent */
unsigned int sign:1;
} fields;
Eric has already addressed the problem with this approach.
} uEx; /* abbreviation of unionExample */
uEx.aFloat = 1.0;
printf("\n The union as a float: %f; as in integer in hex: %x; \n the sign bit is %x; the biased exponent is %x; the signifcand is %x \n", \
uEx.aFloat, uEx.anInt, uEx.fields.sign, uEx.fields.expo, uEx.fields.sig);
printf("\n 1.0 printed with with %%f is %f and with %%x is 0x%x \n", 1.0, 1.0);
}
The size of both integers and floats on the machine is 32 bits. Here's the result:
The union as a float: 1.000000; as in integer in hex: 3f800000;
the sign bit is 0; the biased exponent is 7f; the signifcand is 0
1.0 printed with with %f is 1.000000 and with %x is 0x0
The first two lines of output are what I was expecting per IEEE 754; but the 0x0 in the last line has me confused. Why is it not printing as 0x3f800000 ?
There's two levels to your question. The basic answer is the one you've
already been given by several people in various forms: the "%x" format
specifier requires that the corresponding argument actually be an
expression of unsigned int type. The constant 1.0 has type double - it's
not float, and it's definitely not unsigned int. When there's a type
mismatch like this, the standard says that the behavior of your program
is undefined.
The deeper question is "Why does the standard say that?", and I'm not
sure that anyone has addressed that. The thing is, when compiling a call
to a function with variable arguments like printf(), the compiler
needn't know anything about what types the function expects. The format
string that gets passed to printf() is parsed at run time by the
printf() function, the compiler need only pass it on without parsing.
All that the compiler needs to do is apply the default argument
promotions: "the integer promotions are performed on each argument, and
arguments that have type float are promoted to double." (6.5.2.2p6).
All other types are passed through normally. The integer promotions are
described in 6.3.1.1p2:" "If an int can represent all values of the
original type (as restricted by the width, for a bit-field), the value
is converted to an int; otherwise, it is converted to an unsigned int."
Incidentally, the default argument promotions are why it's simply not
possible to pass a float argument to printf(). printf("%f\n", 1.0F)
results in 1.0F being converted from float to double before being passed
to printf().
Note: while in general, compilers can't know what a variable argument
function expects, many of them do know that the printf() and scanf()
family of functions interpret their arguments in a well-defined manner
based upon the format string. The standard does not require compilers to
parse that format string, but many real world compilers often do, at
least when it's a string literal, and they'll produce appropriate
warning message. If your compiler didn't warn you about your code, you
should increase the warning level. If there's no way to get it to warn
you about such code, get a better compiler.
printf(), on the other hand, knows nothing about the types being passed
other than the information it gets from the format string. If it sees a
%x specifier, it assumes that the compiler has done whatever it normally
does with an argument of unsigned int type, which could be quite
different from what it does with an argument of double type - they're
likely to be different sizes, they might even be passed in different
registers. If what the compiler did with the argument is different from
what printf() expected it to do, then the call to printf() can fail, in
principle with arbitrarily spectacular results. That's why the standard
says that such programs have undefined behavior.
Now, there is a way to do approximately what you want, but it only works
on some platforms. It still has undefined behavior, but for different
reasons that are less likely to cause problems. This only works if you
know that there's some unsigned integer type which has exactly the same
size as a float; for the sake of argument, let's assume that the type is
uint32_t, since that's the single type that's most likely to have that
characteristic.
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#ifndef UINT32_MAX
#error uint32_t not supported
#endif
int main(void)
{
if(sizeof(uint32_t) != sizeof(float))
{
fprintf(stderr, "Sizes don't match: "
"sizeof(uint32_t) == %zu, sizeof(float)==%zu\n",
sizeof(uint32_t), sizeof(float));
return EXIT_FAILURE;
}
float f = 1.0;
printf("%" PRIx32 "\n", *(uint32_t*)&f);
return EXIT_SUCCESS;
}
PRIx32 expands to a string containing the format specifier that is
appropriate for printing uint32_t values; it automatically gets merged
with the the "%" and "\n" to form a single string literal. That
specifier might be "d" if uint32_t is a typedef for "unsigned", or "ld"
if it's a typedef for "unsigned long". In the unlikely case that
UINT32_MAX < INT_MAX, it will be "d", because expressions of that type
will be promoted to int when passed to to printf(). In the unlikely
event that uint32_t is an extended integer type for which no standard
format specifier is appropriate, PRIx32 will be an
implementation-specific format specifier for that type. Regardless of
what uint32_t is, PRIx32 will be the appropriate specifier, which means
you don't have to deal with those issues. It's a clumsy solution, but it
does work.
The expression *(uint32_t*)&f performs what's called type punning, and
it's not guaranteed to work; it violates C's anti-aliasing rules.
However, if no error messages are generated by the compilation or
execution of the above code, it stands a good chance of doing what you
want. Sort of.
The complications? The floating point value you're looking at might be a
trap representation for the integer type that you're using to alias it.
Also, either type might have padding bits, and they might not be in the
same location for the two types. These problems are rather unlikely, and
they can't occur for uint32_t; the exact-sized types are prohibited from
having padding bits or trap representations. If, however, you find that
you have to use some other type for this purpose, that could be an
issue. If so, this code is in danger of seriously malfunctioning.
The other complication is byte ordering: the C standard says that an
object with a size of n bytes can be copied into an array of n unsigned
char elements, and that it's the values of those unsigned char elements
that constitute the object representation. The code I've written above
takes those same n bytes, and interprets them as an unsigned integer of
the specified type. That's not the same thing: the unsigned integer
could be big-endian or little-endian; less likely, but still possible,
are other byte orders that are generically called middle-endian, such at
2143 or 3412.
The right way to get the object representation is the way that matches
the standard's definition: declare
unsigned char array[sizeof f];
memcpy(&f, array, sizeof f);
and then print out the values of array. You can also achieve much the
same effect by putting f and array into the same union.