Reputation: 45434
Let i
be a signed integer type. Consider
i += (i&-i);
i -= (i&-i);
where initially i>0
.
Source: setter's code of an online coding puzzle (w/o any explanation/comments).
Upvotes: 17
Views: 4050
Reputation: 4547
The expression i & -i
is based on Two's Complement being used to represent negative integers. Simply put, it returns a value k
where each bit except the least significant non-zero bit of i
is set to 0
, but that particular bit keeps its own value. (i.e. 1
)
As long as the expression you provided executes in a system where Two's Complement is being used to represent negative integers, it would be portable. So, the answer to your second question would be that the expression is dependent on the representation of negative integers.
To answer your first question, since arithmetic expressions are dependent on the data types and their representations, I do not think that there is a solely arithmetic expression that would be equivalent to the expression i & -i
. In essence, the code below would be equivalent in functionality to that expression. (assuming that i
is of type int
) Notice, though, that I had to make use of a loop to produce the same functionality, and not just arithmetics.
int tmp = 0, k = 0;
while(tmp < 32)
{
if(i & (1 << tmp))
{
k = i & (1 << tmp);
break;
}
tmp++;
}
i += k;
Upvotes: 11
Reputation: 215277
If i
has unsigned type, the expressions are completely portable and well-defined.
If i
has signed type, it's not portable, since &
is defined in terms of representations but unary -
, +=
, and -=
are defined in terms of values. If the next version of the C++ standard mandates twos complement, though, it will become portable, and will do the same thing as in the unsigned case.
In the unsigned case (and the twos complement case), it's easy to confirm that i&-i
is a power of two (has only one bit nonzero), and has the same value as the lowest-place bit of i
(which is also the lowest-place bit of -i
). Therefore:
i -= i&-i;
clears the lowest-set bit of i
.i += i&-i;
increments (clearing, but with carry to higher bits) the lowest-set bit of i
.For unsigned types there is never overflow for either expression. For signed types, i -= i&-i
overflows taking -i
when i
initially has the minimum value of the type, and i += i&-i
overflows in the +=
when i
initially has the max value of the type.
Upvotes: 6
Reputation: 4082
The easiest way to think of it is in terms of the mathematical equivalence:
-i == (~i + 1)
So -i
inverts the bits of the value and then adds 1
. The significance of this is that all the lower 0
bits of i
are turned into 1
s by the ~i
operation, so adding 1
to the value causes all those low 1
bits to flip to 0
whilst carrying the 1
upwards until it lands in a 0
bit, which will just happen to be the same position as the lowest 1
bit in i
.
Here's an example for the number 6 (0110 in binary):
i = 0110
~i == 1001
(~i + 1) == 1010
i & (~i + 1) == 0010
You may need to do each operation manually a few times before you realise the patterns in the bits.
Here's two more examples:
i = 1000
~i == 0111
(~i + 1) == 1000
i & (~i + 1) == 1000
i = 1100
~i == 0011
(~i + 1) == 0100
i & (~i + 1) == 0100
See how the + 1
causes a sort of 'bit cascade' carrying the one up to the first open 0
bit?
So if (i & -i)
is a means of extracting the lowest 1
bit, then it follows that the use cases of i += (i & -i)
and i -= (i & -i)
are attempts to add and subtract the lowest 1 bit of a value.
Subtracting the lowest 1 bit of a value from itself serves as a means to zero out that bit.
Adding the lowest 1 bit of a value to itself doesn't appear to have any special purpose, it just does what it says on the tin.
It should be portable on any system using two's complement.
Upvotes: 0
Reputation: 45434
Here is what I researched prompted by other answers. The bit manipulations
i -= (i&-i); // strips off the LSB (least-significant bit)
i += (i&-i); // adds the LSB
are used, predominantly, in traversing a Fenwick tree. In particular, i&-i
gives the LSB if signed integers are represented via two's complement. As already pointed out by Peter Fenwick in his original proposal, this is not portable to other signed integer representations. However,
i &= i-1; // strips off the LSB
is (it also works with one's complement and signed magnitude representations) and has one fewer operations.
However there appears to be no simple portable alternative for adding the LSB.
Upvotes: 5
Reputation: 40080
On a Two's Complement architecture, with 4 bits signed integers:
| i | bin | comp | -i | i&-i | dec |
+----+------+------+----+------+-----+
| 0 | 0000 | 0000 | -0 | 0000 | 0 |
| 1 | 0001 | 1111 | -1 | 0001 | 1 |
| 2 | 0010 | 1110 | -2 | 0010 | 2 |
| 3 | 0011 | 1101 | -3 | 0001 | 1 |
| 4 | 0100 | 1100 | -4 | 0100 | 4 |
| 5 | 0101 | 1011 | -5 | 0001 | 1 |
| 6 | 0110 | 1010 | -6 | 0010 | 2 |
| 7 | 0111 | 1001 | -7 | 0001 | 1 |
| -8 | 1000 | 1000 | -8 | 1000 | 8 |
| -7 | 1001 | 0111 | 7 | 0001 | 1 |
| -6 | 1010 | 0110 | 6 | 0010 | 2 |
| -5 | 1011 | 0101 | 5 | 0001 | 1 |
| -4 | 1100 | 0100 | 4 | 0100 | 4 |
| -3 | 1101 | 0011 | 3 | 0001 | 1 |
| -2 | 1110 | 0010 | 2 | 0010 | 2 |
| -1 | 1111 | 0001 | 1 | 0001 | 1 |
Remarks:
i&-i
only has one bit set (it's a power of 2) and it matches the least significant bit set of i
.i + (i&-i)
has the interesting property to be one bit closer to the next power of two.i += (i&-i)
sets the least significant unset bit of i
.So, doing i += (i&-i);
will eventually make you jump to the next power of two:
| i | i&-i | sum | | i | i&-i | sum |
+---+------+-----+ +---+------+-----+
| 1 | 1 | 2 | | 5 | 1 | 6 |
| 2 | 2 | 4 | | 6 | 2 | -8 |
| 4 | 4 | -8 | |-8 | -8 | UB |
|-8 | -8 | UB |
| i | i&-i | sum | | i | i&-i | sum |
+---+------+-----+ +---+------+-----+
| 3 | 1 | 4 | | 7 | 1 | -8 |
| 4 | 4 | -8 | |-8 | -8 | UB |
|-8 | -8 | UB |
UB: overflow of signed integer exhibits undefined behavior.
Upvotes: 10
Reputation: 270
i & -i
is the easiest way to get the least significant bit (LSB) for an integer i
.
You can read more here.
A1: You can read more about 'Mathematical Equivalents' here.
A2: If the negative integer representation is not the usual standard form (i.e. weird big integers), then i & -i
might not be LSB.
Upvotes: 3