ハードを知らねばソフトは書けない,
コンピュータの中身を楽しく学ぼう

講師 矢沢 久雄

2進数でも同じ,負の値も補数で表す

 コンピュータが実際に使っている2進数の補数を求める方法を説明しよう。ここでは,例として8けたの2進数を取り扱う。16けたでも32けたでも考え方は同じである。

 00110101という2進数の補数は,この値に補数を加えると9けた目にけた上がりする値となる。この値は00110101の「0と1のパターンを逆にして1を加えたもの」として求められるが,感覚的に理解してもらえるだろうか。00110101の0と1のパターンを逆にした値は11001010であるが,この時点で2つの値を加えると1111111のようにすべてのけたが1である値になる。従って,パターンを逆にした値に1を加えた値にすれば,11111111に1を足すことになるので,順番にけた上がりしていって,100000000のように9けた目にけたが上がるはずである。

 00110101の反転した値は,11001010。これに1を加えた11001011が00110101の補数になる。この手順は「反転して1加える」と覚えてほしい。実際に足し算してけたがあがるかどうかを確かめてみよう。

00110101・・・もとの値
+11001011・・・反転して1(補数)加える
------------
100000000・・・加えるとけた上がりする

 実は,補数の裏技は,減算を実行するためだけではなく,負の値を表すためにも利用する。

 例えば,01110111 - 00110101という減算は 01110111+(-00110101)と同じなのだから,00110101の補数11001011は-00110101を表していると考えることもできる*9

 ところが,ここで困った問題が生じてしまう。それは,同じ11001011という2進数であっても,それが負の値を補数で表しているのか,それとそのまま正の値を表しているのかが区別できないということである。そこで,1つのルールが取り決められている。

 2進数の最上位けたが1のものは負の値を補数で表したものであり,最上位けたが0のものは正の値を表したものであるとするのである。11001011は,最上位けたが1なので補数で表された負の値であることがわかる。

 このルールに従うと,8けたの2進数で表される数値の範囲がおのずと決まってくる。最上位ビットが0で正の値を表すのは00000000~01111111の範囲であり,最上位ビットが1で負の値を表すのは10000000~11111111の範囲である。2進数の00000000~01111111は,10進数の0~127となる。それでは10000000~11111111は10進数のいくつだろうか。これらの値は,もとの値を反転して1を加えたものである。逆算してもとの値を求めるには,1を引いて反転すればいいのだが,そんな面倒なことは暗算ではできない。

表3●データ型と補数使用の有無
 ここで知恵をしぼってほしい。負の値の負の値は,もとの正の値に戻るはずだ。もとの値を反転して1を加えて負の値としたなら,その負の値を反転して1を加えればもとの値に戻るはずだ。この手順なら暗算でもできる。10000000を反転して1を加えると,10000000となる(偶然同じ値だ)。10000000は10進数で128となるので,10000000は-128を表していることになる。11111111を反転し1を加えると00000001となる。00000001は10進数で1となるので,11111111は-1を表していることになる。従って,2進数の10000000~11111111は,10進数の-128~-1となる。説明が長くなってしまったが,補数を使って負の値を表すというルールの下では,8けたの2進数で-128~127の範囲の値を表せることになる。「補数」および「反転して1加える」を理解してもらえただろうか。

 補数を利用するかどうか,すなわち負の数を表すかどうかは,変数のデータ型によって決まる(表3[拡大表示])。BASICとC言語では,それぞれ実装が異なるので注意してほしい。C言語ではデータ型の先頭にunsignedを指定することで,補数を使わないことが明示的に示されるが,BASICの場合は8ビットのデータ型なら補数を使わず,16ビットのデータ型なら補数を使うという統一性のない仕様となっている。

コード化はプログラマの役目

 引き続きハードウエアの観点から,コンピュータが取り扱うデータについて説明しよう。

 言うまでもないことかもしれないが,コンピュータが取り扱うデータは,内部的にすべて数値となっている。ワープロ・ソフトの文字データも,お絵かきソフトのグラフィック・データも,ゲーム・ソフトの音声データも,すべて数値として処理されているのだ。さて,16進数で41というデータがあったとしよう。この値は,何を表しているのだろうか?

