「コンピュータの数値表現」の版間の差分
2016年6月8日 (水) 07:15時点における版
コンピュータの数値表現とは、コンピュータシステムにおける数の表現法である。数学的には「数」の概念は複素数など大きく広がっているわけであるが、この記事ではこの冒頭部を除くと、もっぱら固定長の整数の、しかもコンピュータの内部的な事情の話に偏っている(すなわち、数学的な議論は多くない)。実数の近似表現などについては浮動小数点数の記事や任意精度演算の記事を、代数的数のコンピュータでの扱いなどといった話題については、適切な参考文献を参照のこと。
コンピュータに詳しくない人は、コンピュータでの数値計算が無謬(誤りがない)であると誤解していることがある。例えば、{\displaystyle 3\times {\frac {1}{3}}} を計算すると正確に 1 が得られると期待するかもしれない。しかし、実際にはコンピュータや電卓では 0.9999999999999999 のような結果となり、場合によっては 0.99999999923475 のような値になることもある。「実数型」などという概念は、それ自体が誤謬であるとも言える。
後者の値はバグの存在を示しているわけではなく、二進法の浮動小数点数による近似の結果生じるのである。十進法の浮動小数点数、数式処理システム、ある種の多倍長整数系では答えが 1 や 0.9999999999999999... になる。
ビット、バイト、ニブル、符号なし整数
ビット
ビット (bit)の概念は、1 または 0、on または off、yes または no という二値として理解でき、何らかのトグルスイッチを符号化したものと言える。単一のビットは以下の2種類の状態のどちらかを必ず表す。
2進値 | 10進値 |
---|---|
0 | 0 |
1 | 1 |
単一のビットはそれ単体では2種類の値しか表せないが、2ビット列にするとより多くの値を表せる。
2進値 | 10進値 |
---|---|
00 | 0 |
01 | 1 |
10 | 2 |
11 | 3 |
同様に3ビットでは、2ビットのときの2倍の種類の値を表せる。
2進値 | 10進値 |
---|---|
000 | 0 |
001 | 1 |
010 | 2 |
011 | 3 |
100 | 4 |
101 | 5 |
110 | 6 |
111 | 7 |
ビット数が増えると、0 と 1 の組み合わせで表される数の種類も指数関数的に増える。上の例では、1ビットでは2種類の値しか表せないが、2ビットでは4種類の値、3ビットでは8種類の値が表せる。ビット数によって表せる値の数は以下のようになる。
ビット数(b) | 表せる値の数(N) |
---|---|
1 | 2 |
2 | 4 |
3 | 8 |
4 | 16 |
5 | 32 |
6 | 64 |
7 | 128 |
8 | 256 |
バイト
バイト(byte)は8ビットのビット列を指し、256種類の値を表せる。現代のコンピュータは情報を8ビット単位で処理するか、さらにそれに2の冪をかけたビット列単位(例えば、16ビット、32ビット、64ビット)で処理することが多い。8ビットは基本単位として広く採用されており、「オクテット(octet)」とも呼ばれる(これは、過去のコンピュータに7ビットや9ビットを1バイトと定義したものがあったためである)。現在、コンピュータ上でアドレス指定可能な最小単位(バイト)も一般にオクテットであり、そのためにオクテットとバイトは同義語として使われることが多くなっている。
ニブル
オクテットの半分、すなわち4ビットのビット列をニブル(nibble)と呼ぶ。16種類の値を符号化でき、数値に当てはめれば 0 から 15 になる。ニブルの値と数としての解釈はどういうマッピングであってもいいが、一般に以下のように解釈される。
2進値 | 10進値 | 2進値 | 10進値 |
---|---|---|---|
0000 | 0 | 1000 | 8 |
0001 | 1 | 1001 | 9 |
0010 | 2 | 1010 | 10 |
0011 | 3 | 1011 | 11 |
0100 | 4 | 1100 | 12 |
0101 | 5 | 1101 | 13 |
0110 | 6 | 1110 | 14 |
0111 | 7 | 1111 | 15 |
他にグレイコードのような順序付けもあるが、上記の順序は一般に使われている十進法のような位取り記数法となっている。例えば以下のような十進数値があるとする。
- 7531
これは一般に次のように解釈される。
- (7 × 1000) + (5 × 100) + (3 × 10) + (1 × 1)
あるいは10のべき乗を使うと次のようになる(ゼロでない数のゼロ乗は1)。
- (7 × 103) + (5 × 102) + (3 × 101) + (1 × 100)
数値の各桁は 0 から 9 の10種類の値をとるので、これを十進法または「底が10」であるという。また各桁はその位置によって10のべき乗で重み付けされている。
同様に二進法でも同じことが言える。十進の13という値は二進では以下のように表される。
- 1101
各桁は 1 から 0 という2種類の値しかとらないので、これを二進法または「底が2」であるという。また、桁位置ごとの重み付けは次のようになる。
- 1101
- = (1 × 23) + (1 × 22) + (0 × 21) + (1 × 20)
- = (1 × 8) + (1 × 4) + (0 × 2) + (1 × 1)
- = 13 (十進)
ここで使われている2のべき乗は 1, 2, 4, 8 である。プログラマは 216 までの2のべき乗をよく使うので、暗記していることが多い。
20 = 1 28 = 256 21 = 2 29 = 512 22 = 4 210 = 1,024 23 = 8 211 = 2,048 24 = 16 212 = 4,096 25 = 32 213 = 8,192 26 = 64 214 = 16,384 27 = 128 215 = 32,768 216 = 65,536
210 = 1,024 という値を(SI接頭辞とは異なるものの)「キロ」あるいは K と表すことがある(2進接頭辞では「キビ」または Ki)。その場合、それより大きな2のべき乗は次のようにも表される。
211 = 2 K = 2,048 214 = 16 K = 16,384 212 = 4 K = 4,096 215 = 32 K = 32,768 213 = 8 K = 8,192 216 = 64 K = 65,536
同様に 220 = 1,024 × 1,024 = 1,048,576 は「メガ」または M と表すことがある(2進接頭辞では「メビ」または Mi)。
221 = 2 M 222 = 4 M
さらに 230 は「ギガ」または G と表される(2進接頭辞では「ギビ」または Gi)。
SI接頭辞と同じ呼び方をすると混乱が生じることがあるため、1998年12月の国際電気標準会議が2のべき乗値を表す新たな単位を策定した。これを2進接頭辞と呼ぶ。
もうひとつ注意しなければならない点として、16ビットで表せる値は65,536種類あるが、これは 0 から 65,535 までの値である。人間は物を数えるとき 1 から数え始めるが、コンピュータでは 0 を最初とすることが多い。その方がプログラムが容易になるからである。
以上がコンピュータでの二進法による数値表現の基本だが、以下のような制限がある。
- 単純な算術であっても、与えられたビット数で表せる範囲でのみ実行できる。すなわち、16ビットを一度に処理するシステムなら、算術の結果が 65,536 以上となるような演算はできない。
- この方式では、小数を表せない。すなわち、整数しか扱えない。
- この方式では、負の数を表せない。全ての数はゼロか正である(つまり符号がない)。
このような制限はあるが、このような符号なし整数は何かを数えるのには非常に便利である。これは非常に単純でコンピュータにも扱いやすい。
なぜ2進数なのか?
- コンピュータが使う論理はブール論理であり、これは2値の論理である。したがって二進法の2値とブール論理体系における2状態が直接対応可能である。
- 3値以上の値を識別するハードウェアは2値のハードウェアよりも複雑になる。
- 二進法は十進法よりも若干効率がよい。初期のコンピュータは十進(二進化十進表現)を使っているものが多かった。普段、人間が行っている計算をコンピュータに行わせようとしたとき、十進で実装するのは自然の流れと言える。それが回路としてはほとんど使われなくなったのは、二進に比べて回路量が増え、信頼性が低くなるためである。
- 他の底を採用しようとした例もある。三進法を使ったコンピュータも、二進法よりも効率がよいのではないかと期待され、実験的に開発されたことがある。一般に数値を記号で表現するとき、三進法が最も効率がよいとされるが、二進法もそれとほぼ同程度の効率である。実際、三進法コンピュータにそれ以上の具体的な利点がなかったため、普及することはなかった。[1]
8進表現と16進表現
八進法と十六進法は二進数値を表現するのに便利で、コンピュータでよく使われている。コンピュータでは二進数値を表示することがよくあるが、単に 1001001101010001 などと表示すると間違いやすい。そこで二進数値を8を底として表したり(base-8、八進法)、16を底として表したり(base-16、十六進法)することが多い。
十進の体系では、10種類の数字(0 から 9)を組み合わせて数値を以下のように表す。
- 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ...
八進の場合、8種類の数字を使う(0 から 7)。
- 0 1 2 3 4 5 6 7 10 11 12 13 14 15 16 17 20 21 22 23 24 25 26 ...
すなわち、八進の "10" は十進の "8" に等しく、八進の "20" は十進の "16" に等しい。
十六進では、16種類の数字を使う(0 から 9 の後に一般に A から F までが続く)。
- 0 1 2 3 4 5 6 7 8 9 A B C D E F 10 11 12 13 14 15 16 17 18 19 1A 1B...
すなわち、十六進の "10" は十進の "16" に等しく、十六進の "20" は十進の "32" に等しい。
底変換
これらはいずれも位取り記数法だが、十進法の桁の重み付けが10のべき乗であるのに対して、八進法では8のべき乗、十六進法では16のべき乗になっている。十六進や八進の数値を十進に変換するには、各桁の数字にその桁位置の重み付け値をかけ、それらの総和を求めればよい。例えば、
- 八進 756
- = (7 × 82) + (5 × 81) + (6 × 80)
- = (7 × 64) + (5 × 8) + (6 × 1)
- = 448 + 40 + 6 = 十進 494
- 十六進 3b2
- = (3 × 162) + (11 × 161) + (2 × 160)
- = (3 × 256) + (11 × 16) + (2 × 1)
- = 768 + 176 + 2 = 十進 946
したがって、八進数1桁は3ビットの二進数値と完全な対応関係にある。
000 = 八進 0 001 = 八進 1 010 = 八進 2 011 = 八進 3 100 = 八進 4 101 = 八進 5 110 = 八進 6 111 = 八進 7
同様に、十六進数1桁は4ビットの二進数値と完全な対応関係にある。
0000 = 十六進 0 1000 = 十六進 8 0001 = 十六進 1 1001 = 十六進 9 0010 = 十六進 2 1010 = 十六進 a 0011 = 十六進 3 1011 = 十六進 b 0100 = 十六進 4 1100 = 十六進 c 0101 = 十六進 5 1101 = 十六進 d 0110 = 十六進 6 1110 = 十六進 e 0111 = 十六進 7 1111 = 十六進 f
そこで、1001001101010001 のような長い二進数値も容易に八進数に変換できる。
001 001 001 101 010 001 二進 = 1 1 1 5 2 1 111521 八進
また、十六進数への変換も容易である。
1001 0011 0101 0001 二進 = 9 3 5 1 9351 十六進
しかし、十進数(37713)への変換はこれらよりも面倒である。
八進数値や十六進数値を十進に変換する場合、以下のようなパターンを踏襲する。
(d1 * base + d2) * base + dn........
すなわち、最初の桁の数字を底とかけ、それに次の桁の数字を足し、これを再帰的に繰り返せばよい。以下に例を示す。
- 十六進 A1 の場合
- d1=A (または十進では 10)
- d2=1
- base=16
- d1 * base + d2 = 10 * 16 + 1 = 十進 161
- 十六進 129 の場合
- d1=1
- d2=2
- d3=9
- base=16
- (d1 * base + d2) * base + d3 = ( 1 * 16 + 2) * 16 + 9 = 十進 297
同様に八進や二進の数値も変換できる。
- 二進 1011 の場合
- d1=1
- d2=0
- d3=1
- d4=1
- base=2
- ((d1 * base + d2) * base + d3) * base + d4 =
- ((1 * 2 + 0) * 2 + 1) * 2 + 1 = 十進 11
- 八進 1232 の場合
- d1=1
- d2=2
- d3=3
- d4=2
- base=8
- ((d1 * base + d2) * base + d3) * base + d4 =
- ((1 * 8 + 2) * 8 + 3) * 8 + 2 = 十進 666
2進数での符号付数値表現
コンピュータで二進数の負の数を表す方法は本質的なものではない。そのため、符号付整数の表現はいくつか存在する。いずれの場合も符号ビットがあり、多くの場合最左端のビットが符号ビットとされる。すなわち、符号ビットが1なら負の数を表し、0なら正の数を表す。
符号-仮数部
符号-仮数部表現は、最も単純な符号付数値表現である。1ビットの符号ビットと仮数(または絶対値)を表す他のビット群からなる。例えば、4ビットの場合、
0101 =わ +たす5 1101 =わ -ひく5
1の補数
1の補数とは、数のビット毎の反転が符号を反転させるとする表現である。これはビット単位のNOT演算に他ならない。例えば、
0101 =わ +たす5 1010 =わ -ひく5
1の補数でも符号-仮数部表現でも、ゼロの表現が二種類存在するという問題がある。このため、どちらも最近のコンピュータでは滅多に使われない。1の補数では、
0000 = +0 1111 = -0
符号-仮数では、
0000 = +0 1000 = -0
2の補数
最近のコンピュータでは2の補数表現が広く使われている。2の補数は、ビット毎のNOT演算を施した後に1を加算したものである。例えば、
0101 =わ +たす5 1011 =わ -ひく5
したがって、
0000 = 十進 0 1000 = 十進 -8 0001 = 十進 1 1001 = 十進 -7 0010 = 十進 2 1010 = 十進 -6 0011 = 十進 3 1011 = 十進 -5 0100 = 十進 4 1100 = 十進 -4 0101 = 十進 5 1101 = 十進 -3 0110 = 十進 6 1110 = 十進 -2 0111 = 十進 7 1111 = 十進 -1
この方式では、16ビットの二進数は −32,768 から 32,767 に対応し、32ビットでは −2,147,483,648 から 2,147,483,647 に対応することになる。
2の補数の利点は、ほとんどの演算で符号を気にする必要がなく、符号無しの整数と全く同じに扱える点である。
例えば、5 + (-5) は以下のようになる。
0101 +1011 10000
ここで、数値は4ビットで表しているので、演算結果も4ビットでなければならず、したがって先頭の1は捨てられ、結果として期待した通りの0が得られる。
演算において符号を気にする必要がないのは、2n を法とした合同式になっているためである。例えば、15 ≡ -1 (mod 16) である。コンピュータは一般に固定のビット数で数値を表すので、法が一定であり理想的である。2の補数表現と符号なし数値の違いは、大小比較方法と表示方法である。
2の補数表現の唯一の奇妙な点として、表現可能な最も小さい数(16ビットなら -32768)の2の補数をとると自分自身になる点が上げられる。しかし、それが問題となることは滅多に無い。
2進数による小数の表現
固定小数点数
固定小数点数形式は、金銭勘定など浮動小数点数の精度では十分ではない場合、すなわちビジネスにおける計算(表計算ソフトやCOBOLなど)でよく使われる。
整数部と小数部のビット数は、必要とされる精度や範囲に十分なように選ばれる。例えば、32ビット形式では、整数部に16ビット、小数部に16ビットといったように設定される。
桁位置の重み付けは、整数部と小数部で連続的となる。例えば整数部が、8の位、4の位、2の位、1の位となっている場合、小数部は0.5の位、0.25の位、0.125の位と続く。
例:
整数ビット群 小数ビット群 0.5 = 1⁄2 =わ 00000000 00000000.10000000 00000000 1.25 = 11⁄4 =わ 00000000 00000001.01000000 00000000 7.375 = 73⁄8 =わ 00000000 00000111.01100000 00000000
ただし、この形式では二進では表せない数が出てくる。例えば、1/5(十進では 0.2)は正確に表すことはできず、最も近い値は以下のようになる。
13107 / 65536 =わ 00000000 00000000.00110011 00110011 =わ 0.1999969... 十進の場合 13108 / 65536 =わ 00000000 00000000.00110011 00110100 =わ 0.2000122... 十進の場合
これは、桁を増やしても正確に表すことはできない。1/3 という数値を考えてみよう。これを十進の小数で表すと 0.333333... となって永遠に続く。これを適当な桁で止めると、その数値表現は 1/3 を正確に表すことはできていない。
つまり、十進で有限小数で表せる数が二進数で有限小数になるとは限らない。これを回避するトリックとして、小数ではなく分子と分母を別々に格納した一種の分数として内部で保持する方式がある(四則演算などを分数として行う)。しかし、平方根を求めるなどといった演算はできないし、分数同士の加減算では通分によって分母が表現できないほど大きな値になる危険性もある。
浮動小数点数
非常に大きな数や非常に小さな数を表すには、浮動小数点数形式を使う。
十進数では、以下のような浮動小数点数形式がよく使われる。
- 1.1030402 × 105 = 1.1030402 × 100000 = 110304.02
あるいは、より簡潔に以下のように表記する。
- 1.1030402E5
これは、「1.103402 と1の後に5個ゼロが続く値をかける」ことを意味する。1.1030402 を「仮数」、それにかける10のべき乗(E5、すなわち 105)を「指数」と呼ぶ。指数が負の場合、1の前に小数点とゼロがある値をかけることを意味する。例えば、
- 2.3434E-6 = 2.3434 × 10-6 = 2.3434 × 0.000001 = 0.0000023434
この方式の利点は、仮数の桁数だけでは表せない範囲の数値を指数をかけることで表せる点にある。この方式の二進数版がコンピュータ向けに定義されている。最も一般的なものとして IEEE 754 があり、以下のような64ビットの浮動小数点数形式を定義している。
- 11ビットの指数部。「エクセス1023」形式。エクセス1023とは、指数を符号なしの整数 0 から 2047 で表し、実際の指数の値はそこから 1023 を減算したものとする方式である。
- 52ビットの仮数部。符号なしの二進数で、小数点以下の部分だけを保持し、小数点以上の値は常に "1" とみなす。
- 符号ビット。数値の符号を与える。
この形式がどう見えるかを示すため、以下のようにメモリ上のバイト毎の内容を示す。
byte 0: S x10 x9 x8 x7 x6 x5 x4 byte 1: x3 x2 x1 x0 m51 m50 m49 m48 byte 2: m47 m46 m45 m44 m43 m42 m41 m40 byte 3: m39 m38 m37 m36 m35 m34 m33 m32 byte 4: m31 m30 m29 m28 m27 m26 m25 m24 byte 5: m23 m22 m21 m20 m19 m18 m17 m16 byte 6: m15 m14 m13 m12 m11 m10 m9 m8 byte 7: m7 m6 m5 m4 m3 m2 m1 m0
ここで "S" は符号ビット、"x" は指数ビット、"m" は仮数ビットである。これを計算に使用する場合、以下のように解釈する。
- <符号> × (1 + <仮数の小数部>) × 2<指数部> - 1023
この方式で、十進にして約15桁の有効数字の以下の範囲の数値を表現できる。
最大 | 最小 | |
---|---|---|
正の数 | 1.797693134862231E+308 | 4.940656458412465E-324 |
負の数 | -4.940656458412465E-324 | -1.797693134862231E+308 |
これ以外に特別な値として NaN(Not A Number)があるが、ここでは解説しない。32ビットの浮動小数点数形式もあり、符号ビット、23ビットの仮数部、8ビットの指数部(エクセス127)から成る。
byte 0: S x7 x6 x5 x4 x3 x2 x1 byte 1: x0 m22 m21 m20 m19 m18 m17 m16 byte 2: m15 m14 m13 m12 m11 m10 m9 m8 byte 3: m7 m6 m5 m4 m3 m2 m1 m0
数値としての解釈は以下の通りである。
- <符号> × (1 + <仮数の小数部>) × 2<指数部> - 127
表現できる数値は以下の範囲である。
最大 | 最小 | |
---|---|---|
正の数 | 3.402823E+38 | 2.802597E-45 |
負の数 | -2.802597E-45 | -3.402823E+38 |
浮動小数点数も整数と同様、表せる値の範囲がある。また、精度も制限されている。64ビットの浮動小数点数は十進の15桁程度の精度しかない。演算結果の桁数がそれより多い場合、誤差が生じる。例えば、非常に大きな数に非常に小さな数(ゼロに近い数)を加算すると、有効数字の桁の範囲が違いすぎるため元の大きな数が得られる場合がある。浮動小数点数を使った演算では常に誤差が生じる。これを放置して計算を続けていくと、誤差が蓄積して誤った結果が得られる場合がある。
もうひとつの問題は、浮動小数点数の仮数が二進数で表されているため、十進の仮数とは完全に一致しないことがある点である。すなわち、0.75 のような十進でも二進でも有限小数で表せる数なら何の問題もない。しかし、例えば 0.1 という十進の小数は、二進数では 0.000110011... というように無限小数になる。
プログラミング言語での数値
低級プログラミング言語では、符号の有無や固定小数点か浮動小数点かを気にする必要がある。例えば浮動小数点の加算なのか整数の加算なのかによって、使用する命令が全く違ったものとなる。
しかし、LISPやPythonといった高級 プログラミング言語では数値のデータ型はより抽象的であり、rational、bignum、complex などがある。それらの言語では、どんな数値であっても算術演算を正しく処理することが期待できる。演算子オーバーロードによって、符号無し整数、符号付整数、浮動小数点数、複素数などどんな数値であっても全く同じ記述方法で演算が可能となっている。REXXやJavaなどの言語では、十進法の浮動小数点数をサポートしていて、予期しない結果を防ぐことができる。Javaは符号無し型を持たない。
脚注
関連項目
外部リンク
- この記事は、パブリックドメインを表明しているvectorsiteの記事をベースとして英語版Wikipediaで執筆されたものを翻訳したものです。