Help with printing a bit pattern with printf and %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 ?
 
E

Eric Sosman

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;

Aside: This won't always do what you want. The compiler
has a lot of freedom in laying out bit-fields, and different
compilers will do it differently.
} 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 ?

The short (and uninformative) answer is: When the argument
and the conversion specification disagree, the behavior is
undefined.

Most likely, the "%x" specifier looks at half of the `double'
argument,[*] finds a whole lot of zero bits, interprets them as
the `unsigned int' it was expecting, and prints their value.

Even that much isn't guaranteed, though. Some machines pass
floating-point arguments in a different way than others (perhaps
in dedicated F-P registers), so the "%x" would look for its
argument in one place while the `1.0' inhabits another. GIGO.

[*] Yes, the type of `1.0' is `double', not `float'. Even
if you were to write `1.0f' it wouldn't help: the "default
argument promotions" would convert the `1.0f' to `double' while
marshalling the arguments for printf. There is no way to pass
a `float' -- or `short', or `char' -- argument to printf.
 
G

glen herrmannsfeldt

(snip)
The short (and uninformative) answer is: When the argument
and the conversion specification disagree, the behavior is
undefined.
Most likely, the "%x" specifier looks at half of the `double'
argument,[*] finds a whole lot of zero bits, interprets them as
the `unsigned int' it was expecting, and prints their value.

I believe that used to be mostly true, but isn't anymore.
Even that much isn't guaranteed, though. Some machines pass
floating-point arguments in a different way than others (perhaps
in dedicated F-P registers), so the "%x" would look for its
argument in one place while the `1.0' inhabits another. GIGO.

Well, more and more pass the first N in registers, depending
on how many registers are available. And, yes, sometimes different
for fixed and floating point registers.
[*] Yes, the type of `1.0' is `double', not `float'. Even
if you were to write `1.0f' it wouldn't help: the "default
argument promotions" would convert the `1.0f' to `double' while
marshalling the arguments for printf. There is no way to pass
a `float' -- or `short', or `char' -- argument to printf.

-- glen
 
K

Keith Thompson

glen herrmannsfeldt said:
(snip)
The short (and uninformative) answer is: When the argument
and the conversion specification disagree, the behavior is
undefined.
Most likely, the "%x" specifier looks at half of the `double'
argument,[*] finds a whole lot of zero bits, interprets them as
the `unsigned int' it was expecting, and prints their value.

I believe that used to be mostly true, but isn't anymore.

I think it depends on the platform. On an x86 Linux system, the
behavior appears to be consistent with arguments being pushed onto
the stack, with "%x" printing half the bits of a double argument.
On x86_64, something else is going on and "%x" prints garbage.
(I generated assembly listings, but I don't know either CPU well
enough to understand them in the little time I spent looking
at them.)

[...]
 
G

glen herrmannsfeldt

Keith Thompson said:
glen herrmannsfeldt said:
(snip)
The short (and uninformative) answer is: When the argument
and the conversion specification disagree, the behavior is
undefined.
Most likely, the "%x" specifier looks at half of the `double'
argument,[*] finds a whole lot of zero bits, interprets them as
the `unsigned int' it was expecting, and prints their value.
I believe that used to be mostly true, but isn't anymore.
I think it depends on the platform. On an x86 Linux system, the
behavior appears to be consistent with arguments being pushed onto
the stack, with "%x" printing half the bits of a double argument.
On x86_64, something else is going on and "%x" prints garbage.
(I generated assembly listings, but I don't know either CPU well
enough to understand them in the little time I spent looking
at them.)

I know IA32 (x86) well enough usually to figure it out, but not
AMD64 which is what this computer is running. As well as I understand
it, there are separate floating point and fixed point registers,
and arguments are passed in the appropriate registers.

-- glen
 
N

Noob

glen said:
I know IA32 (x86) well enough usually to figure it out, but not
AMD64 which is what this computer is running. As well as I understand
it, there are separate floating point and fixed point registers,
and arguments are passed in the appropriate registers.