表4●味覚コード
 値だけを見ただけでは,何を表しているかは分かるはずがない。16進数の41という値が,計算に使われる数値を表しているなら10進数の65であり,文字を表しているなら「A」であり,グラフィックを表しているなら「○●○○○○○●」というパターンであろう。もしかすると,データではなくマシン語となったプログラム・コードの一部かもしれない。同じ値であっても,それを処理するプログラム・コードによって様々な種類のデータとして解釈することができる。逆に言えば,プログラマには処理するデータを適切な数値として表す役目があるのだ。

 本来は数値でない文字や色など情報を数値で表すことを「コード化」と呼ぶ。文字であればすでに国際標準となったJISやシフトJISといったコード体系がある。とはいえ,プログラマが独自にコード化をしなければならない場合もある。例えば,食べ物の味を表す「味覚コード」というものを作ってみよう(表4)。ここでは,10進数で味覚コードを表している。

「あまからい」が「苦い」になる?

表5●味覚コード(改善案)
 表4は,決して間違いではない。コードの値の違いから,味覚を区別するという目的は十分に果たしている。しかし,もう少し工夫することもできる。それは,せっかく数値にコード化したのだから,それらを演算した結果を意味のあるものにすることだ。

 例えば,「あまい」と「からい」を表すコードを加算してみよう。「あまからい」という意味合いの値になることを期待したいところだが,

「あまい」+「からい」= 1 + 2
= 3
=「にがい」
という何ともおかしな結果になってしまう。これを防ぐためには,加算しても影響の出ないコード化をすればよい。表4の味覚コードでは10進数を使っているが,これを2進数で表すと加算しても影響の出ないコード化が容易に行える。このように味覚コードを改善してみよう(表5)。ここでは,8けたの2進数を使っている。

 加算しても影響の出ないコードを10進数で考えるのは難しいことだが,2進数で考えればすぐにできる。味覚の種類ごとに1とするけたをずらすのである。このコード化なら

「あまい」+「からい」= 00000001 + 00000010
= 00000011
となり,結果の00000011はその他の味覚を表す値とは一致せず,あらたに「あまからい」という味覚を表すものと解釈することができる。

図6●文字コード“A”と“B”の比較
例えばアルファベット順に並べるなどの用途を想定して,文字列はコード化されている
 これで完璧だ,と喜んでいるようではまだ甘い。もしも,第2案で減算をするなら
「からい」-「あまい」= 00000010-00000001
= 00000001
=「あまい」
ということになってしまう。「からい」ものから「あまい」成分を引いた結果が「あまい」となってしまう。何ともおかしなことだ。

 何かをコード化するのも,コード化したデータをどのように演算するのかを決めるのも,すべてプログラマの役目だ。コンピュータには,プログラマのコード化の意図を知るすべがない。どのようなコード化を行っても,数値として演算するだけである。プログラマにとって重要なことは,どのような演算をするのかに応じて,適切にコード化することだ。

 なお,コード化においては,データの比較を考慮する必要があることもある。例えば,文字データをアルファベット順に並べ替える処理を適切に行うためには,文字データを表わすコードがアルファベット順に大きくなっていなければならない。コード化された情報は,値の大小で比較するしかないからだ。実際に,JISコードにしてもシフトJISコードにしても,並べ替えを考慮したコード化がなされている(図6[拡大表示])。

2進数には論理演算

表7●OR演算の真理値表
表6●AND演算の真理値表
 AND,OR,XOR,NOTなどといった言葉を聞いたことがあるだろう*10。これらは,コンピュータで行える演算の一種で「論理演算」と呼ぶ。演算には,算術演算(四則演算)と論理演算とがあるのだ。ほとんどすべてのプログラム言語は,四則演算を行う演算子と論理演算を行う演算子を提供している。人間にとって,算術演算は10進数で考えた方が分かりやすいが,論理演算は2進数で考えた方が分かりやすい。

 プログラムの中で論理演算が使われる場面には,条件分岐とデータ変換の2通りがある。条件分岐とは,if文を使って「もしも○○なら△△する」といった処理を行うことであり,データ変換とは,8けたの2進数の上位4けただけをすべて0にするといった処理を行うことである。条件分岐に関してはセミナーの第2回で取り上げるので,ここでは論理演算によるデータ変換の方法を説明しよう。

表9●NOT演算の真理値表
表8●XOR演算の真理値表
 まず,論理演算の「真理値表」を覚えなければならない。真理値表とは,論理演算の結果を一覧表示したものである。算術演算は感覚的に理解できても,論理演算は人間の感覚には合わない。そこで,表にして暗記しようというわけである(表6~表9)。

 ここで注意してほしいのは,論理演算の結果は決してけた上がりしないことである。1けた以上の2進数で論理演算を行う場合には,対応するけたで論理演算する(図7[拡大表示])。けた数の異なる2進数の間では論理演算できない。また,NOT演算は2つの値ではなく,1つの値に対して実行するもので,いわば算術演算のマイナス記号のようなものである。まずこれらを,論理演算の大前提として覚えておいてほしい。

