ゴミ箱

くだらないことからUnityの知識共有まで

ビット演算の考え方が面白い

ビット演算ってなんだ

Wikipedia先生より

ビット演算(ビットえんざん、bitwise operation: 直訳すると「ビット毎操作」)とは、固定長のワードなどといった「ビットのカタマリ」(コンピュータの数値表現なども参照)に対して、各のビット全てに対する論理演算をいっぺんに行う演算操作である。 実装の観点からは、現在一般的な二進法(ディジタル)式の電子式コンピュータでは、加減算ではビットあたり数個程度の論理ゲートに加え多少複雑なキャリー伝搬の処理が、乗除算では多段に渡る処理が必要であるのに対し、ビット演算は1個か高々2個の論理ゲートで行えるため、多くの場合、最短サイクルしか必要としない。そのことから、高性能なプログラムを実現するための機械語コーディングではビット演算の使いこなしは重要なテクニックである。

コンピュータの世界では全てのデータを二進数で扱っていて、ビットに対して演算を行う方法は高速な処理を実現できる。ってことですかね…。 私みたいにUnityからゲーム開発を始めた新参者にはなかなか想像しづらいのですが、普段使っているintなどもコンピュータ上では二進数で計算が行われているってことですね。 そして二進数で扱っているからこそできる演算方法がビット演算です。

十進数と二進数

f:id:blacker1017:20180225203712p:plain
前提知識として十進数と二進数の対応表を張っておきます。 見てわかる通り0と1で表されていて、なんかこう桁が上がっていくんですよ(語彙力)。 詳しくはググってください。

ビット演算の使用例

早速ですが実際にどんなときに使えるのかというと、複数のフラグを管理したいときです。 まずはビット演算を使わない例を下のコードに示します。言語はC#です。

public class Smple1
{
    public class ProgrammingSkill
    {
        public bool c = false;
        public bool cpp = false;
        public bool cs = false;
        public bool java = false;
        public bool php = false;
    }
    
    public static void Main()
    {
        ProgrammingSkill mySkill = new ProgrammingSkill();
        mySkill.cpp = true;
        mySkill.cs = true;
        
        OutPut("c", mySkill.c);
        OutPut("cpp", mySkill.cpp);
        OutPut("cs", mySkill.cs);
        OutPut("java", mySkill.java);
        OutPut("php", mySkill.php);
    }
    
    public static void OutPut(string skillName, bool skillFlag)
    {
        string temp = skillFlag ? "はできます!" : "はできません…";
        System.Console.WriteLine(skillName + temp);
    }
}

出力結果

cはできません…
cppはできます!
csはできます!
javaはできません…
phpはできません…

次にビット演算を使うと以下のようになります。 出力結果は変わりません。

public class Smple2
{
    public static int c = 1;    // 00001
    public static int cpp = 2;  // 00010
    public static int cs = 4;   // 00100
    public static int java = 8; // 01000
    public static int php = 16; // 10000
    
    public static void Main()
    {
        //! cppとcsのフラグを立てる
        int mySkill = cpp | cs; // 00110
        
        OutPut("c", (mySkill & c) != 0);
        OutPut("cpp", (mySkill & cpp) != 0);
        OutPut("cs", (mySkill & cs) != 0);
        OutPut("java", (mySkill & java) != 0);
        OutPut("php", (mySkill & php) != 0);
    }
    
    public static void OutPut(string skillName, bool skillFlag)
    {
        string temp = skillFlag ? "はできます!" : "はできません…";
        System.Console.WriteLine(skillName + temp);
    }
}

複数のboolフラグで管理していたmySkillが一つのintで表すこととができています。 考え方としては、intで保存している二進数はそれぞれの桁が必ず0か1なので0がfalse、1がtrueのフラグを桁数分持っていてそれによって判定を行う、ってかんじですかね。

実際に使う演算子をコメントで説明しようとして失敗しているのが下のコードです。 今回のようにintをフラグとして利用する時に使う演算の一例です。 理解できなかったらググってください…

public class Sample3
{
    public static void Main()
    {
        //! 0001 (1)
        //! 0010 (2)
        int temp = 1 | 2;
        //! 0011 (3) ←OR演算の結果
        
        //! 0011 (3)
        //! 0100 (4) 
        temp |= 4;
        //! 0111 (7) ←OR演算の結果
        
        //! 0111 (7)
        //! 1101 (2の反転)
        temp &= ~2;
        //! 0101 (5) ←AND演算の結果
        
        //! tmpは2のフラグが立っているか
        System.Console.WriteLine((temp & 2) != 0);
        //! 出力結果 False
        
        //! 最終的には1と4のフラグが立った状態
    }
}


終わりに

正直、Smple1のように複数のフラグをクラスで管理していた方がイメージしやすく拡張性もあると思うので、 Sample2のようなビット演算の使いどころは慎重に考える必要があると思います。 ただ、二進数のintを複数のフラグとする考え方は面白いと思ったので今回の記事を書きました。 こういう考え方もあるんだ、と知っていただけたら幸いです。

最後にSmple2にあるintのビットデータ宣言を簡単にする方法を以下のコードに記します。

public class Smple4
{
    public int c = 1;           // 00001
    public int cpp = c << 1;    // 00010
    public int cs = cpp << 1;   // 00100
    public int java = cs << 1;  // 01000
    public int php = java << 1; // 10000
}

「<<」はシフト演算?と言うらしいです。 意味としては1の位置を左に一個移動するってことらしいです。 ビットデータを一つずつ手で宣言するより正確なのでこっちをオススメします。