矢沢 久雄

 今回は,2進数で負の値を表す「補数表現」と,同じ値のまま2進数のビット数を増やす「符合拡張」を説明します。紙の上ならマイナス記号を使って「-1011」のような負の2進数を表すことができますが,コンピュータにはできません。デジタルICの構造上,0と1の数字を表せても,マイナス記号を表す手段がないからです。0と1だけで,どうやって負の値を表すかが前半のテーマです。後半では,8ビットから16ビット,16ビットから32ビット,というように,2進数のビット数を増やす方法がテーマです。単に上位けたを0で満たせばよいと思われるかもしれませんが,それだけではダメなのです。

足し算で引き算ができる

 2進数の0と1だけを使って負の値を表すにはどうしたらよいか? いきなりですが,答えを言ってしまいましょう。負の値を正の値で表すのです。「そんなバカげたことができるのか?」と思われるかもしれませんが,できるのです。2進数で説明する前に,皆さんが使い慣れた10進数で具体例を示しましょう。

図1●補数を使えば負の値を正の値で表せる
 5-2という計算を例とします。-2の部分は引き算ですが,マイナス2を足す,と考えることもできます。すなわち5-2=5+(-2)です。驚かれるかもしれませんが,-2を正の値で表すと8になるのです。すなわち,5-2=5+(-2)=5+(8)ということになります。皆さんは,「5-2=3だが,5+(8)=13で,答えが違うじゃないか!」と思われることでしょう。ここで,1つルールがあります。それは,けた上がりを捨てる,ということです。5+(8)=13ですが,けた上がりを捨てれば13は3となります。これで,5-2と同じ結果が得られますね。したがって,-2を正の値で表すと8なのです。このように,負の値を正の値で表したものを「補数(ほすう)」と呼びます(図1)。

 狐につままれたような話かもしれません。けた上がりを捨てるというルールが,実用的でないと思われるかもしれません。しかし,コンピュータにとって,補数は,実に都合のよいものなのです。

図2●けた上がりを捨てるのは,コンピュータにとって好都合
 例えば,8ビット単位でデータを取り扱うコンピュータがあったとしましょう。10進数の5-2という引き算を2進数8ビットで表すと,00000101-00000010となります。00000101が5で,00000010が2です。-00000010を補数で表すと11111110になります(補数の求め方は,後で説明します)。00000101-00000010を00000101+11111110として計算してみてください。けた上がりして9ビットの100000011になりますね。8ビット単位でデータを取り扱うコンピュータを使っているのですから,9ビット目は物理的に格納できないので,消えてしまいます。すなわち9ビットの答え100000011は,8ビットの00000011になります。2進数の00000011は,10進数の3です。確かに5-2の答えになっているでしょう(図2[拡大表示])。

 図2で,00000101+11111110を計算する方法は,分かりますね。10進数の足し算の場合と同様に,下位けたから足していって,その結果を下に書けばよいのです。1と1を足す場合には,次のけたにけた上がりすることに注意してください。

 「負の値を正の値で表すことで,マイナス符号は不要となったが,プラス符号はどうするのだろう?」と思われるかもしれません。プラス符号は使わないのです。10進数でも,+123と123は同じ意味でしょう。

補数の求め方

 10進数の補数の求め方を説明しましょう。10進数の-2を補数で表すと8になったことから予測できるとおり,足してけた上がりする値が補数です。2+8=10になりますね。10-2=8だから,-2の補数は8だと考えてもOKです。