図7●8桁の2進数のAND演算
それぞれのけたで,AND演算をすればよい
 覚え方は,真理値表も論理演算の意味を感覚的に理解するのが近道である。すでに承知している読者も多いかも知れないが,念のため確認しておこう。

 演算子は英語の意味をそのまま表している。それぞれを日本語に訳してみよう。ANDは「どっちも」である。2つの入力値が,どっちも1なら結果が1になる。ORは「どちらか」。2つの値のうち,どちらかが1になれば結果は1になる。両方とも1の場合も1だ。XORは「排他的(eXclusive)にどちらか(OR)」。どちらかが1の場合だけ1になる。両方とも1,あるいは両方とも0の場合は0になる。最後のNOTは一番簡単で「否定」である。入力値は1つで,1なら0,0なら1である。

論理演算をデータ変換に利用する

図8●AND演算によるクリア
1のけただけ残る。0とAND演算したけたは0にクリアする
 例によって,ここからが本題である。論理演算をデータ変換でどのように使うのかを説明しよう。論理演算を自在に使いこなせることが,プログラマの必須条件といってもいいだろう。なぜなら,本来数値でない情報をコード化したデータ同士の演算では,論理演算が大活躍するからである。プログラミングの経験を積めばおのずと経験することだろう。順に見ていこう。

 AND演算は,特定のけたの値だけをクリアして0にし,その他のけたの値をそのまま残すときに利用できる(図8[拡大表示])。例えば,01011100という8けたの2進数の上位4ビットだけを0クリアし,下位4ビットをそのまま残すなら,00001111という値とAND演算する。00001111を見て分かるように,0クリアしたいけたを0に,そのまま残したいけたを1にした値を使う,というのがポイントである。

図9●OR演算によるセット
1のけただけ,1になる。0とOR演算したけたはそのまま残る
 OR演算は,特定のけたの値だけを強制的に1にセットし,その他のけたをそのまま残すために利用できる(図9[拡大表示])。これはちょうどAND演算の使い方の逆になる。例えば,01011100という8けたの2進数の上位4ビットだけを強制的に1にセットし,下位4ビットをそのまま残すなら,11110000という値とOR演算を行えばよい。11110000を見て分かるように,1にセットしたいけたを1に,そのまま残したいけたを0にした値を使うことがポイントである。

 XOR演算は,特定のけたの値だけを反転し(図10[拡大表示]),その他のけたをそのまま残すために使われる。例えば,01011100という8けたの2進数の上位4ビットだけをすべて反転し,下位4ビットをそのまま残すなら,11110000という値とXOR演算を行えばよい。11110000を見てわかるように,反転したいけたを1に,そのまま残したいけたを0にした値を使うことがポイントである。

図10●XOR演算による反転
1のけただけ反転する。0とXOR演算したけたはそのまま残る
 NOT演算は,もとの値をすべて反転したい場合に使われる。例えば,01011100という8けたの2進数の補数を求めるには「反転して1を加える」のであるから,(NOT 01011100) + 00000001という演算を行えばよいことになる。NOT演算の結果は11111111とのXOR演算と同様だが,XOR演算が特定のけただけを反転できるのに対し,NOT演算ではすべてのけたを一斉に反転することしかできない(図11[拡大表示])。

まとめと次回予告

 コンピュータのハードウエアの基礎から始めて,ICのピンから2進数へと話を展開してきたが,十分にご理解いただけただろうか。もしもプログラミングの経験が豊富でありながら,不可解なバグに遭遇してしまったなら,ハードウエアや2進数のレベルまで立ち返って,プログラム・コードを見直してほしい。解決の糸口が見つかることも多いはずだ。

図11●NOT演算による反転
すべてのけたが反転する
 最後に,知識の確認の意味を含めて,問題を出すので答えを出してほしい。条件が1つある。必ず暗算で問題を解くこと。

【問題】補数を使って表された
11111111111111111111111111111111
という2進数は,10進数でいくつになるか?

 答えは注釈に示しておこう。

さて次回は,「プログラミング手法」というテーマで,プログラミングの基本的な考え方を説明する予定である。お楽しみに。