t-hom’s diary

主にVBAネタを扱っているブログ…とも言えなくなってきたこの頃。

VBAでビット演算(論理演算)

f:id:t-hom:20160211095553p:plain

ビット演算とは、二進数同士のちょっと変わった計算のことである。

通常、我々の日常生活では十進数を使っている。0~9の十種類を組み合わせて数を表現する方法だ。
二進数は1と0の二種類だけですべての数を表す方法だ。

たとえば、3は二進数では11、5は101となる。
対応表を作ると簡単に求まる。

下表の一番上の行は二進数の位を表す。

十進数↓ 128 64 32 16 8 4 2 1
3 0 0 0 0 0 0 1 1
5 0 0 0 0 0 1 0 1
93 0 1 0 1 1 1 0 1
27 0 0 0 1 1 0 1 1

たとえば3は、1と2で作れるので1の位と2の位に1を入れる。
5は4と1で作れるので101といった具合に埋めていけば良い。

この2進数の1桁のことをコンピューターではビットと呼ぶ。
これを使ったちょっと変わった計算が、ビット演算(または論理演算)と呼ばれる。

ビット演算にはいくつか種類があるが、代表的なものはANDとORだ。

ためしに3 AND 5というのをやってみよう。
ANDは、2つのビットのうち、両方が1のときに1となり、それ以外は0となる。
ケタをあわせて、位ごとにAND計算する。
3 = 0 1 1
5 = 1 0 1

4の位は0 And 1 = 0
2の位は1 And 0 = 0
1の位は1 And 1 = 1

よって、答えは001となる。十進数に直しても1である。

次にOR運算を紹介する。
ORは、どちらかのビットが1なら1、両方0のときは0になる。
計算過程は省略するが、結果は111となる。
十進では4+2+1=7となる。

実はこの計算、10進のままでよければVBAでも簡単にできる。

Sub test10()
    Debug.Print 3 And 5
    '結果は1
    Debug.Print 3 Or 5
    '結果は7
End Sub

VBAでは2進数を扱えないため、2進数は文字列で表現することになるが、2進数文字列のまま計算するよりは、整数型の10進数に直して計算した後に2進数文字列に戻したほうがパフォーマンスは良いかもしれない。

また、VBAでは10進数→16進数文字列の変換関数Hex$が用意されている。
16進数から2進数への変換は、変換表を使えば究めて簡単である。
16進数の一桁が2進数の4桁と対応しているため、以下のようになる。

0 1 2 3 4 5 6 7 8 9
0000 0001 0010 0011 0100 0101 0110 0111 1000 1001
A B C D E F
1010 1011 1100 1101 1110 1111


さて、試しに、5 And 12、5 Or 12、5 Xor 12をやってみた。

Dim BinArr(0 To 15) As String
Sub BinArrInit()
    '初めから16進数の対応を用意しておく。
    BinArr(0) = "0000"
    BinArr(1) = "0001"
    BinArr(2) = "0010"
    BinArr(3) = "0011"
    BinArr(4) = "0100"
    BinArr(5) = "0101"
    BinArr(6) = "0110"
    BinArr(7) = "0111"
    BinArr(8) = "1000"
    BinArr(9) = "1001"
    BinArr(10) = "1010"
    BinArr(11) = "1011"
    BinArr(12) = "1100"
    BinArr(13) = "1101"
    BinArr(14) = "1110"
    BinArr(15) = "1111"
End Sub
    
Sub Test()
    BinArrInit
    Debug.Print Hex2Bin(Hex$(5 And 12))
    Debug.Print Hex2Bin(Hex$(5 Or 12))
    Debug.Print Hex2Bin(Hex$(5 Xor 12))
End Sub

Function Hex2Bin(h As String) As String
    Dim Result As String
    For i = 1 To Len(h)
        x = Mid(h, i, 1)
        Select Case Asc(x)
        Case Asc("0") To Asc("9")
            '0~9ならそのままBinArrに渡す
            Result = Result & BinArr(x)
        Case Asc("a") To Asc("f")
            'a~fなら、10~15に変換して渡す
            Result = Result & BinArr(Asc(x) - Asc("a") + 10)
        Case Asc("A") To Asc("F")
            'A~Fなら、10~15に変換して渡す
            Result = Result & BinArr(Asc(x) - Asc("A") + 10)
        Case Else
            Err.Raise 1000, , "引数に不正な文字が含まれています。"
        End Select
    Next
    Hex2Bin = Result
End Function

Testプロシージャを実行すると、次の結果になる。

0100
1101
1001

Hex2Binで少しややこしい処理をしているので解説。
以下の部分である。

    Case Asc("A") To Asc("F")
        'A~Fなら、10~15に変換して渡す
        Result = Result & BinArr(Asc(x) - Asc("A") + 10)

Asc関数は文字コードを求めるもので、Aなら65、Bなら66となる。
ただ、ここで求めたいのは16進数である。Aは16進数で10となる。

以下の処理であるが、xが"A"のとき、65-65+10で10になる。

Asc(x) - Asc("A") + 10

xが"B"のとき、66-65+10で11になる。
xが"C"のとき、67-65+10で12になる。

つまり、Aとの差分を求めて、16進のAである10を足しているということ。

10の代わりに「&HA」としても良いけど、また余計な説明が増えるのでやめておいた。

ちなみにNotはビット演算とは挙動が違うのでオリジナルを作るしかない。今回Notはパス。

当ブログは、amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイト宣伝プログラムである、 Amazonアソシエイト・プログラムの参加者です。