整数のなかみ(前編)

 これから先「ポインタ」というものを見ていこうと思うのですが、そのためにまず「変数ってなんなの?」ってことをはっきりとさせておくべきかなと思って、変数の中でも特に重要であろうと思われる整数値に絞って解説してみました。

コンピューターのデータと各用語
オセロのコマ  ちょっと大変ですが、コンピューターそのものの話と、用語について。
 コンピューターのデータはすべて0と1で表されます。これはオセロのコマの裏表のようなものだと考えてください。データを蓄える場所=メモリには、この「オセロのコマ」がいっぱい並んでいて、それぞれが「裏」や「表」になっていて、データが格納されているというようなイメージを浮かべると、分かりやすいと思います。

 このコマひとつを「ビット」という単位で表します。コマの裏表をで表すと、1ビットはこの2種類の状態を持つことになります。
 これを並べて、例えば「0011」であれば、4桁なので4ビットになります。一桁増えるごとに倍のデータを格納できるようになり、全体では「2の桁数乗」のデータを格納できます。実際にオセロのコマを並べてみて、何通りの組み合わせがあるか、確かめてみてください。4ビットなら4桁なので、2の4乗=16通りの組み合わせができるはずです。これだけの数、データを格納できるということです。
 またVC5.0等で使われる型でよく「32ビット値」というものが出てきますが、これはこの1と0(つまりビット)が32桁並んでひとまとめという意味です。このように、ビットとはどれだけの数のデータを格納できるかということを表す単位なのです。

 ビットは「2進数」です。つまり「2になったら一桁繰り上がり、元の桁は0になる」ということです。普通日常生活で使う「10進数」は、10になったらひとつ繰り上がります。これと比べてみてください。例えば「0101」に1を足せば、一番右の位が「1+1=2」になって「繰り上がり+0」になり、「0110」という結果になります。

16進数とバイト  ビットはあまりにも単位が小さいので、普段は使用しません。普段は次のふたつの量を使って計算します。
 ひとつは「16進数」です。16進数はその名の通り「16になったら一桁繰り上がる数」です。1から9までは10進数と同じですが、10から15をAからFで表します。ホームページの色指定に使うので、結構なにげに使ってる人もいるでしょう。
 16進数は一桁で4ビットの値を格納できます。それだけ、大きな値を表すことができるというわけです。
 C言語では「普通の10進数じゃなくて16進数ですよー」と示す場合には0x」を付けます。例えば「0x5b」みたいな感じです。ちなみにアルファベットは大文字でも小文字でもOK。あと当然プログラミングでは半角英数でね。
 この「ビット−16進」変換はめんどいですが、実は簡単にできます。アクセサリの「電卓」を実行して、「電卓の種類」を「関数電卓」にしてください。電卓が大きくなりましたね。左上のボタンが「10進」になっているのを確認して数字を入力。そのあと左隣の「16進」にすればあーら簡単、10進数が16進数に変換されちゃいました。簡単ですねー。逆ももちろんOK(アルファベットは数字キーの下のA〜Fを使って入力してください)。とりあえず「テストに出る」とかじゃないのなら、これを使うのが一番手っ取り早いでしょう。

 さて、もうひとつの量は「バイト」です。バイトは16進数で2桁、つまり8ビットを表します。10進数だと「2の8乗=256」種類のデータを格納できるということになります。
 C言語のすべてのデータ型はこのバイト単位で計算します。最小単位のchar型は1バイトですし、malloc()等で取得するバッファサイズも、バイト単位で指定しますし、sizeof()で返ってくる変数のサイズも、バイト単位です。
 基本的に、プログラミングで使うのはこのみっつ、「ビット」「バイト」「16進数」です。

