bitwise NOT

Started by
18 comments, last by alvaro 14 years, 4 months ago
Assuming you fully understand the bitwise NOT operator, and the fact that it NOTs all the bits in the value (32 bits for 32 bit values, 64 for 64, etc), you can read this:

http://en.wikipedia.org/wiki/Two's_complement

You are experiencing the 2's compliment rule. Just read the above article and it'l make sense (hopefully) :p
Advertisement
I'd still argue that for demonstration purposes unsigned types would be more suitable. (How negative numbers are represented is an interesting issue, but somewhat a separate topic from bitwise operators.)

While you can do things with signed values, how often do you really think about them in terms of positive and negative values? E.g, if you want all bits set, would you rather use -1 or ~0 (or 0xFFFFFFFF - BTW hex literals seem to be unsigned in C++)? If you want this bitmask 0xFFFF0000, would you consider using -65536 instead?
Start with an 8-bit value. An unsigned 8-bit number has the range 0 to 255, or 0x00 to 0xFF. A signed 8-bit number has the range -128 to 127, or 0x80 to 0x7F. Notice how exactly half the values are positive and exactly half the values are negative as long as you consider '0' to be positive.

2's compliment works so well because it naturally falls into how a computer counts. Notice when I wrote the range for a signed 8-bit number has the range -128 to 127, and wrote the hex representation of 0x80 to 0x7F. If you start counting from 0 (0x00) and count up to 127, you get 0x7F. Add one more and you get 0x80. Adding one more to 0x7F and making it 0x80 changes the MSB (Most Significant Bit) which is the sign bit. You just rolled over from 127 (0x7F) to -128 (0x80). This means the largest negative number of any n-bit value will have the sign bit set to '1' and all other values set to '0'. 0x80 is the largest negative 8-bit value, 0x8000 is the largest negative 16-bit value, 0x80000000 is the largest negative 32-bit value, etc.

That also means the smallest negative number of any n-bit value is all '1's. A signed 8-bit value of 0xFF is -1, which makes sense. Add 1 to -1 and you get 0. Add 1 to 0xFF and you roll over and get 0x00, or 0.


Now, in 2's compliment math, in order to invert a value, you need to perform two steps. First, invert all the bits. Second, add 1. Let's look at an example. Take '87', which is 0x57 or 01010111b. Inverting the bits gets you 0xA8 or 10101000b. Now, add 1 to get 0xA9 or 10101001b. 0xA9 == -87.

As a side note: simply inverting the bits is known as 1's compliment. Adding 1 after taking the 1's compliment gives you the 2's compliment. Those works in both directions, making a negative number positive and a positive number negative.


Hope this helps.
Quote:Original post by visitor
I'd still argue that for demonstration purposes unsigned types would be more suitable. (How negative numbers are represented is an interesting issue, but somewhat a separate topic from bitwise operators.)

While you can do things with signed values, how often do you really think about them in terms of positive and negative values? E.g, if you want all bits set, would you rather use -1 or ~0 (or 0xFFFFFFFF - BTW hex literals seem to be unsigned in C++)? If you want this bitmask 0xFFFF0000, would you consider using -65536 instead?

I think you're confusing the reasons for using one or another a bit. If you want the bit pattern for 0xFFFF0000, then of course you use 0xFFFF0000, becuase that is what you wanted. For the same reason, if you want the bit pattern for 200, you use 200 and not 0x000000C8. It's about intent, and 0xFFFF0000 describes your intent better, not that it is unsigned and you prefer to view bit patterns as unsigned values.

Personally, I look at bit patterns as neither signed or unsigned. I look at them as bit patterns. Whether signed or unsigned, bitwise operations rarely makes any sense when looking at the values underlying datatype, and not individual bits alone.
Quote:Original post by Brother Bob
[...] Whether signed or unsigned, bitwise operations rarely makes any sense when looking at the values underlying datatype, and not individual bits alone.


I generally agree. The only honest exception I know occurs in the analysis of impartial games, where Nimber addition is computed as bitwise exclusive-or. In this case, negative values make no sense.

There are some interesting bit operations you can do involving operations that don't appear at first glance to make any sense, such as ANDing with a negative number. For example, see if you can figure out what these do:


[1] x & (x - 1)
[2] (x + 8) & (-7)
Quote:Original post by cache_hit
For example, see if you can figure out what these do:


[1] x & (x - 1)
[2] (x + 8) & (-7)


[1] is a well-known trick to erase the last bit set in x.
[2] is a weird operation with no apparent use.
Quote:Original post by alvaro
Quote:Original post by cache_hit
For example, see if you can figure out what these do:


[1] x & (x - 1)
[2] (x + 8) & (-7)


[1] is a well-known trick to erase the last bit set in x.
[2] is a weird operation with no apparent use.



[1] Which is especially useful if you're trying to test whether a number is a power of 2, since that means x & (x - 1) == 0 if and only if x is a power of 2.
[2] Temporary dyslexia. I meant to write (x + 7) & (-8)
Quote:Original post by cache_hit
I meant to write (x + 7) & (-8)

Round up to nearest multiple of 8.
Quote:Original post by cache_hit
[1] Which is especially useful if you're trying to test whether a number is a power of 2, since that means x & (x - 1) == 0 if and only if x is a power of 2.


Watch your operator precedence! `x & (x - 1) == 0' if and only if x is 1.

This topic is closed to new replies.

Advertisement