図3●補数は,もとの数をもらって桁上がりする数
 -2と8の関係から,補数の理屈を説明すれば,「5-2という計算において,5から2を引くということは,5に8を足すことと同じ」,「なぜなら,8を足すと,8は5から2をもらって(引いて)10にけた上がりする」,「そのけた上がりを捨ててしまえば,残った3が答えになるから」となります。ご理解いただけたましたでしょうか(図3[拡大表示])。

 2進数でも考え方は,同じです。2進数の補数表現のことを「2の補数」と呼びます。簡単な例をあげてみましょう。8ビットで-1を表してみます。-1の補数は,1をもらって9ビット目にけた上がりして(9ビット目にけた上がりすれば,自動的に消えてなくなります)100000000になる8ビットの正の値でから,11111111となります。

 それでは,-100を補数で表すとどうなるでしょう。10進数の100は,2進数で01100100です(ここでは8ビットで考えているので,7ビットで1100100と表せる数であっても上位けたに0を入れます)。01100100をもらって9ビット目にけた上がりして100000000になる8ビットの正の値は,いくつでしょう? ちょっと頭が混乱しちゃいますね。

図4●「反転して1」で補数を求められる
 こう考えてください。2進数では,0と1だけを使います。01100100(10進数の100)の0と1を反転させた10011011と,01100100を足すとすべてのビットが1になり,11111111となりますね。11111111に1を加えると,9ビットの100000000になります。すなわち,2進数の補数は,もとの数の0と1を反転させたものに1を加えれば求められるのです。理屈がわかったなら,補数の求め方を「反転して1」と呪文のようにおぼえてください(図4[拡大表示])。

 補数のことを考えると,1つの値が正の値を表しているのか,それもと補数で負の値を表しているのかが,区別できなくなっちゃいますね。例えば,11111111が正の値だとすれば255です。補数だとすれば-1です。そこで,約束があります。2進数の最上位けたが0なら正の値を表し,1なら負の値を表しているとみなすのです(この約束に従うかどうかは,プログラムを作るときに,変数のデータ型で指定できます)。最上位けたが正負の符号を表していることになるので,最上位けたのことを「符号ビット」と呼びます。この約束に従えば,11111111は,-1を表していることになります。

 それでは,問題です。補数で表された2進数の10000001を10進数で表すと,いくつでしょうか? 符号ビットが1なので,負の値であることはわかりますね。それでは,その絶対値は...答えは簡単です。負の値の負の値は正の値になりますね。マイナスをマイナスすれば,符号が反転してプラスに戻ります。例えば,-2の負の値は2です。10000001の絶対値を知りたかったら,10000001の補数を求めて負の値を正の値にしてみればよいのです。やり方は,やはり「反転して1」です。

 10000001を「反転して1」すると,01111111になります。01111111は,10進数で127です。したがって,10000001の符号はマイナスで,絶対値は127であることがわかります。10000001は,10進数で-127です。「補数の補数を求めれば,絶対値が分かる」と覚えてください。

シフト演算

 「演算」とは,データに何らかの操作を行うことです。コンピュータでは,算数の四則演算だけでなく,論理演算やシフト演算なども使われます。ここでは,シフト演算をマスターしておきましょう。論理演算に関しては,この講座の第4回で説明します。

 シフト(shift)とは,「ずらす」という意味です。シフト演算では,データを2進数で考えて,けたを左右にずらします。上位けたにずらすことを「左シフト」と呼び,下位けたにずらすことを「右シフト」と呼びます。何けたすなわち何ビットずらすかを「シフト数」と呼びます。例えば,00001100というデータを3ビット左シフトすれば01100000となり,2ビット右シフトすれば00000011となります。簡単ですね!

 2進数の左シフトは,1ビットシフトするごとに,もとの値が2倍になります。右シフトは,1ビットシフトするごとに,もとの値が2分の1になります。これは,10進数を1けた左シフトすれば10倍になり,右シフトすれば10分の1になるのと同じ理屈です。このことから,シフト演算を掛け算や割り算の代わりに使うこともあります。

 左シフトする場合は下位けたに空きビットが生じ,右シフトする場合には上位けたに空きビットが生じます。ここまでの説明では,空いたビットに何気なく0を入れてきましたが,それでは問題が生じる場合があります。最上位ビットが1となった負の値の右シフトの場合です。例えば,11000000という負の値を1ビット右シフトして,上位けたに0を入れてみましょう。01100000となり,符号ビットが0なので正の値となりますね。1ビット右シフトすることは,もとの値を2分の1にすることです。11000000(10進数で-64)を2分の1にした結果が01100000(10進数で96)というのは,なんともおかしな話です。

