Help with printing a bit pattern with printf and %x

Discussion in 'C Programming' started by matt.jaffe@gmail.com, Apr 17, 2013.

  1. Guest

    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 ?
    , Apr 17, 2013
    #1
    1. Advertising

  2. Eric Sosman Guest

    On 4/17/2013 1:19 AM, wrote:
    > 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.

    --
    Eric Sosman
    d
    Eric Sosman, Apr 17, 2013
    #2
    1. Advertising

  3. Eric Sosman <> wrote:

    (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
    glen herrmannsfeldt, Apr 17, 2013
    #3
  4. glen herrmannsfeldt <> writes:
    > Eric Sosman <> wrote:
    >
    > (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.)

    [...]

    --
    Keith Thompson (The_Other_Keith) <http://www.ghoti.net/~kst>
    Working, but not speaking, for JetHead Development, Inc.
    "We must do something. This is something. Therefore, we must do this."
    -- Antony Jay and Jonathan Lynn, "Yes Minister"
    Keith Thompson, Apr 17, 2013
    #4
  5. Keith Thompson <> wrote:
    > glen herrmannsfeldt <> writes:
    >> Eric Sosman <> wrote:


    >> (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
    glen herrmannsfeldt, Apr 18, 2013
    #5
  6. Noob Guest

    [OT] x86 and amd64 calling conventions

    glen herrmannsfeldt wrote:

    > 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.
    Noob, Apr 18, 2013
    #6
  7. glen herrmannsfeldt <> writes:
    > Keith Thompson <> wrote:
    >> glen herrmannsfeldt <> writes:
    >>> Eric Sosman <> wrote:

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

    --
    Keith Thompson (The_Other_Keith) <http://www.ghoti.net/~kst>
    Working, but not speaking, for JetHead Development, Inc.
    "We must do something. This is something. Therefore, we must do this."
    -- Antony Jay and Jonathan Lynn, "Yes Minister"
    Keith Thompson, Apr 18, 2013
    #7
  8. On 17-Apr-13 16:25, Keith Thompson wrote:
    > glen herrmannsfeldt <> writes:
    >> Eric Sosman <> wrote:
    >>> 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

    --
    Stephen Sprunk "God does not play dice." --Albert Einstein
    CCIE #3723 "God is an inveterate gambler, and He throws the
    K5SSS dice at every possible opportunity." --Stephen Hawking
    Stephen Sprunk, Apr 18, 2013
    #8
  9. Tim Rentsch Guest

    Eric Sosman <> writes:

    > On 4/17/2013 1:19 AM, wrote:
    >> 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.
    Tim Rentsch, Apr 19, 2013
    #9
  10. In article <kkptii$30s$>,
    Stephen Sprunk <> wrote:
    ....
    >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.
    Kenny McCormack, Apr 19, 2013
    #10
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Replies:
    3
    Views:
    1,724
    Timothy Bendfelt
    Jan 19, 2007
  2. ben
    Replies:
    4
    Views:
    603
    Martin Ambuhl
    Jun 26, 2004
  3. whatluo

    (void) printf vs printf

    whatluo, May 26, 2005, in forum: C Programming
    Replies:
    29
    Views:
    1,229
  4. Replies:
    9
    Views:
    954
    Juha Nieminen
    Aug 22, 2007
  5. Jeff.M
    Replies:
    6
    Views:
    163
    Lasse Reichstein Nielsen
    May 4, 2009
Loading...

Share This Page