整数の中身
 さて問題です。−1と4294967295は等しい? ま、結果は次のコードを試してみれば一目瞭然でしょう。


	if( 4294967295 == -1 )
		TRACE( "Hit(4294967295 == -1)\n" );	//ヒットです、これ。
	

 なんでこんなことになってしまうんでしょうか。ま、これはかなり極端な例ですが、C言語での「データの扱い」というものを的確に表していると言えるでしょう。とゆーわけで、比較的よく使う「整数」というものについて見てみましょう。

 整数値の場合、ビットの並びをそのまま整数として数えます。例えば「0000」は「0」、「0001」は「1」、「0010」は「2」といった感じです。この数え方でいくと、この4ビットのデータは1〜15まで表せることになりますね。基本はこんな感じです。
 ウィンドウズで使われている整数値は「32ビット」なので、「2の32乗=4294967296」個の数字が入ることになります(実際には429496729までです。0の分があるから)。が、これだと負の数、つまりマイナスを表せません
 ここで出てくるのが「signed」と「unsigned」です。「signed」は符号の付いた整数値、「unsigned」符号ナシ整数値です。

 上で書いた「4294967295」の数字が当てはまるのが「符号ナシ整数値」です。符号ナシ整数値は32ビットのデータすべてを数字に使うことができるので、0から4294967295までの数字を表すことができます。
 C言語では、この符号ナシ整数値の型をunsigned intと書きます。でもVCではUINTの方が見慣れてるでしょう。このふたつは同じものです(WINDEF.Hを見てね)。
 では、この符号ナシ整数の変数の中はどんな風になっているのでしょう。次のコードを試してみてください。


	UINT	uiTest1 = 4294967295;
	
	TRACE( "%d, %u, %X\n", uiTest1, uiTest1, uiTest1 );
	//	-1, 4294967295, FFFFFFFF
	

 TRACE()で使う書式はprintf()の「書式指定」の項目を見てください。
 さて、その書式指定で「%X」を指定すると、変数の中身を16進数の形で表示します。この結果はすべてF。0から数えていって、一番大きい数字を表すわけですから、当然ですね。
 右から2番目の「%u」は、変数の中身を符号ナシ整数値とみなして表示します。UINTはその通りのものなので、結果は同じ4294967295が出ます。
 さて、一番左の「%d」。これは符号あり整数値とみなして表示するというものです。つまりこれは「FFFFFFFF」というデータがあったとき、符号あり整数値では−1という意味だということなのです。

 では、ここから「符号あり整数値」について見てみましょう。
 符号あり整数値の場合には、32ビットのうちたったひとつを符号のデータとして使います。一番上のビットが0の時にプラス、1の時にマイナスとすることで、符号の付いた整数値も扱えるようになります。
 このため、数字としては1ビット減って31ビット個しか使えなくなってしまいます。たった1ビットですが「2の31乗=2147483648」と大きく減ってしまいます。その分マイナスの数字も使えます。範囲としては「+2147483647〜−2147483648」ということになります(プラスの方が1少ないのは、同じく0の分があるから)
 C言語では、デフォルトの設定では「何も付けないとsigned」なので、int型が「符号あり整数値」の型ということになります。はっきりと「符号あり」ということを示すときには「signed int」という型にします。
 さて、ちょっとここで脇道に逸れて「2の補数」というものについて見てみましょう。

