Question on printf with %x

M

matt.jaffe

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;

} 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 theresult:

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 ?
 
S

Siri Cruise

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;

} 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 ?

%f expects a (double), and the arguments of printf have default promotions so
1.0 is passed as (double). So the format expects (double),(unsigned int), but
the argument list is (double),(double). If sizeof(double)>sizeof(unsigned int),
you're mismatching values as well types.
 
I

Ike Naar

printf("\n 1.0 printed with with %%f is %f and with %%x is 0x%x \n", 1.0, 1.0);

The %x conversion specification expects a corresponding argument
of type unsigned int but an argument of type double (1.0) is passed.
By 7.19.6.1 (formatted input/output functions: the fprintf function)

9 [...] If any argument is not the correct type for the
corresponding conversion specification, the behavior is undefined.
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 ?

First, note that 1.0 is not a float but a double. Its hexadecimal
representation will probably not be 0x3f800000.

Not that it really matters; since the passed argument does not
match the conversion specification, the behaviour is undefined, so
printf is not obliged to print "0x3f800000"; it might print "Boo",
or it might even terminate the program.
 
F

Francois Grieu

printf("\n 1.0 printed with with %%f is %f and with %%x is 0x%x \n", 1.0, 1.0);

Try

float vx = 1.0;
printf("\n 1.0 printed with with %%f is %f and with %%x is 0x%x \n", vx, *(unsigned*)&vx );

It might do what you expect on your architecture. Or not.
 
J

Johann Klammer

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;
I'm not even sure if compiler might not align those fields, messing up
your assumptions about which bit is where...
} 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 ?

Something likely invoking undefined behavior, but what will (probably)
do what you want(on LE boxes with 32 bit integer):

float f=1.0;

printf("0x%x\n",*((int*)&f));
 
J

James Kuyper

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.
 
J

James Kuyper

On 04/17/2013 10:25 AM, James Kuyper wrote:
....
memcpy(&f, array, sizeof f);

I'm not doing too well this morning. That should have been
memcpy(array, &f, sizeof f);
 
K

Keith Thompson

Francois Grieu said:
Try

float vx = 1.0;
printf("\n 1.0 printed with with %%f is %f and with %%x is 0x%x \n", vx, *(unsigned*)&vx );

It might do what you expect on your architecture. Or not.

That assumes, among other things, that float and unsigned have the same
size and alignment requirements. If they have different sizes, this
will try to print either a subset of vx, or a chunk of memory wider than
vx. If the alignment requirements differ, dereferencing the converted
pointer can crash your program or have other arbitrarily bad results.

But even if the sizes and alignments match, the behavior is not well
defined (I'm too lazy to figure out whether it's strictly undefined).

You can try this:

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
int main (void)
{
float vx = 1.0;
unsigned int ui;
printf("vx = %f\n", vx);
if (sizeof vx == sizeof ui) {
memcpy(&ui, &vx, sizeof ui);
printf("ui = 0x%x\n", ui);
exit(EXIT_SUCCESS);
}
else {
fprintf(stderr, "Size mismatch\n");
exit(EXIT_FAILURE);
}
}

The output on my system is:

vx = 1.000000
ui = 0x3f800000
 
M

matt.jaffe

On Wednesday, April 17, 2013 8:44:08 AM UTC-7, Keith Thompson wrote:

[snip]

OK, thanks all; I think you've given me what I need. When I have time later today, I'll play with the various suggestions until I'm (reasonably ;-) sure I understand everything in sufficient depth to satisfy my curiosity.

Thanks again to all who responded,

....Matt
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Similar Threads


Members online

Forum statistics

Threads
473,744
Messages
2,569,482
Members
44,900
Latest member
Nell636132

Latest Threads

Top