Why does "$(( ~33 ))" produce -34?

5,141

Solution 1

The man page of bash says:

   ! ~    logical and bitwise negation

Signed numbers are usually stored in Two's complement representation:

...
-4 = 1100
-3 = 1101
-2 = 1110
-1 = 1111
 0 = 0000
 1 = 0001
 2 = 0010
 3 = 0011
...

This means if you take a number like 2 it is bitwise interpreted as 0010. After bitwise negation this becomes 1101, which is the representation of -3.

Solution 2

This is the result of two's complement arithmetic.

~ is a bitwise negation that inverts all of the bits being operated upon. Two's complement arithmetic works by inverting all of the bits and adding 1. Since you have only flipped the bits, but not added one, you get the same number, inverted, minus one.

Wikipedia has a good article on two's complement here.

As an example:

  • 3 in binary is 0011
  • -3 in (two's complement) binary is 1101
  • Inverting 0011 gives you 1100, which is -4, since you haven't added 1.

Solution 3

The ~ operator is the bitwise NOT operator. Using it is not the same as negating a number.

From wikipedia, a bitwise NOT operation is equal to taking the two's complement of the value minus one:

NOT x = −x − 1

Negating a binary number is equivalent to taking its two-complement value.

Using the ~ NOT operator = take its one-complement value.

In simpler terms, ~ just flips all the bits of the binary representation.

For your examples:

33 (decimal) = 0x00100001 (8-bit binary)

~33 = ~0x00100001 = 0x11011110 = -34 (decimal)

Or in decimal arithmetics, using the ~x = -x - 1 formula:

~33 = -33 - 1 = -34

and

~255 = -255 - 1 = -256

Solution 4

The ~ (arithmetic) operator flips all bits, it is called the bitwise negation operator:

! ~    logical and bitwise negation

So, in places where the context is arithmetic, it changes a number with all bits as zeros to all bits as ones. A $(( ~0 )) converts all the bits of the number representation (usually 64 bits nowadays) to all ones.

$ printf '%x\n' "$(( ~0 ))"
ffffffffffffffff

A number with all ones is interpreted as the negative number (first bit 1) 1, or simply -1.

$ printf '%x\n' "-1"
ffffffffffffffff

$ echo "$(( ~0 ))"
-1

The same happens to all other numbers, for example: $(( ~1 )) flips all bits:

$ printf '%x\n' "$(( ~1 ))"
fffffffffffffffe

Or, in binary: 1111111111111111111111111111111111111111111111111111111111111110

Which, interpreted as a number in two's representation is:

$ echo "$(( ~1 ))"
-2

In general, the human math equation is that $(( ~n )) is equal to $(( -n-1 ))

$ n=0    ; echo "$(( ~n )) $(( -n-1 ))"
-1 -1

$ n=1    ; echo "$(( ~n )) $(( -n-1 ))"
-2 -2

$ n=255  ; echo "$(( ~n )) $(( -n-1 ))"
-256 -256

And (your question):

$ n=33   ; echo "$(( ~n )) $(( -n-1 ))"
-34 -34

Solution 5

The problem is that ~ is a bit-wise operator. Hence you are negating more bits than you perhaps intend. You can see this better by converting the results to hex e.g.:

result_in_hex=$(printf "%x" $(( ~33 ))); echo $result_in_hex
ffffffffffffffde

versus what you had:

result_in_dec=$(printf "%d" $(( ~33 ))); echo $result_in_dec
-34

I'm assuming you mean to negate 0x33. If that is the case then this would work:

result_in_hex=$(printf "%2x" $(( ( ~ 0x33 ) & 0xFF))); echo $result_in_hex
cc

You need to also use & which is the bit-wise and operator to avoid all the ff at the start.

Share:
5,141

Related videos on Youtube

gasko peter
Author by

gasko peter

Updated on September 18, 2022

Comments

  • gasko peter
    gasko peter almost 2 years
    $ echo $(( 255 ))
    255
    $ echo $(( 33 ))
    33
    $ echo $(( ~33 ))
    -34
    $ echo $(( ~255 ))
    -256
    $ 
    

    and my kernel is:

    $ uname -a
    Linux HOSTNAME 3.2.0-40-generic-pae #64-Ubuntu SMP Mon Mar 25 21:44:41 UTC 2013 i686 i686 i386 GNU/Linux
    

    QUESTION: ~ is for negating the number AFAIK. But why does ~33 produce -34 and why does ~255 produce -256?