マイナスと「2の補数」
2の補数の作り方  16進数で「マイナス」を表すときには、一番上のビットを1にするだけではできません。その証拠に、−1はFFFFFFFFです。この特殊なマイナスの数字は「2の補数」という方法を使って求めます。
 「2の補数」の言葉の意味は考えなくていいです。単に方法だけ憶えておけばいいでしょう。
 方法は簡単。「ビットを反転し、1を足す」だけ。例えば「0101」(5)なら、ビットを反転して「1010」、これに1を足して「1011」。これが「−5」を表します。簡単ですねー。ビット数は違いますが、「0001」なら「1111」、つまり0xFとなります。これで−1がFFFFFFFFになる理由が分かりましたね。

 なんでまたこんなめんどくさいことをするのかというと、2の補数を使うと「足し算で引き算ができる」からです。んなバカな、と思うかもしれませんがちゃんとできてしまうのです。
 例として「7−4=3」を計算してみましょう。7は「0111」、4は「0100」になります。ここで4の「2の補数」は「1100」となり、これが「−4」を表します。この時点で、式は「7+(−4)」となります。これを計算すると「10011」、一番上の1を無視して「0011」、つまりになったというわけです。
 なんでまたこんなことをするのかというと、理由はふたつあります。ひとつは「引き算をしなくていい」ということ。足し算とビット反転さえできればコンピューターの中にわざわざ「引き算の回路」を作らなくて済むというわけです。もうひとつは「繰り下がりを気にしなくていい」という部分があります。繰り下がりは繰り上がりに比べて面倒なので、しない方が楽に済みます。
 まぁ、「こういうことができるから2の補数をマイナスにしよう」ということでもあるし、この辺はむかーしのコンピューターの名残だったりするので、「マイナスは2の補数でできてるんだ」ってことを感じていればいいでしょう。単にマイナスの値を求めるのであれば、実際にこの方法を使わずに、さっき書いた「計算機」を使えば簡単に出せますし。

 ちなみに「なんで足し算で引き算ができるんだよーっ!!」という人のために、簡単な例で種明かしをしましょう。
 実は、「2の補数を作る」ということは、10進数なら「10の桁数乗から引く」ことをなのです。たとえば「3」は「10−3=4」が、「57」なら「100−57=43」が、それぞれの2の補数になるというわけです。
 同じく2進数の場合、「01」なら「100−01=11」、「0101」なら「10000−0101=1011」が2の補数になります。でも「一桁多い数字」を作るのはめんどくさいので、「10000=1111+1」のようにふたつに分けます。さらに「1111」から2進数を引くと、必ず「引いたものの反転」という結果になります。この結果から、2進数の場合「反転して1を足す」ことが、「10の桁数乗から引く」ことと一致することが分かります。
(この辺の解説が間違っていたので訂正しました)

 さて次に、確認のために「10進数」で2の補数を使った引き算を実行してみましょう。同じく例は「7−4」にします。
 4の「2の補数」をまず作ってみましょう。この場合「4」は一桁ですから、10進数では「10×1から引く」ことで求まりますので、「10−4=6」が「4の2の補数」ということになります。
 この変換で、式は「7+6=13」、上の位を消して「3」、見事に答が出たということです。

2の補数の計算・ビーカー編  これを、簡単な例と図を使って説明しましょう。
 「10」の目盛りが書かれたビーカーがふたつあったとします。Aには「7」まで水が、Bには「4」まで水が入っているとします。さて、水の量の差はいくらでしょうか。
 これは単に、水位の差を調べれば簡単に分かりますね。水の高さの差は3、つまり答は3ってことになります。
 さて、では足し算を使ってこの差を求めてみましょう。まずなんらかの方法を使って「Bの水位から10の目盛りまで」と同じ量の水を用意します。これが「2の補数の作製」にあたります。
 この「Bの水位から10の目盛りまで」と同じ量の水(Cとします)をAに入れます。10の目盛りを超えることになるでしょう。で、実はこの「10の目盛りから水位まで」が、答、つまり「水位の差」と一致するのです。これで、足し算で引き算ができてしまったことになります。
 なぜこの差が足し算で求まってしまうのでしょう。もういちどBのビーカーを見てください。Cの水を作った領域はふたつに分けられます。ひとつは「10の目盛りからAの水位」までの領域、もうひとつは「Aの水位からBの水位(つまりこれが答)」までの領域です。
 CをBに入れた時、「10の目盛りからAの水位」までの領域の水は、当然Aの方も空なので、そのまますっぽり入ってしまうことになります。ということは、これだけでAは10の目盛りまで満たされることになります。で、そこに「Aの水位からBの水位」=答が入ります。この水の水位は10の目盛りからなので、これを計れば答が出てくるというわけです。

 これと同じことを2進数でも行うことで、足し算と2の補数を使って引き算ができてしまうのです。ただその中で、「反転して1を足す」というテクニックを用いている部分がちょっと違うくらいです。
 なーんか腑に落ちないかもしれません。現実的にはCの水を作ること自体が難しいんですが、コンピューターの世界では「ビット反転」は簡単にできてしまうので、この方法が採られるというわけです。

つづく
 ちょっと長くなっちゃたので、次回に続きます。

(C)KAB-studio 1998 ALL RIGHTS RESERVED.