ビット演算に関するメモです。
本記事では、汎整数拡張、通常の算術変換、既定の実引数拡張(にともなう符号拡張)の話題はスコープ外としますが、Cでビット演算をする場合はそれらに気を付けてください。暗黙の型変換にともなう符号拡張の影響により、素朴な直観に反する結果となる場合があります。
#「2の補数をとる」ことのビットイメージ
基本情報技術者試験で、2の補数は「ビット反転+1」
と暗記した思い出がありますが、その効果をビットイメージであらわすと
- 最右のビット
1
より左側にあるビットをすべて反転させる。全ビットが0
のときは0
を返す。(例:1010 1000 -> 0101 1000
)
`^^^^`で示すビットのみが反転
x -> -x
--------- ---------
1010 1000 -> 0101 1000
^^^^ ^^^^
1001 0100 -> 0110 1100
^^^^ ^ ^^^^ ^
1111 1111 -> 0000 0001
^^^^ ^^^ ^^^^ ^^^
0000 0000 -> 0000 0000
紙とペンを使って、何パターンかビット反転+1
をやってみると、この効果が成立するワケが腑に落ちるかと思います。
#x+1のビットイメージ
「1
の加算」は、手計算しても分かるように「最右のビット0
から最下位ビットまでをビット反転する」ことに等しいです。
x -> x + 1
--------- ---------
1010 1000 -> 1010 1001
^ ^
1001 0111 -> 1001 1000
^^^^ ^^^^
0000 0000 -> 0000 0001
^ ^
1111 1111 -> 0000 0000
^^^^ ^^^^ ^^^^ ^^^^
#x-1のビットイメージ
「1
の減算」は、手計算しても分かるように「最右のビット1
から最下位ビットまでをビット反転する」ことに等しいです。
x -> x - 1
--------- ---------
1010 1000 -> 1010 0111
^^^^ ^^^^
1001 0111 -> 1001 0110
^ ^
0000 0000 -> 1111 1111
^^^^ ^^^^ ^^^^ ^^^^
1111 1111 -> 1111 1110
^ ^
#「-~x」が「x+1」、「~-x」が「x-1」に等しい理由
N
ビットにおける「x
のビット反転(1の補数)」を数式で表現すると、以下に等しいです。
2^N - 1 - x
N
ビットにおける「x
の2の補数」を数式で表現すると、以下に等しいです。
2^N - x
以上をふまえると-~x
は
\begin{align}
2^N - (2^N - 1 - x)&= 2^N - 2^N + 1 + x\\ &= x + 1
\end{align}
同様に~-x
は
\begin{align}
2^N - 1 - (2^N - x)&= 2^N - 1 - 2^N + x\\ &= x - 1
\end{align}
#ビット演算イディオム集
この性質を利用して、いくつかイディオムを作ってみました(以下表)。
個人的には、-(-x|~x)
が「最右のビット1を最下位ビットに移動する」のが、ビットの動きがあって面白いなーと思いました。
x -> -(-x|~x)
--------- ---------
1000 1000 -> 1000 0001
^ ^
# | 式 | 説明 | 例 |
---|---|---|---|
1 | x^x |
定数0 を返す。 |
1010 1011 -> 0000 0000 |
2 | -~(x^x) |
定数1 を返す。 |
1010 1011 -> 0000 0001 |
3 |
~x|x ~(x^x) x ^ ~x
|
全ビット1 を返す。 |
1010 1011 -> 1111 1111 |
4 | -~x |
x + 1 を返す。 |
1010 1011 -> 1010 1100 |
5 | ~-x |
x - 1 を返す。 |
1010 1100 -> 1010 1011 |
6 | -(-x|~x) |
最右の1 を、最下位ビットに移動。・ x=0 のとき1 を返す。 |
1010 1000 -> 1010 0001 |
7 |
~(x|-x) |
末尾の連続する0 を保存するマスクを作成。・ x=0 のときすべてのビットが1 となる。・ x の最下位ビットが1 のとき0 を返す。 |
1010 1000 -> 0000 0111 |
8 | x ^ (x & -~x) |
末尾の連続する1 を保存するマスクを作成。・ x の最下位ビットが0 のとき0 を返す。 |
0100 1111 -> 0000 1111 |
9 |
x ^ (~-x) |
最右の1 と末尾の連続する0 を保存するマスクを作成。・ x=0 のときすべてのビットが1 となる。x の最下位ビットが1 のとき1 を返す。 |
1010 1000 -> 0000 1111 |
10 |
x ^ (-~x) |
最右の0 と末尾の連続する1 を保存するマスクを作成。・全ビットが 1 のときx を返す。 |
0101 0111 -> 0000 1111 |
11 | -(x&-x) |
最上位ビット~最右の1までを保存するマスクを作成。 ・ x=0 のとき0 を返す。 |
0110 1000 -> 1111 1000 |
12 | x & -x |
最右の1 をピックアップするマスクを作成。・ x=0 のとき0 を返す。 |
1010 1000 -> 0000 1000 |
13 | ~x & (-~x) |
最右の0 をピックアップするマスクを作成。・全ビットが 1 のとき0 を返す。 |
0100 1011 -> 0000 0100 |
14 | x ^ (-~(x|~-x) & x) |
最右の連続する1 を保存するマスクを作成。・ x=0 のとき0 を返す。 |
0100 1110 -> 0000 1110 |
15 | x ^ (~-(x|-~x)|x) |
最右の連続する0 を保存するマスクを作成。・全ビットが 1 のとき0 を返す。 |
0100 0011 -> 0011 1100 |
16 |
~(-x|~x) x & ~-x
|
最右の1 を0 にする。・ x=0 のとき0 を返す。 |
1010 1000 -> 1010 0000 |
17 | x | -~x |
最右の0 を1 にする。・全ビットが 1 のときx を返す。 |
1010 0111 -> 1010 1111 |
18 | -x & ~x |
「末尾の連続する0 」以外のビットをすべて反転する。・最下位ビットが1のとき全ビットを反転 |
1010 1000 -> 0101 0000 |
19 | x & -~x |
末尾の連続する1 を0 にする。・ x の最下位ビットが0 のときx を返す。 |
0100 1111 -> 0100 0000 |
20 | x | ~-x |
末尾の連続する0 を1 にする。・最下位ビットが 1 のときx を返す。 |
0100 1000 -> 0100 1111 |
21 | -~(x|~-x) & x |
最右の連続する1 を0 にする。・ x=0 のとき0 を返す。 |
0100 1110 -> 0100 0000 |
22 | ~-(x|-~x)|x |
最右の連続する0 を1 にする。・全ビットが 1 のときx を返す。 |
0101 0011 -> 0101 1111 |
#【参考1】ビットダンプ関数
#include <stdio.h>
#include <limits.h>
void bit_dump(unsigned char x)
{
for(size_t pos = sizeof(x) * CHAR_BIT; pos--;)
printf("%d", !!(x & 1<<pos));
}
#【参考2】ビット演算の演算子の優先順位
優先順位 | 演算子 | 結合性 |
---|---|---|
1 | 単項+、単項-、~、! | 右結合 |
2 | <<、>> | 左結合 |
3 | & | 左結合 |
4 | ^ | 左結合 |
5 | | | 左結合 |
6 | && | 左結合 |
7 | || | 左結合 |
#【参考3】参考文献