2のべき乗をもとめるメソッドの挙動を追ってみよう
次のメソッドIsPowerOfTwo
は2のべき乗を求めるためのメソッドなのですが、処理をすぐに頭で終えるでしょうか?
bool IsPowerOfTwo(int num)
{
return (num & (num - 1)) == 0;
}
&
は、bitごとのAnd演算を行う演算子です。32bitの2の補数表現で表した二進数で上記の処理を考えてみましょう。
numが1のとき、
-
00000000000000000000000000000001
<- 1 の4bitの2の補数表現 -
00000000000000000000000000000000
<- 1 - 1 = 0 の32bitの2の補数表現 - 上記二つをビット演算でAndすると結果は
0
- 結果、IsPowerOfTwoは
true
となる
numが4のとき、
-
00000000000000000000000000000100
<- 4 の4bitの2の補数表現 -
00000000000000000000000000000011
<- 4 - 1 = 3 の32bitの2の補数表現 - 上記二つをビット演算でAndすると結果は
0
- 結果、IsPowerOfTwoは
true
となる
numが32のとき、
-
00000000000000000000000000100000
<- 32 の4bitの2の補数表現 -
00000000000000000000000000011111
<- 32 - 1 = 31 の32bitの2の補数表現 - 上記二つをビット演算でAndすると結果は
0
- 結果、IsPowerOfTwoは
true
となる
numが31のとき、
-
00000000000000000000000000011111
<- 31 の4bitの2の補数表現 -
00000000000000000000000000011110
<- 31 - 1 = 30 の32bitの2の補数表現 - 上記二つをビット演算でAndすると結果は
1
- 結果、IsPowerOfTwoは
false
となる
処理を追うことができたでしょうか?
このようにbitごとのAnd演算を活用して、IsPowerOfTwo
メソッドはint型の数値が2のべき乗かどうか調べています。
さて、残念ながらIsPowerOfTwo
では、落とし穴があります。気づきましたか?
正しくないIsPowerOfTwo
IsPowerOfTwo
の落とし穴は、2のべき乗でない0
とint.MinValue
でもtrue
を返してしまうということです。
0は2のべき乗ではありませんね。
32bitの2の補数表現で、numが0
の時を考えてみましょう。
numが0のとき、
-
00000000000000000000000000000000
<- 0 の4bitの2の補数表現 -
11111111111111111111111111111111
<- 0 - 1 = -1 の32bitの2の補数表現 - 上記二つをビット演算でAndすると結果は
0
- 結果、IsPowerOfTwoは
true
となる
このように0は2のべき乗でないのに、true
を返してしまいます。
同様に、numがint.MinValue(=-2,147,483,648)のときを示します。
-
10000000000000000000000000000000
<- int.MinValue(=-2,147,483,648) の4bitの2の補数表現 -
01111111111111111111111111111111
<- オーバーフローが起きる、int.MinValue(=-2,147,483,648) - 1 = int.MaxValue(=2,147, 483,647) の32bitの2の補数表現 - 上記二つをビット演算でAndすると結果は
0
- 結果、IsPowerOfTwoは
true
となる
このように2のべき乗を求める計算のつもりだった、次のコードは
bool IsPowerOfTwo(int num)
{
return (num & (num - 1)) == 0;
}
0
とint.MinValue
でtrue
を返すので、正しいメソッドではありません。
正しくは、次のようにする必要があります。
bool IsPowerOfTwo(int num)
{
if(num <= 0) {
return false;
} else {
return (num & (num - 1)) == 0;
}
}
Unityでのバグ
Unityには、Mathf.IsPowerOfTwoという引数にとったintが2のべき乗かどうか調べるメソッドがあります。
残念ながらこのメソッドは、2のべき乗でない0
とint.MinValue
でもtrue
を返してしまいます。
このMathf.IsPowerOfTwoメソッドは外部で実装されていて、IL Viewerでその実装を確認することはできないのですが、本投稿で示した次のような実装がされている可能性があります。
そのため、このような正しくなっていない挙動となっている可能性があります。
bool IsPowerOfTwo(int num)
{
return (num & (num - 1)) == 0;
}
IsPowerOfTwo
メソッドを使う機会があり、0を引数に渡すことがある場合は、特に注意が必要ですね。