Why does "$(( ~33 ))" produce -34?
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 you1100
, 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.
Related videos on Youtube
gasko peter
Updated on September 18, 2022Comments
-
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
?