図5●右シフトの場合は,上位ビットに符号ビットを入れる
 この問題は,右シフトの場合は,空いた上位けたに,もとの数の符号ビットを入れるという方法で解決できます。この方法に従えば,11000000という負の値を1ビット右シフトした結果は,11100000となり,10進数で表せば-32となります。ちゃんと-64÷2=-32になっていますね(図5[拡大表示])。

 シフト演算には,2種類の形式があるのです。左シフトでも右シフト空いたけたには0を入れる「論理シフト」と,左シフトで空いた下位けたには0を入れ,右シフトでは空いた上位けたに符号ビットの値(0または1)を入れる「算術シフト」です。左シフトでは,論理シフトと算術シフトの結果に違いはありませんが,言葉として使い分けられます。

図6●論理シフトでは,データを単なるビットの羅列と考える
 算術シフトは,シフトを掛け算や割り算の代用とするときに,算数として正しい結果が得られるから「算術」と呼ばれるのです。論理シフトは,データが数値ではなく,単なるビットの羅列であるとみなすときに使われるので「論理」と呼ばれるのです。ICの8本のピンの先に電球を付け,データをシフトすることで,電球の光が流れているように見せる場合などには,論理シフトを使うことになります(図6[拡大表示])。

符号拡張

 なぜ,算術右シフトの場合は,上位けたに符号ビットを入れることで,正しい結果が得られるのでしょう。正の値の場合なら符号ビットは0なので,上位けたに0を入れていくことで正しい結果が得られることは,納得できますね。ところが,負の値の場合に,上位けたに1を入れていくことは,何とも不思議です。符号ビットを1に保つことは納得できても,複数ビット算術右シフトする場合に,どんどん1を入れて行って問題ないのでしょうか?

 冷静に考えてみれば,不思議でも何でもないのです。補数で表した負の値の絶対値を求めるためには,負の値の負の値,すなわち補数の補数を求めました。補数を求める方法は,「反転して1」です。もしも,算術右シフトで,空いた上位けたに0を入れるとしたら,それを反転すると1になってしまいます。この1が10進数でいくつになるかは,けたによって違いますが,ゼロでないことは確かです。したがって,0を入れたのでは絶対値がおかしくなってしまいます。それに対して,空いた上位けたに1を入れれば,それを反転すると0です。この0がどのけたにあっても,値はゼロなのですから,絶対値の大きさに影響が与えられないというわけです。

 算術右シフトで上位けたに符号ビットを入れればよいことが分かると,「符号拡張」も理解できます。符号拡張とは,8ビットのデータを16ビットに拡張したり,16ビットのデータを32ビットに拡張することです。8ビットCPU用のデータを32ビットCPUで処理するときなどに使われるのだと思ってください。

図7●符号拡張では,上位桁を符号ビットで満たす
 符号拡張でも,負の値に注意しなければなりません。正の値の場合は,拡張する上位けたを0で満たすだけですが,負の値の場合は,上位けたを1で満たします。これによって,ビット数が拡張されても,もとの値と同じ値にすることができます。正の値の場合でも,負の値の場合でも,もとの値の符号ビット(0または1)で上位けたを満たすことから「符号拡張」と呼ばれるのです。もとの値の符号ビットを上位けたに「グ~ン」と延ばすイメージです。8ビットの値を16ビットに符号拡張する例を図7[拡大表示]に示しておきます。

 データ表現というテーマは,堅苦しくてつまらないように思われたかもしれませんが,補数表現や符号拡張の仕組みは,けっこう面白いでしょう。こういうことを面白いと感じる人は,IT業界に適正があると思いますが,いかがでしょう。次回は,2進数で小数点数を表す方法を説明します。お楽しみに!