※ ここでいう黒魔術とは、「マジックナンバーや、あまり一般的でない演算子を使うので、実装はシンプルになるけど、可読性は低いから、その点は注意しようね~」ぐらいの意味です。
※ 符号付き整数を想定しています
トグル
x ^= 1
0
にこれを作用させると 1
になり、1
にこれを作用させると 0
になります。これを利用して、トグルスイッチみたいに切り替えたり、「ペアのうち片方を指す」みたいな使い方をできたりします。
補数によるペア
~x
~
は全ビットを反転させます(補数)。反転前の数を $A$ とすると、 $-A-1$ を返します。
これの嬉しいポイントとして、ある非負整数に対して一対一対応のペアが作れる ということがあります。
例えば、非再帰DFSにおいて「行きがけ」と「帰りがけ」のそれぞれのノードを、「ある非負整数」と「その補数」として表現できます。
参考記事:それ、非再帰で書けます
ちなみに、補数の関係は以下のようになっています。わかりやすく示すために、$4$ bit符号付整数で考えています。
「数値の文字」と「数値」の切り替え
c ^= 48
数値の文字ならばその数値を、数値ならばそれに対応する文字を返します。
なぜそうなるのか
小文字と大文字の切り替え
c ^= 32
char
型の文字に対して、小文字ならば大文字を、大文字ならば小文字を返します。
なぜそうなるか
最も右にあるビットを削除する
x & (x-1)
その数で立っているビットのうち、最も右にあるものを削除します。
なぜそうなるか
最も右にあるビットだけを抽出する
x & -x
上のと似ていますが、その数で立っているビットのうち、最も右にあるだけを抽出します。
なぜそうなるか
2冪かどうかを判定
x & (x-1) == 0
上の応用ですが、「1つ以上ビットが立っており、最も右にあるビットを削除したとき、$0$ になる」と「$1$ つのビットだけが立っている」は同値です。そして、$1$ つのビットだけが立っていることは、$2^n$($n$ は非負整数) で表される $2$ 冪の数であることと同値です。
int main(){
for(int i=1;i<=16;i++){
bool beki = (i&(i-1)) == 0;
cout << i << " " << beki << endl;
}
}
1 1
2 1
3 0
4 1
5 0
6 0
7 0
8 1
9 0
10 0
11 0
12 0
13 0
14 0
15 0
16 1
最も近い2冪を出す
$2$ 冪かどうかの判別と、最右ビットの削除を組み合わせると、最も近い $2$ 冪を出すことができます。
「その数以下で最小の $2$ 冪」を求めます。その数が $2$ 冪ならそのまま、そうでなければ、そうなるまで最右ビットを削除していけばよいです。
int main(){
for(int i=1;i<=16;i++){
int x = i;
while(x&(x-1)){
x &= x-1;
}
cout << i << " " << x << endl;
}
}
1 1
2 2
3 2
4 4
5 4
6 4
7 4
8 8
9 8
10 8
11 8
12 8
13 8
14 8
15 8
16 16
少し改造すると、「その数以上で最小の $2$ 冪」を出すこともできます。
int main(){
for(int i=1;i<=16;i++){
int x = i;
if(x&(x-1)){
while(x&(x-1)){
x &= x-1;
}
x <<= 1;
}
cout << i << " " << x << endl;
}
}
1 1
2 2
3 4
4 4
5 8
6 8
7 8
8 8
9 16
10 16
11 16
12 16
13 16
14 16
15 16
16 16