コンピュータにおける四則演算
コンピュータは単純な処理が得意であり、とりわけ足し算が得意であるという特徴があります。
しかし、その一方で引き算やかけ算、割り算については「知らない」のです。
ところが、「知らない」というのは「出来ない」ということを意味しているわけではありません。
コンピュータには引き算やかけ算、割り算を実行可能にする仕組みがあります。
そこで、今回はコンピュータが引き算を実行可能にしている仕組みについて考えていきたいと思います。
足し算によって引き算と同じ結果を得る仕組み
足し算しか知らないコンピュータで引き算を実現するのであれば、足し算を利用して引き算と同じ結果を得るということができれば良いと考えることができます。
たとえば、$6-4$という引き算をコンピュータに実行させたいときのことを考えてみます。
$6$から$4$を引くということをコンピュータは知りません。
しかし、足し算は可能ですから$6$に**$-4$を足す**ということはできます。
つまり、$6$から$4$を引くときの「$4$」という正の数を「$-4$」という負の数に変換し、それを$6$に足すことは可能です。
すると、この両者は同じ結果を得ることができます。
6-4=6+(-4)
したがって、コンピュータで負の数を表現することができれば引き算を実現することができるのです。
コンピュータで負の数を表す方法
コンピュータが負の数を表現するにあたり、最も単純な方法として、「2進数の先頭の1ビット(1桁)を符号にする」というものがあります。
先頭の1ビットが「0」ならば正の値、「1」ならば負の値を表します。
たとえば、8ビットの2進数「$00000101$」をこの方法で負の数として表現する場合について考えてみます。
この2進数を正と負にしたものが以下の表になります。
|符号ビット| | | | | | | | | 表現する値 |
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|$0$|$0$|$0$|$0$|$0$|$1$|$0$|$1$| |$5$|
|$1$|$0$|$0$|$0$|$0$|$1$|$0$|$1$| |$-5$|
|||||||||||
|$1$|$0$|$0$|$0$|$1$|$0$|$1$|$0$| |$0?$|
ところで、絶対値が同じ正と負の数を足した場合、その和は0になります。
そのことを踏まえて表の一番下の行に注目してください。
この行は2進数の正と負の数を足したものですが、全ての桁が0にはなっていません。
したがって、単に先頭の1ビットの値を符号として、その部分だけを変えたとしても負の数を正しく表現することはできないということになります。
補数を使って負の数を表現する
そこで、コンピュータは「補数」という表現方法を使って負の数を表します。
補数には文字通り「補う数」という意味があります。
また、補数には「その桁数での最大値を得るために補う数」と「次の桁に繰り上がるために補う数」という2種類が存在します。
10進数においては、前者を「9の補数」、後者を「10の補数」と呼びます。
そして、2進数においては、前者を「1の補数」、後者を「2の補数」と呼びます。
10進数の9の補数と10の補数の求め方
ここで、例として10進数「$739$」という数の補数について考えてみましょう。
この数は3桁の数です。そして、3桁で表現できる最大の数は「$999$」になります。
$739$が最大値の$999$になるためにいくつ数を補ってあげればよいか、これが9の補数に当たる数です。
以下の数式では9の補数と$739$の関係を表現しています。
\begin{align}
739+(9の補数)&=999\\
(9の補数)&=999-739\\
&=260
\end{align}
この式より、$739$の9の補数は$260$と求められました。
次に、$739$という3桁の数にある数を加えて桁が繰り上がるときのことを考えてみましょう。
3桁の数が繰り上がって4桁の数になるときの数は「$1000$」になります。
$739$が4桁の数$1000$になるためにいくつ数を補ってあげればよいか、これが10の補数に当たる数です。
以下の数式では10の補数と739の関係を表現しています。
\begin{align}
739+(10の補数)&=1000\\
(10の補数)&=1000-739\\
&=261
\end{align}
この式より、$739$の10の補数は$261$と求められました。
2進数の1の補数と2の補数の求め方
今度は「$0101$」という4桁の2進数を例として補数について考えていきたいと思います。
この数は4桁の数です。そして、4桁で表現できる最大の数は「$1111$」になります。
$0101$が4桁の数$1111$になるためにいくつ数を補ってあげればよいか、これが1の補数に当たる数です。
以下の数式では1の補数と$0101$の関係を表現しています。
\begin{align}
0101+(1の補数)&=1111\\
(1の補数)&=1111-0101\\
&=1010
\end{align}
この式より、$0101$の1の補数は$1010$と求められました。
次に、$0101$という4桁の2進数にある数を加えて桁が繰り上がるときのことを考えてみましょう。
4桁の2進数が繰り上がって5桁になるときの数は「$10000$」になります。
$0101$が5桁の数$10000$になるためにいくつ数を補ってあげればよいか、これが2の補数に当たる数です。
以下の数式では2の補数と$10000$の関係を表現しています。
\begin{align}
0101+(2の補数)&=10000\\
(2の補数)&=10000-0101\\
&=1011
\end{align}
この式より、$0101$の2の補数は$1011$と求められました。
8ビットの2進数「00000101」の負の数を求める
こうして、10進数や2進数の補数表現が可能になったわけですが、
ここで、改めて先ほど表で示した8ビットの2進数「$00000101$」について考えてみましょう。
まず、8ビットの2進数「$00000101$」の2の補数を求めてみます。
これまでと同様に、8桁の2進数が繰り上がって9桁になるときの数は「$100000000$」になります。
「$00000101$」が9桁の数「$100000000$」になるためにいくつ数を補ってあげればよいか、これが2の補数に当たる数なので、
\begin{align}
00000101+(2の補数)&=100000000\\
(2の補数)&=100000000-00000101\\
&=11111011
\end{align}
この式より、「$00000101$」の2の補数は「$11111011$」と求められました。
(補足) 2進数の2の補数の簡単な求め方
「2進数」① 2進数の足し算と引き算で紹介させていただいたような形で、2進数の2の補数を求めることが出来ないというわけではないのですが、桁が増えるにつれて筆算ではかなり面倒になってきます。
そこで、2進数の2の補数を簡単に求める方法をご紹介したいと思います。
先ほどの8ビットの2進数「00000101」を例としてみます。
まず、この2進数の各桁の値に着目します。
各桁はそれぞれ0か1になっていますが、「桁の値が0ならば1、1ならば0」というようにすべて反転させてしまいます。
こうして、各桁の0と1を反転させた「11111010」という数が得られます。
さらに、この数に1を加えます。すると、「11111011」という数になります。
実は1を加えたこの値が2進数の2の補数になっています。
元の数 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 |
---|---|---|---|---|---|---|---|---|
0と1を反転させた数 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 0 |
↑にさらに1を加えた数 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | $1$ |
それでは2進数「$00000101$」に2の補数「$11111011$」を加えてみます。
当然の話なのですが、この2つの8桁の数を足すと、9桁に繰り上がった「$100000000$」という数になります。
これらの2つの数とその和を下の表に示しています。
桁数 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | |
---|---|---|---|---|---|---|---|---|---|---|
①2進数 | $0$ | $0$ | $0$ | $0$ | $0$ | $1$ | $0$ | $1$ | ||
②2の補数 | $1$ | $1$ | $1$ | $1$ | $1$ | $0$ | $1$ | $1$ | ||
①+② | $1$ | $0$ | $0$ | $0$ | $0$ | $0$ | $0$ | $0$ | $0$ |
ここで、一番下の行について注目してください。
9桁目は「$1$」という値になっているのですがそれ以外の1〜8行目についてはすべて「$0$」になっています。
つまり、1〜8桁目に関しては、絶対値が等しい正の数と負の数を足すという計算の結果として期待されていた「全ての桁が0になっている」状態になっています。
したがって、コンピュータで2進数の負の数を表現するためには、「2の補数」を使えば良いということがわかりました。
「2の補数」を用いた2進数の引き算の流れ
それでは実際に2進数の引き算の流れについて考えてみましょう。
(正の数)ー(正の数)の引き算
ここでは例として、4ビットの数「$0110$」(10進数で$6$)から「$0100$」(10進数で$4$)を引いてみます。
まずこの引き算を単純な式として表すと以下のようになります。
0110-0100
コンピュータはこのままでは計算することができないので、「$0100$」の負の数を2の補数を使って表現します。「$0100$」の2の補数は「$1100$」なので、この値を「$0110$」に足す式に置き換えて計算します。
\begin{align}
0110-0100&=0110+(-0100)\\
&=0110+1100\\
&=10010
\end{align}
すると、「$10010$」という5桁の2進数が得られました。
先ほどの説明の通り、5桁目の「$1$」は繰り上がった数なので、この引き算においては無視します。
残りの4桁の値に着目すると、「$0010$」という数になっています。この値は10進数で「$2$」であるため、正しい答えが得られたということになります。
(負の数)ー(正の数)の引き算
もう1つの例として、負の数から正の数を引くというパターンの引き算について考えてみましょう。
例として、「10進数で$-5$の大きさの2進数」から「10進数で3の大きさの2進数」を引いてみたいと思います。このときの2進数の桁数は4桁とします。
まず、それぞれの数を4桁の2進数にしてみます。
「10進数で$−5$の大きさの2進数」について、これは負の数なので同じ絶対値の正の数である「$5$」を4桁の2進数にしてから2の補数を用いて負の数「$-5$」として表現したいと思います。
正の数「$5$」の2進数の値は「$0101$」なので、2進数の2の補数を使って負の数「$-5$」を表現すると「$1011$」となります。
今度は、「10進数で3の大きさの2進数」についてですが、この数は「$0011$」となります。
この2つの2進数を使って引き算を表すと以下のようになります。
1011-0011
この場合でもやはりコンピュータは計算することができないので、「$0011$」の負の数を2の補数を使って表現します。「$0011$」の2の補数は「$1101$」なので、この値を「$1011$」に足す式に置き換えて計算します。
\begin{align}
1011-0011&=1011+(-0011)\\
&=1011+1101\\
&=11000
\end{align}
すると、「$11000$」という5桁の2進数が得られました。
先ほどの説明の通り、5桁目の「$1$」は繰り上がった数なので、この引き算においては無視します。
残りの4桁の値に着目すると、「$1000$」という数になっています。
この数が答えとなりますが、10進数に変換する際にある問題が生じます。
どういうことかを理解するために、元の10進数の数の計算に立ち返ってみます。
10進数で$-5$の大きさの2進数から、10進数で3の大きさの2進数を引くということなので、10進数で考えるのであれば答えは当然「$-8$」となるはずです。
しかし、2進数の引き算で計算した結果を見てみると、繰り返しになりますが「$1000$」となっています。この数をそのまま10進数にすると「$8$」になってしまい、期待する答えと符号が入れ替わってしまっています。
2の補数と符号ビットの関係
このようなことがなぜ起きるのかというと、符号を定義していないことが原因として上げられます。
今回の計算では4桁の2進数を扱いましたが、4桁目を符号ビット(1なら負、0なら正)として扱っていれば、この問題を未然に回避することが出来たのです。
先ほどの引き算に戻り、4桁の2進数のうち4桁目を符号ビットとすると、2進数と2の補数は以下の表のようになります。
$-8$ | $-7$ | $-6$ | $-5$ | $-4$ | $-3$ | $-2$ | $-1$ | $0$ | $1$ | $2$ | $3$ | $4$ | $5$ | $6$ | $7$ |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1000 | 1001 | 1010 | 1011 | 1100 | 1101 | 1110 | 1111 | 0000 | 0001 | 0010 | 0011 | 0100 | 0101 | 0110 | 0111 |
この表の正の数について着目すると、表現できるのは「$7$」までとなっています。
4桁なので、10進数で「$8$」の大きさの2進数「$1000$」を表現することはできるはずです。
しかし、これは「4桁目を符号ビットとする」というルールと干渉してしまうため、「$1000$」は正の数ではなく負の数であるということが確定します。
したがって、先ほどの引き算では答えとして「$1000$」という数が得られましたが、これは10進数で「$8$」になる2進数ではなく、「$-8$」を表した2進数の2の補数なのです。
参考にさせて頂いた書籍
きたみりゅうじ 『キタミ式イラストIT塾 基本情報技術者平成31/01年』 技術評論社 2019年
学習してみて
記事で力尽きてしまったのであまり書けませんが、「2の補数を簡単に作る方法」これはぜひ覚えておきたいですね。
最後の部分「2の補数と符号ビットの関係」がなかなか難しく苦労しました。
ありがとうございました。