t-hom’s diary

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

VBA 基本情報技術者試験に出てくる半加算器と全加算器をVBAコードで作って学習する

コンピューターは「電子計算機」と訳されるように、計算する機械である。
Youtubeを見ているときも音楽を聴いているときも、ゲームで遊んでいるときも、メールを読んでいるときも、CPUでは絶えず「計算」が行われている。

さて、今回はそのコンピューターがどのように計算しているのかという話。
といっても、トランジスタが云々といった電気的な話まで掘り下げるとややこしいので、AndとかOr、XorといったVBAの命令で扱える範囲で説明していこうと思う。

まあ要するに、基本情報技術者試験に出てくる半加算器と全加算器をコードで作ってみようという話である。

前提知識

まず前提知識として、コンピュータの内部では数値はすべて二進数で扱われることを知っている必要がある。

以下は4桁までの二進数と十進数の対応表である。

二進数 十進数
0000 0
0001 1
0010 2
0011 3
0100 4
0101 5
0110 6
0111 7
1000 8
1001 9
1010 10
1011 11
1100 12
1101 13
1110 14
1111 15

こんな風に0と1しか使えないから、すぐ繰り上がる。

人間にしてみたら桁が増えて、とっても面倒くさい。しかしコンピューターで扱うには都合がいい。

なぜかというと、まずCPUの電力を下げられる。0と1の2種類だけなら、0ボルトを0、1ボルトを1とすれば良い。(もちろんホントはもっと電圧は低いけど細かく書くと混乱するので便宜上1ボルト単位とする。)10進数だと、0~9ボルトまでの電圧差であらわすことになってしまい、それだけで電気を喰う。

次にハードディスク。N極とS極の2種類しかあらわせない磁器によるデータの保管ができるのは、二進数のおかげである。CDも、表面の凹凸というたった2種類の状態の差でデータを記録している。

二進数の計算

さて、計算の話に戻ろう。二進数で計算するということはたとえば、2 + 3と書いたらコンピュータの中身は「0010 + 0011 = 0101」と計算されていることになる。
二進数のひと桁をビットと呼ぶが、内部ではビットとビットが加算され、繰り上がるということが延々と行われている。
その加算を行う基本的なユニットである、半加算器をさっそくVBAで作ってみよう。

半加算器を作る

半加算器とは、1桁どうしの足し算を行い、2桁の結果を出力する装置である。
つまり、以下の4種類である。
0 + 0 = 00
1 + 0 = 01
0 + 1 = 01
1 + 1 = 10

加算器の回路はWikipediaで紹介されている。
加算器 - Wikipedia

こちらがそのレシピとなる、加算器の論理回路図。
f:id:t-hom:20161018202216p:plain

といっても、回路図に慣れていない方も多いと思うので、以下に意味を記載する。
f:id:t-hom:20161018202546p:plain

AとBが入力、出力のSが1の位、Cが2の位である。
Aが1、Bも1なら、Cが1、Sが0となる。
1 + 1 = 10。つまりA + B = CSということ。

さっそくVBAでやってみたいが、この加算器のレシピ、VBAで扱うに当たってひとつ問題がある。
VBAではAnd、Orに1や0を与えた場合、真理値表どおりに出力されるのだが、Not演算だけはうまくいかない。
Not 0が-1、Not 1が-2になってしまうのだ。

ということで、もう一つのレシピを使うことにした。
f:id:t-hom:20161018203630p:plain

Xorを用いた方法で、先ほどよりシンプルだけれど、同じことができる。

書いたコードがこちら。

Type Output
    S As Byte
    C As Byte
End Type

Function 半加算器(ByVal A As Byte, ByVal B As Byte) As Output
    半加算器.S = A Xor B
    半加算器.C = A And B
End Function

Sub メイン()
    Dim O As Output
    
    O = 半加算器(0, 0)
    Debug.Print O.C & O.S
    
    O = 半加算器(0, 1)
    Debug.Print O.C & O.S
    
    O = 半加算器(1, 0)
    Debug.Print O.C & O.S
    
    O = 半加算器(1, 1)
    Debug.Print O.C & O.S
End Sub

まず半加算器をFunctionで作ることにした。
AとBはふつうに引数で良いが、Functionプロシージャは値を一つしか返せない。
そこで、Outputというユーザー定義型にCとSを格納して返すことにした。

メインコードを実行するとこうなった。
f:id:t-hom:20161018204337p:plain

実際に1 + 1 = 10ができている。

次に全加算器であるが、その前に半加算器に0と1以外の数値を入れると想定外のことが起きるので、以下のように0と1以外は受け付けないように改良しておいた。

Function 半加算器(ByVal A As Byte, ByVal B As Byte) As Output
    If A = 0 Or A = 1 And B = 0 Or B = 1 Then
        半加算器.S = A Xor B
        半加算器.C = A And B
    Else
        Err.Raise 10000, Description:="有効な引数は1か0のみです"
    End If
End Function

全加算器を作る

全加算器って何かというと、桁の繰り上げに対応した加算器である。
半加算器でできるのは次の4種類の計算であるが、繰り上がってきたビットを足すにはこれだけでは足りない。
0 + 0 = 00
1 + 0 = 01
0 + 1 = 01
1 + 1 = 10

たとえば、010 + 001を計算するケースなら半加算器でも事足りる。
f:id:t-hom:20161018205933p:plain

