Thread: Getting unexpected outputs for bitfields and floats

  1. #1
    Registered User
    Join Date
    Nov 2008
    Posts
    127

    Getting unexpected outputs for bitfields and floats

    I'm getting unexpected output in 2 different cases. The 1st deals with bitfields. The C++ standard has this line about integral promotions:

    An rvalue for an integral bit-field (9.6) can be converted to an rvalue of type int if int can represent all the values of the
    bit-field; otherwise, it can be converted to unsigned int if unsigned int can represent all the values of the bit-field.
    If the bit-field is larger yet, no integral promotion applies to it. If the bit-field has an enumerated type, it is treated as any
    other value of that type for promotion purposes.
    This sounds like the value of a bitfield will always be treated as a signed int if the signed representation of the value will fit in the bits. This seems to hold true for my C compiler, but not my C++ compiler.

    I tried storing a small negative value in a bitfield that has enough bits to store the sign bit and the value. But when I print out the bitfield, I always get a large number

    In the example code below, I expect the output:

    Code:
    foo.x = -1
    foo.y = -2
    foo2.x = 31
    foo2.y = 6
    foo3.x = -1
    foo4.x = 4294967295
    But I get:
    Code:
    foo.x = 31
    foo.y = 6
    foo2.x = 31
    foo2.y = 6
    foo3.x = -1
    foo4.x = -1
    -------------------

    The other issue I'm having is sort of similar. I'm trying to store 4294967295 into a float, but when I print it out, I get 4294967296. i've tried storing a few other large values like this and what's printed out is rarely the value I stored. I thought it might be because of some int to float conversion, so I tried 4294967295.0. Still no luck. Then I remember that defaults to a double so maybe that's the issue so I tried 4294967295.0f. Still no luck. Why can't I store the correct value here? I don't think it's an IEE format thing since I can use these values as floats on a calculator program.

    The example code showing both issues is below.
    Code:
    #include <stdio.h>
    
    typedef struct
    {
       signed char x : 5;
       signed char y : 3;
    }my_struct_t;
    
    typedef struct
    {
       unsigned char x : 5;
       unsigned char y : 3;
    }my_struct2_t;
    
    typedef struct
    {
       signed int x : 32;
    }my_struct3_t;
    
    typedef struct
    {
       unsigned int x : 32;
    }my_struct4_t;
    
    int main()
    {
      my_struct_t foo;
      
      foo.x = -1;
      foo.y = -2;
      
      printf("foo.x = %d\n", foo.x);
      printf("foo.y = %d\n", foo.y);
      
      my_struct2_t foo2;
      
      foo2.x = -1;
      foo2.y = -2;
      
      printf("foo2.x = %d\n", foo2.x);
      printf("foo2.y = %d\n", foo2.y);
      
      my_struct3_t foo3;
      
      foo3.x = -1;
      
      printf("foo3.x = %d\n", foo3.x);
      
      my_struct4_t foo4;
      
      foo4.x = -1;
      
      printf("foo4.x = %d\n", foo4.x);
      
      float f = 4294967295.0f;
      
      printf("f = %f\n", f);
      
      return 0;
    }

  2. #2
    Lurking whiteflags's Avatar
    Join Date
    Apr 2006
    Location
    United States
    Posts
    9,612
    for my_struct_t, you stored -1 in x and -2 in y. One binary representation of -1 is 11111111 11111111 11111111 11111111 -- This is two's complement. So no matter what you end up with five bits on - 11111 which is 31 when converted back to int for printing. -2 is 11111111 11111111 11111111 11111110 in the same system. Taken from the low end, you end up with 110 which is 6 when converted back to int for printing.

    So really, in general, the correct values are being stored, but you can't see it.

    I'd recommend printing and working in a different base:
    Code:
    #include <iostream>
    #include <iomanip>
    #include <limits>
    
    struct my_struct
    {
      int x0 : 5;
      int x1 : 3;
      int x3;
    };
    
    int main()
    {
      std::cout << std::hex;
      my_struct s = { -1, -2, std::numeric_limits<int>::max() };
      std::cout << s.x0 << '\t' << s.x1 << '\t' << s.x3 << std::endl;
    }
    C++ code - 17 lines - codepad

    Hexadecimal allows you to directly see which bits are turned on. Every digit in hex corresponds to a nibble (4 bits), so if you memorize the representations of the first 15 binary numbers, you can actually see your bits.

    Also, bit-fields are basically entirely compiler dependent, by word of the standard. Using a bitset, or an unsigned integer, even, offers more portability.

    As for the floating point number, perhaps that number is not a modal number. There are very few numbers that can be represented exactly in floating point. Floating point is for precision not accuracy. 0.2 in a floating point number is actually represented by .1999999. If you want accuracy, use integers.
    Last edited by whiteflags; 08-07-2013 at 03:04 PM. Reason: bits, not bytes

  3. #3
    Registered User
    Join Date
    Oct 2006
    Posts
    3,445
    a 32-bit IEEE float lacks the precision required to store 4294967295 (232-1) precisely, so it approximates it with the nearest value that it can represent, which in this case, is 4294967296 (232).
    What can this strange device be?
    When I touch it, it gives forth a sound
    It's got wires that vibrate and give music
    What can this thing be that I found?

  4. #4
    Algorithm Dissector iMalc's Avatar
    Join Date
    Dec 2005
    Location
    New Zealand
    Posts
    6,318
    If you print using %d then you're telling it to interpret that as a signed integer. The number is still negative though, which you can confirm by going:
    Code:
    if (foo.x < 0)
        printf("Told ya!");
    Floats can only store about 6 decimal places, the rest beyond that will come out to whatever is the most easily storable value in binary form, which happens to be the number you are seeing. Use a double if you want up to around 15 decimal places.
    My homepage
    Advice: Take only as directed - If symptoms persist, please see your debugger

    Linus Torvalds: "But it clearly is the only right way. The fact that everybody else does it some other way only means that they are wrong"

  5. #5
    Registered User
    Join Date
    Nov 2008
    Posts
    127
    Quote Originally Posted by whiteflags View Post
    for my_struct_t, you stored -1 in x and -2 in y. One binary representation of -1 is 11111111 11111111 11111111 11111111 -- This is two's complement. So no matter what you end up with five bits on - 11111 which is 31 when converted back to int for printing. -2 is 11111111 11111111 11111111 11111110 in the same system. Taken from the low end, you end up with 110 which is 6 when converted back to int for printing.

    So really, in general, the correct values are being stored, but you can't see it.
    11111 is -1 when converted to int using 2's complement. Likewise 11110 is -2 when converted to int using 2's complement. This is why I'm confused about the output.

    If you print using %d then you're telling it to interpret that as a signed integer. The number is still negative though, which you can confirm by going:
    Yes, exactly. Printing as a signed int should print the sign, shouldn't it?
    Last edited by homer_3; 08-07-2013 at 03:15 PM.

  6. #6
    Lurking whiteflags's Avatar
    Join Date
    Apr 2006
    Location
    United States
    Posts
    9,612
    In no part of the universe is 11111 -1 or 110 -2. There are not enough bits.

  7. #7
    Registered User
    Join Date
    Oct 2006
    Posts
    3,445
    Quote Originally Posted by whiteflags View Post
    In no part of the universe is 11111 -1 or 110 -2. There are not enough bits.
    if the word is only 5 bits, then it 11111 is definitely -1, but I don't know of any machine that has a 5 bit word.
    What can this strange device be?
    When I touch it, it gives forth a sound
    It's got wires that vibrate and give music
    What can this thing be that I found?

  8. #8
    Registered User
    Join Date
    Nov 2008
    Posts
    127
    Quote Originally Posted by Elkvis View Post
    if the word is only 5 bits, then it 11111 is definitely -1, but I don't know of any machine that has a 5 bit word.
    Of course there are enough bits. What makes you say there aren't enough bits? 2's compliment works fine with 2 bits or more. 11111 - 1 = 11110. ~11110 = 00001.

  9. #9
    Lurking whiteflags's Avatar
    Join Date
    Apr 2006
    Location
    United States
    Posts
    9,612
    Quote Originally Posted by homer_3 View Post
    Of course there are enough bits. What makes you say there aren't enough bits? 2's compliment works fine with 2 bits or more. 11111 - 1 = 11110. ~11110 = 00001.
    Yes, logically speaking that is correct but you have a mental block about what is actually happening.

    When you print the number, it is stored in an int, which is a region of memory with many more bits than five on 99% of the machines in the world. When five bits is interpreted in the space of an int on the average platform, two's complement doesn't matter. 5 bits on is 31. I will not argue this point further.

  10. #10
    Registered User
    Join Date
    Nov 2008
    Posts
    127
    Quote Originally Posted by whiteflags View Post
    Yes, logically speaking that is correct but you have a mental block about what is actually happening.

    When you print the number, it is stored in an int, which is a region of memory with many more bits than five on 99% of the machines in the world. When five bits is interpreted in the space of an int on the average platform, two's complement doesn't matter. 5 bits on is 31. I will not argue this point further.
    So is this implementation defined or maybe just a difference between C and C++? My C compiler prints out -1 and -2 where the C++ results in the 31/6. I also thought that the fact that the value is a bitfield means that none of the other bits are supposed to be considered when evaluating the number. If what you're saying is correct, shouldn't the output be the result of ((-1 << 5) | -2)? But we know that is wrong, because then bitfields wouldn't be useful at all.

  11. #11
    Lurking whiteflags's Avatar
    Join Date
    Apr 2006
    Location
    United States
    Posts
    9,612
    Right now, the only difference is that one language's compiler is using sign extension and the other isn't. My guess is that if the compiler decided to fill the bits starting on the high end it would use sign extension, and starting on the low end it wouldn't. In C at least, I know that this behavior is unspecified. (I posted the exact quotation like a couple days ago)

    If fact, you are doing so many things to make this hard it's unbelievable. Because of the relationship I pointed out earlier, many people use hexadecimal, not decimal. People also use unsigned numbers because in certain situations the shift operators are also implementation defined.

  12. #12
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    When you access a bit field, the compiler doesn't invent some weird type with exactly that number of bits, the expression is a normal integer type. Therefore the "missing" bits have to be filled with something. If the value is considered to be unsigned, then filling with zero is correct. If the value is signed, then the missing bits should be equal to the "sign bit", this is called sign extension. The compiler may not do what you wish it did.

    I think this is an undefined area of the standard.

    EDIT: If you know that a signed right shift is an arithmetic shift (this is implementation defined), you can perform the conversion yourself by shifting left by (8*sizeof(int)-BITFIELD_SIZE) and then right again by the same quantity. The left shift places the "sign bit" into the actual sign bit of the native integer, and the following arithmetic right-shift replicates the sign bit as desired.
    Code:
    //try
    //{
    	if (a) do { f( b); } while(1);
    	else   do { f(!b); } while(1);
    //}

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Bitfields
    By KansaiRobot in forum C Programming
    Replies: 8
    Last Post: 09-20-2012, 10:58 PM
  2. BitFields -- is this possible?
    By trish in forum C Programming
    Replies: 6
    Last Post: 01-22-2012, 02:22 AM
  3. bitfields
    By Tiago in forum C Programming
    Replies: 2
    Last Post: 05-16-2010, 09:21 AM
  4. Bitfields, Bit/Little Endian
    By azerej in forum C Programming
    Replies: 0
    Last Post: 05-26-2008, 02:01 AM
  5. bitfields
    By Hunter2 in forum C++ Programming
    Replies: 2
    Last Post: 11-27-2002, 05:21 PM