Search code examples
ccharprintfunsignednegation

Why printf ("%d", ~a); displays -4 when a is equal to 3?


Running the following program display -4 while 252 is expected:

unsigned char a=3;
printf ("%d", ~a);

Why this code doesn't display 252?

I also tested the folowings according to the proposed answer:

printf ("%u", ~a);

displays: 4294967292

printf ("%hu", ~a);

displays: 65532

Why ~a doesn't return an unsigned char since a is an unsigned char?

My question is not what should I do to display 252 ? My question is Why 252 is not displayed?


Solution

  • In addition to the answer of @Someprogrammerdude, here are the relevant passages from The Book1):

    Unary arithmetic operators (§6.5.3.3/4)

    The result of the ~ operator is the bitwise complement of its (promoted [ !! ] ) operand (that is, each bit in the result is set if and only if the corresponding bit in the converted operand is not set). The integer promotions are performed on the operand, and the result has the promoted type. If the promoted type is an unsigned type, the expression ~E is equivalent to the maximum value representable in that type minus E.

    Arithmetic operands - Boolean, characters, and integers (§6.3.1.1):

    1. Every integer type has an integer conversion rank defined as follows:

      • No two signed integer types shall have the same rank, even if they have the same representation.
      • The rank of a signed integer type shall be greater than the rank of any signed integer type with less precision.
      • The rank of long long int shall be greater than the rank of long int, which shall be greater than the rank of int, which shall be greater than the rank of short int, which shall be greater than the rank of signed char.
      • The rank of any unsigned integer type shall equal the rank of the corresponding signed integer type, if any.
      • The rank of any standard integer type shall be greater than the rank of any extended integer type with the same width.
      • The rank of char shall equal the rank of signed char and unsigned char.
      • The rank of _Bool shall be less than the rank of all other standard integer types.
      • The rank of any enumerated type shall equal the rank of the compatible integer type (see 6.7.2.2).
      • The rank of any extended signed integer type relative to another extended signed integer type with the same precision is implementation-defined, but still subject to the other rules for determining the integer conversion rank.
      • For all integer types T1, T2, and T3, if T1 has greater rank than T2 and T2 has greater rank than T3, then T1 has greater rank than T3.
    2. The following may be used in an expression wherever an int or unsigned int may be used:

      • An object or expression with an integer type whose integer conversion rank is less than or equal to the rank of int and unsigned int.
      • A bit-field of type _Bool, int, signed int, or unsigned int. If an int can represent all values of the original type, the value is converted to an int; otherwise, it is converted to an unsigned int. These are called the integer promotions.48) All other types are unchanged by the integer promotions.
    3. The integer promotions preserve value including sign. As discussed earlier, whether a "plain" char is treated as signed is implementation-defined.

    48) The integer promotions are applied only: as part of the usual arithmetic conversions, to certain argument expressions, to the operands of the unary +, -, and ~ operators, and to both operands of the shift operators, as specified by their respective subclauses.


    Your Question:

    Why ~a doesn't return an unsigned char since a is an unsigned char?

    Because integer promotions apply.

    unsigned char a = 3;
    printf ("%d", ~a);
    

    a is an unsigned char, a type with a range that can be represented by an int. So a gets promoted to an int. Assuming 32-bit wide ints and two's complement:

      310 = 0000 0000 0000 0000 0000 0000 0000 00112
    ~310 = 1111 1111 1111 1111 1111 1111 1111 11002

    The result interpreted as signed int is negative because the most significant bit, the sign bit, is set.

    Convert to decimal:

        1111 1111 1111 1111 1111 1111 1111 11002
     ¬ 0000 0000 0000 0000 0000 0000 0000 00112
     + 0000 0000 0000 0000 0000 0000 0000 00012
       ────────────────────────────
        0000 0000 0000 0000 0000 0000 0000 01002

    01002 = 0 × 23 + 1 × 22 + 0 × 22 + 0 × 22
               1 × 22
               =   410
               = −410 (with the original sign)

    ~> printf() prints -4.

    To get the desired result of 252 with your original code which uses "%d" as format specifier, some casting would be needed:

    unsigned char a = 3;
    printf("%d\n", (int)((unsigned char) ~a));  // prints 252
    //              ^^^   ^^^^^^^^^^^^^
    //               |          cast the result of ~a back to unsigned char *)
    //               |          to discard the bits > CHAR_BIT
    //               cast the char back to int to agree with the format specifier 
    

    *)Thanks to chux who made me remember that char could be signed! A cast to (possibly signed) char would give the wrong result of -4.

    To get the same result without a cast, you could use the length modifier hh:

    The fprintf function (§7.19.6.1/7)

    The length modifiers and their meanings are:

    hh  Specifies that a following d, i, o, u, x, or X conversion specifier applies to a signed char or unsigned char argument (the argument will have been promoted according to the integer promotions, but its value shall be converted to signed char or unsigned char before printing); or that a following n conversion specifier applies to a pointer to a signed char argument.

    [...]

    unsigned char a = 3;
    printf("%hhu\n", ~a);  // prints 252
    


    The problem with your other attempts:

    printf ("%u", ~a);
    

    displays: 4294967292

    printf ("%hu", ~a);
    

    displays: 65532

    Since ~a is an integer, it is not the correct type for the format specifier u and

    The fprintf function (§7.19.6.1/9):

    If a conversion specification is invalid, the behavior is undefined.248) If any argument is not the correct type for the corresponding conversion specification, the behavior is undefined.



    1) ISO/IEC 9899/Cor3:2007 aka C99:TR3 aka C99