Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does the complement behave differently through printf?

I was reading a chapter on bitwise operators, I came across 1's complement operator program and decided to run it on Visual C++.

int main ()
{
   unsigned char c = 4, d;
   d = ~c;
   printf("%d\n", d);
}

It gives the valid output: 251

Then instead of using d as a variable to hold the value of ~c, I decided to directly print the value of ~c.

int main ()
{
   unsigned char c=4;
   printf("%d\n", ~c);
}

It gives the output -5.

Why didn't it work?

like image 439
Sanketssj5 Avatar asked Feb 17 '15 10:02

Sanketssj5


People also ask

What is the effect of the ones complement operator ~?

The one's complement operator ( ~ ), sometimes called the bitwise complement operator, yields a bitwise one's complement of its operand. That is, every bit that is 1 in the operand is 0 in the result. Conversely, every bit that is 0 in the operand is 1 in the result.

How does complement work in C?

The bitwise complement operator is a unary operator (works on only one operand). It takes one number and inverts all bits of it. When bitwise operator is applied on bits then, all the 1's become 0's and vice versa. The operator for the bitwise complement is ~ (Tilde).

How does complement operator work in Python?

Bitwise one's complement operator (~) Bitwise one's compliment operator will invert the binary bits. If a bit is 1, it will change it to 0. If the bit is 0, it will change it to 1.

How do you find the Bitwise complement of a number?

The way you get this is by taking the binary representation of a number, taking its complement (inverting all the bits) and adding one. Two starts as 0000 0010, and by inverting the bits we get 1111 1101. Adding one gets us the result above. The first bit is the sign bit, implying a negative.


3 Answers

In this statement:

printf("%d",~c);

the c is converted to int1 type before ~ (bitwise complement) operator is applied. This is because of integer promotions, that are invoked to operand of the ~. In this case an object of unsigned char type is promoted to (signed) int, which is then (after ~ operator evaluation) used by printf function, with matching %d format specifier.

Notice that default argument promotions (as printf is a variadic function) does not play any role here, as object is already of type int.

On the other hand, in this code:

unsigned char c = 4, d;
d = ~c;
printf("%d", d);

the following steps occur:

  • c is a subject to integer promotions because of ~ (in the same way, as described above)
  • ~c rvalue is evaluated as (signed) int value (e.g. -5)
  • d=~c makes an implicit conversion from int to unsigned char, as d has such type. You may think of it as the same as d = (unsigned char) ~c. Notice that d cannot be negative (this is general rule for all unsigned types).
  • printf("%d", d); invokes default argument promotions, thus d is converted to int and the (nonnegative) value is preserved (i.e. the int type can represent all values of unsigned char type).

1) assuming that int can represent all values of the unsigned char (see T.C.'s comment below), but it is very likely to happen in this way. More specifically, we assume that INT_MAX >= UCHAR_MAX holds. Typically the sizeof(int) > sizeof(unsigned char) holds and byte consist of eight bits. Otherwise the c would be converted to unsigned int (as by C11 subclause §6.3.1.1/p2), and the format specifier should be also changed accordingly to %u in order to avoid getting an UB (C11 §7.21.6.1/p9).

like image 123
Grzegorz Szpetkowski Avatar answered Nov 10 '22 08:11

Grzegorz Szpetkowski


char is promoted to int in printf statement before the operation ~ in second snippet. So c, which is

0000 0100 (2's complement)  

in binary is promoted to (assuming 32-bit machine)

0000 0000 0000 0000 0000 0000 0000 0100 // Say it is x  

and its bit-wise complement is equal to the two's complement of the value minus one (~x = −x − 1)

1111 1111 1111 1111 1111 1111 1111 1011  

which is -5 in decimal in 2's complement form.

Note that the default promotion of char c to int is also performed in

d = ~c;

before complement operation but the result is converted back to unsigned char as d is of type unsigned char.

C11: 6.5.16.1 Simple assignment (p2):

In simple assignment (=), the value of the right operand is converted to the type of the assignment expression and replaces the value stored in the object designated by the left operand.

and

6.5.16 (p3):

The type of an assignment expression is the type the left operand would have after lvalue conversion.

like image 27
haccks Avatar answered Nov 10 '22 09:11

haccks


To understand behavior of your code, you need to learn the concept called 'Integer Promotions' (that happens in your code implicitly before bit wise NOT operation on an unsigned char operand) As mentioned in N1570 committee draft:

§ 6.5.3.3 Unary arithmetic operators

  1. 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".

Because unsigned char type is narrower than (as it requires fewer bytes) int type, - implicit type promotion performed by abstract machine(compiler) and value of variable c is promoted to int at the time of compilation (before application of the complement operation ~). It is required for the correct execution of the program because ~ need an integer operand.

§ 6.5 Expressions

  1. Some operators (the unary operator ~, and the binary operators <<, >>, &, ^, and |, collectively described as bitwise operators) are required to have operands that have integer type. These operators yield values that depend on the internal representations of integers, and have implementation-defined and undefined aspects for signed types.

Compilers are smart-enough to analyze expressions, checks semantics of expressions, perform type checking and arithmetic conversions if required. That's the reason that to apply ~ on char type we don't need to explicitly write ~(int)c — called explicit type casting (and do avoid errors).

Note:

  1. Value of c is promoted to int in expression ~c, but type of c is still unsigned char - its type does not. Don't be confused.

  2. Important: result of ~ operation is of int type!, check below code (I don't have vs-compiler, I am using gcc):

    #include<stdio.h>
    #include<stdlib.h>
    int main(void){
       unsigned char c = 4;
       printf(" sizeof(int) = %zu,\n sizeof(unsigned char) = %zu",
                sizeof(int),
                sizeof(unsigned char));
       printf("\n sizeof(~c) = %zu", sizeof(~c));        
       printf("\n");
       return EXIT_SUCCESS;
    }
    

    compile it, and run:

    $ gcc -std=gnu99 -Wall -pedantic x.c -o x
    $ ./x
    sizeof(int) = 4,
    sizeof(unsigned char) = 1
    sizeof(~c) = 4
    

    Notice: size of result of ~c is same as of int, but not equals to unsigned char — result of ~ operator in this expression is int! that as mentioned 6.5.3.3 Unary arithmetic operators

    1. The result of the unary - operator is the negative of its (promoted) operand. The integer promotions are performed on the operand, and the result has the promoted type.

Now, as @haccks also explained in his answer -that result of ~c on 32-bit machine and for value of c = 4 is:

1111 1111 1111 1111 1111 1111 1111 1011

in decimal it is -5 — that is the output of your second code!

In your first code, one more line is interesting to understand b = ~c;, because b is an unsigned char variable and result of ~c is of int type, so to accommodate value of result of ~c to b result value (~c) is truncated to fit into the unsigned char type as follows:

    1111 1111 1111 1111 1111 1111 1111 1011  // -5 & 0xFF
 &  0000 0000 0000 0000 0000 0000 1111 1111  // - one byte      
    -------------------------------------------          
                                  1111 1011  

Decimal equivalent of 1111 1011 is 251. You could get same effect using:

printf("\n ~c = %d", ~c  & 0xFF); 

or as suggested by @ouah in his answer using explicitly casting.

like image 17
Grijesh Chauhan Avatar answered Nov 10 '22 07:11

Grijesh Chauhan