まず1の位を計算すると、0 + 1で01になる。
入力A、Bと出力S、Cがあるが、このときCは0なので、次の桁で使用されないからだ。
f:id:t-hom:20161018210058p:plain

そのため2の位でも1 + 0で01になる。3の位は0 + 0 = 0だ。

ところが011+011のような計算では、繰り上がりの結果が次の位で利用されることになる。
f:id:t-hom:20161018210511p:plain

まず1の位のCが1になるので、2の位へ繰り越される。
f:id:t-hom:20161018210551p:plain

そして2の位ではもともとの入力A、Bに加え、繰り上がってきた1の位のCも新たに入力Xとして加算したうえで、改めてSとCを出力する。
f:id:t-hom:20161018211000p:plain

これを行うのが全加算器である。
全加算器も、Wikipediaで作り方がわかる。
f:id:t-hom:20161018211225p:plain

コードにするとこうなる。

Function 全加算器(ByVal A As Byte, ByVal B As Byte, ByVal X As Byte) As Output
    Dim O1 As Output, O2 As Output
    O1 = 半加算器(A, B)
    O2 = 半加算器(O1.S, X)
    全加算器.S = O2.S
    全加算器.C = O1.C Or O2.C
End Function

複数ビットの加算器

さて、全加算器が完成したので、これを連ねて複数ビットの計算を行えるようにしてみる。
レシピはWikipediaから。
f:id:t-hom:20161018211442p:plain

6桁ってのも半端なので、8ビット(つまり1バイト)の加算器を作ることにした。
完成した全コードはこちら。

Type Output
    S As Byte
    C As Byte
End Type

Type Octet
    Bit0 As Byte
    Bit1 As Byte
    Bit2 As Byte
    Bit3 As Byte
    Bit4 As Byte
    Bit5 As Byte
    Bit6 As Byte
    Bit7 As Byte
End Type

Function 半加算器(ByVal A As Byte, ByVal B As Byte) As Output
    If A = 0 Or A = 1 And B = 0 Or B = 1 Then
        半加算器.S = A Xor B
        半加算器.C = A And B
    Else
        Err.Raise 10000, Description:="有効な引数は1か0のみです"
    End If
End Function

Function 全加算器(ByVal A As Byte, ByVal B As Byte, ByVal X As Byte) As Output
    Dim O1 As Output, O2 As Output
    O1 = 半加算器(A, B)
    O2 = 半加算器(O1.S, X)
    全加算器.S = O2.S
    全加算器.C = O1.C Or O2.C
End Function

Function 八ビット加算器(A As Octet, B As Octet) As Octet
    Dim S0 As Output
    S0 = 半加算器(A.Bit0, B.Bit0)
    八ビット加算器.Bit0 = S0.S
    
    Dim S1 As Output
    S1 = 全加算器(A.Bit1, B.Bit1, S0.C)
    八ビット加算器.Bit1 = S1.S
    
    Dim S2 As Output
    S2 = 全加算器(A.Bit2, B.Bit2, S1.C)
    八ビット加算器.Bit2 = S2.S

    Dim S3 As Output
    S3 = 全加算器(A.Bit3, B.Bit3, S2.C)
    八ビット加算器.Bit3 = S3.S

    Dim S4 As Output
    S4 = 全加算器(A.Bit4, B.Bit4, S3.C)
    八ビット加算器.Bit4 = S4.S

    Dim S5 As Output
    S5 = 全加算器(A.Bit5, B.Bit5, S4.C)
    八ビット加算器.Bit5 = S5.S
    
    Dim S6 As Output
    S6 = 全加算器(A.Bit6, B.Bit6, S5.C)
    八ビット加算器.Bit6 = S6.S
    
    Dim S7 As Output
    S7 = 全加算器(A.Bit7, B.Bit7, S6.C)
    八ビット加算器.Bit7 = S7.S
End Function

Function CreateOctet(A, B, C, D, E, F, G, H) As Octet
    CreateOctet.Bit7 = A
    CreateOctet.Bit6 = B
    CreateOctet.Bit5 = C
    CreateOctet.Bit4 = D
    CreateOctet.Bit3 = E
    CreateOctet.Bit2 = F
    CreateOctet.Bit1 = G
    CreateOctet.Bit0 = H
End Function

Sub PrintOctet(O As Octet)
    Debug.Print O.Bit7 & O.Bit6 & O.Bit5 & O.Bit4 & O.Bit3 & O.Bit2 & O.Bit1 & O.Bit0
End Sub

Sub メイン()
    Dim O1 As Octet
    O1 = CreateOctet(0, 0, 0, 1, 1, 0, 0, 1)
    PrintOctet O1
    
    Dim O2 As Octet
    O2 = CreateOctet(0, 1, 1, 0, 0, 0, 1, 0)
    PrintOctet O2
    
    Dim O3 As Octet
    O3 = 八ビット加算器(O1, O2)
    PrintOctet O3
End Sub

まず8ビットをまとめて引数や戻り値として扱うために、ユーザー定義型Octetを作成した。
それからメインコードでビットごとの代入が面倒なので、CreateOctet関数でビットの並び順にOctetを作成してくれるようにして、ならべて二進数っぽく表示させるためにPrintOctetプロシージャも作成。

八ビット加算器がやってることは、愚直にWikipediaのレシピに従って、入力→出力→入力とつないでいるだけ。
コードを書くことで、全加算器が繰り上がりを扱う仕組みがすこぶるよくわかった。

VBAは得意だけど、情報処理は難しいって方は、コードを書いて学習するのも良いかもしれない。

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