コンピューターは「電子計算機」と訳されるように、計算する機械である。
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
こちらがそのレシピとなる、加算器の論理回路図。
といっても、回路図に慣れていない方も多いと思うので、以下に意味を記載する。
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になってしまうのだ。
ということで、もう一つのレシピを使うことにした。
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を格納して返すことにした。
メインコードを実行するとこうなった。
実際に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を計算するケースなら半加算器でも事足りる。
まず1の位を計算すると、0 + 1で01になる。
入力A、Bと出力S、Cがあるが、このときCは0なので、次の桁で使用されないからだ。
そのため2の位でも1 + 0で01になる。3の位は0 + 0 = 0だ。
ところが011+011のような計算では、繰り上がりの結果が次の位で利用されることになる。
まず1の位のCが1になるので、2の位へ繰り越される。
そして2の位ではもともとの入力A、Bに加え、繰り上がってきた1の位のCも新たに入力Xとして加算したうえで、改めてSとCを出力する。
これを行うのが全加算器である。
全加算器も、Wikipediaで作り方がわかる。
コードにするとこうなる。
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から。
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は得意だけど、情報処理は難しいって方は、コードを書いて学習するのも良いかもしれない。