Waaay off-topic, details can be found on Wikipedia.

https://en.wikipedia.org/wiki/X86_calling_conventions#System_V_AMD64_ABI
The calling convention of the System V AMD64 ABI[11] is followed on
Solaris, GNU/Linux, FreeBSD, and other non-Microsoft operating
systems. The first six integer or pointer arguments are passed in
registers RDI, RSI, RDX, RCX, R8, and R9, while XMM0, XMM1, XMM2,
XMM3, XMM4, XMM5, XMM6 and XMM7 are used for floating point
arguments. For system calls, R10 is used instead of RCX.[11] As in
the Microsoft x64 calling convention, additional arguments are passed
on the stack and the return value is stored in RAX.
 
K

Keith Thompson

glen herrmannsfeldt said:
Keith Thompson said:
glen herrmannsfeldt said:
(snip)
The short (and uninformative) answer is: When the argument
and the conversion specification disagree, the behavior is
undefined.
Most likely, the "%x" specifier looks at half of the `double'
argument,[*] finds a whole lot of zero bits, interprets them as
the `unsigned int' it was expecting, and prints their value.
I believe that used to be mostly true, but isn't anymore.
I think it depends on the platform. On an x86 Linux system, the
behavior appears to be consistent with arguments being pushed onto
the stack, with "%x" printing half the bits of a double argument.
On x86_64, something else is going on and "%x" prints garbage.
(I generated assembly listings, but I don't know either CPU well
enough to understand them in the little time I spent looking
at them.)

I know IA32 (x86) well enough usually to figure it out, but not
AMD64 which is what this computer is running. As well as I understand
it, there are separate floating point and fixed point registers,
and arguments are passed in the appropriate registers.

Which means the implementation of the va_arg() macro (assuming
printf uses that mechanism) has to do some extra work to grab the
next argument from the right place; it can't just assume it's at
some offset from the address of the last named argument.
 
S

Stephen Sprunk

glen herrmannsfeldt said:
Eric Sosman said:
The short (and uninformative) answer is: When the argument and
the conversion specification disagree, the behavior is
undefined.

Most likely, the "%x" specifier looks at half of the `double'
argument,[*] finds a whole lot of zero bits, interprets them as
the `unsigned int' it was expecting, and prints their value.

I believe that used to be mostly true, but isn't anymore.

I think it depends on the platform. On an x86 Linux system, the
behavior appears to be consistent with arguments being pushed onto
the stack, with "%x" printing half the bits of a double argument. On
x86_64, something else is going on and "%x" prints garbage. (I
generated assembly listings, but I don't know either CPU well enough
to understand them in the little time I spent looking at them.)

x86 normally passes all arguments on the stack regardless of type, so
bad code often "works". In contrast, x86-64 passes the first several
arguments in registers of the appropriate type, so the same bad code
"doesn't work" because the callee is looking in the wrong place.

Since passing arguments of the wrong type invokes undefined behavior,
both are perfectly valid results.

S
 
T

Tim Rentsch

Eric Sosman said:
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 [high] 1 */
unsigned int expo:8; /* biased exponent */
unsigned int sign:1;
} fields;

Aside: This won't always do what you want. The compiler
has a lot of freedom in laying out bit-fields, and different
compilers will do it differently.

Implementations do have some freedom in laying out bit-fields, but
not very much. Moreover, what those choices are, just as is the
case with how floats are represented, is implementation-defined, so
code like this can be written quite reliably, simply by consulting
the implementation's associated documentation.
 
K

Kenny McCormack

Stephen Sprunk said:
Since passing arguments of the wrong type invokes undefined behavior,
both are perfectly valid results.

Heh heh.

Only by the strange, weird, CLC definition of "valid".

--
Modern Christian: Someone who can take time out from
complaining about "welfare mothers popping out babies we
have to feed" to complain about welfare mothers getting
abortions that PREVENT more babies to be raised at public
expense.
 

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

Members online

Forum statistics

Threads
473,744
Messages
2,569,483
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top