情報基礎A 「Cプログラミング」(ステップ2・補足・数の表現)
このページでは、コンピュータが数値をどのように扱っているかについて補足する。
0. 2進数と16進数
ディジタル機器の内部では、電圧の高低など、区別可能な2つの状態の組み合わせによって処理が行われているから、 状態を2進数に対応付けるのは自然な発想と言えるし、事実、コンピュータ科学では「二進数でモノを考える」場合が多い。
0と1の並びを2進数の表現と考えると、例えば 10101011
は
$$
1 \times 2^7 + 0 \times 2^6 + 1 \times 2^5 + 0 \times 2^4 + 1 \times 2^3 + 0 \times 2^2 + 1 \times 2^1 + 1 \times 2^0
$$
を表し、10進では171となる。このとき、一番右側の桁($2^0$を乗じている桁)はLSB(Least Significant Bit)と呼ぶ。
反対に、最上位の桁はMSB(Most Significant Bit)である。
大きな数を二進数で表すと桁数が多くなって、人間にとっては扱いづらい。
そこで、2進数と相性の良い16進表示がよく用いられている。
2進数を16進数に変換するには、4桁ずつをまとめて、それぞれ16進数の桁に対応させればよい。
例えば、2進数 10101011
を 1010 1011
に分けると、上の桁1010
は(10進で)10、下の桁1011
は(10進で)11となる。
2進数の各桁を表現するのに0,1の2つの記号、10進では0から9までの10個の記号を用いるように、16進数を表すには16個の記号が必要となる。 そこで9より上をアルファベット文字に対応させて
2進 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111 10進 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16進 0 1 2 3 4 5 6 7 8 9 A B C D E F
と表すのが一般的である。すると、10101011
の16進表現は AB
ということになる。
1. 整数の表現
符号あり整数(int型)
整数を表現するには int型を用いる。int型は通常32ビット(2進数で32桁)で、その場合、表現できる数の範囲は、-2147483648〜2147483647 である。 数値とビットの配置の対応は、正の数については、直感されるのと同様
数値 ビットのパターン 0 00000000000000000000000000000000 1 00000000000000000000000000000001 2 00000000000000000000000000000010 3 00000000000000000000000000000011 4 00000000000000000000000000000100 5 00000000000000000000000000000101 ... 2147483647 01111111111111111111111111111111
である。正の数はMSB(最上位のビット)が0である。
一方、負の数は
数値 ビットのパターン -1 11111111111111111111111111111111 -2 11111111111111111111111111111110 -3 11111111111111111111111111111101 -4 11111111111111111111111111111100 -5 11111111111111111111111111111011 ... -2147483647 10000000000000000000000000000001 -2147483648 10000000000000000000000000000000
のように表現され、MSBがいつも1となる。
この負数の表現方法は、以下に述べる考え方に基いている。
ある整数 $a$ の符号を反転させた数を $b \; (=-a)$ とすると、当然 $a+b=0$ が成り立つべきである。上記のビットのパターンはまさにそのように構成されており、例えば $a=1, \ b=-1$とすると
a= 00000000000000000000000000000001 b= 11111111111111111111111111111111 a+b= 00000000000000000000000000000000
となっている(繰り上がりの桁は、表現可能な桁からはみ出してしまうので、実質、「無かった」ことになる)。 このような負数の扱い方2の補数による表現と呼ぶ。 上記の例から明らかなように、$a$ の符号を反転するには、$a$ のビットを全て反転した数にさらに1を加えた数を $b$ とすればよい。
符号付きの整数を扱う場合は、表現できる数値の範囲の他、「予期せぬ」挙動にも注意が必要である。
例えば、2147483647 * 2
の結果は-2
となる。なぜなら、2147483647
のビットパターンをひとつ左にシフトする(2倍する)と、それは-2のパターンに一致するからだ。同様に(-2147483647)*2
は2
になる。
その一方で、(-1)/2
は、ビットパターンを1つ右にシフトした値2147483647
となるわけではない(結果は0である)。
演算の結果が、表現できる範囲の境界をまたいでしまわない限り、符号を含め、値は正しく評価される(そうでないと困る・・)。
符号無し整数(unsigned int型)
非負の整数のみを扱う場合には、unsigned int 型を用いれば、有効な桁を無駄無く使い切ることができる。 以下を実行すると、結果は正しく 4294967294 と表示される。
printf( )関数の書式指定を %u
とする。
#include <stdio.h> main() { unsigned int n=2147483647 ; n=n*2 ; printf("%u\n",n) ; }
2. ビット演算
プログラミングにおいては、整数の値そのものではなく、ビットのパターンを問題とするケースも多い。 そのため、ビット操作のための演算子がいくつか用意されている。
ビット演算 | 動作 |
---|---|
a & b |
aとbのビット毎の論理積(AND) a,bの同じ位置のビットが共に1の場合のみ、結果の対応するビットが1 ビット毎に、$0\&0=0,\ 1\&0=0,\ 0\&1=0,\ 1\&1=1$ |
a | b |
aとbのビット毎の論理和(OR) a,bの同じ位置のビットのいずれかが1の場合のみ、結果の対応するビットが1 ビット毎に、$0|0=0,\ 1|0=1,\ 0|1=1,\ 1|1=1$ |
a ^ b |
aとbのビット毎の排他的論理和(XOR, Exclusive OR) ビット毎に、$0 \wedge 0 = 0, \ 1 \wedge 0 = 1,\ 0 \wedge 1 = 1, \ 1 \wedge 1 = 0$ |
~ a |
aのビットを反転(aの1の補数)した値 |
a << b |
aのビットをbだけ左にシフトした値 |
a >> b |
aのビットをbだけ右にシフトした値 |
上の表で、演算の結果、a,bの値が変化するわけではないことに注意。a + b がaとbの和を与えるが、a,bの値は変化しないのと同じことである。 以下に、ビット演算子を用いた、いくつかのパターンを示す:
整数 a の m ビット目が1かどうかを調べる: if (a & (1<<m)) { 何々 ; }
整数 a の m ビット目を1にセットする: a = a | (1<<m) ;
整数 a の m ビット目を0にセットする: a = a & ~(1<<m) ;
整数 a の m ビット目を反転する: a = a ^ (1<<m) ;
3. float型の実数
実数を近似的に表現するには、数字の並びの決まった位置に小数点があるとみなして、XXXX.YYYYY のように解釈するのが一番簡単である。 これを固定小数点数と呼ぶ。 この方法はその単純さ引き換えに、表現できる数の範囲が(整数型と同様に)限られてしまう。
ところが、実数を使いたいような場面では、大きな数(例えば、光速は $2.9979 \times 10^8$ m/s )や小さな数(プランク定数は $6.626 \times 10^{-34}$ m$^2$ kg/s)がしばしば登場する。 そこで、数値を、仮数部(光速の場合は 2.9979)と、指数部(光速の場合は $10^8$の8)の組み合わせによって表現する方法が一般的である。 そのように表した数を浮動小数点数と呼ぶ。
C言語のfloat型には、IEEE 754方式と呼ばれる浮動小数点数が用いられており、符号に 1 ビット、仮数部に23ビット、指数部に8ビットが割当られている。 日常的には10のべき乗を使って大きい(小さい)数を表現するが、IEEEの方式では2のべき乗が用いられる。 指数部に8ビットが割当られているため、表現できる数の大きさの範囲は、凡そ、$2^{-127}$〜$2^{127}$ となる(大体、$10^{\pm 38}$)。 一方、仮数部には23ビットが割当られているので、有効桁数は10進に換算すると約7桁(弱)になる。
IEEE 754方式では、「数ではない数」が特定のビットのパターンに割当られている。例えば、平方根関数sqrt( )
の引数に負の値を与えると、結果は NaN (Not a Number)となるが、この NaN は、指数部がオール1 (255)で、仮数部が非ゼロのパターンで表現されている。
加えて、正負の無限大も定義されている。
float型では有効桁数や数の範囲が不十分な場合は、double型を用いるとよい。double型は、その名の通り、有効桁数が二倍強、表現できる範囲も拡大(指数部11ビット)されている。