コンピュータの数値表現

提供: miniwiki
移動先:案内検索

コンピュータの数値表現の記事では、コンピュータシステムにおけるの表現方法について解説する。数学では「数」の概念は複素数などに大きく広がっているが、この記事では冒頭部を除くと、もっぱら固定長の整数の、しかもコンピュータの内部の話に偏っている(すなわち、数学的な議論は多くない)。実数の近似表現などについては浮動小数点数の記事や任意精度演算の記事を、代数的数のコンピュータでの扱いなどといった話題については、適切な参考文献を参照のこと。

誤差についての誤謬

コンピュータに詳しくない人[1]は、コンピュータでの数値計算が無謬である(誤りがない)と誤解していることがある。例えば、[math]\frac{1}{\,3\,} \times 3[/math] を計算すると正確に 1 が得られると期待するかもしれない。しかし、実際にはコンピュータや電卓では 0.9999999999999999 のような結果となったり、場合によっては 0.99999999923475 のような値になることもある。「実数型」などという用語は、それ自体が誤謬であるともいえる。

後者の値 (0.99999999923475) はバグではなく、二進法浮動小数点数による近似の結果生じる。ある種の任意精度演算系や、何らかの数式処理システムでそういった演算に対応している場合は、1 や 0.9999999999999999... という結果が得られるものもある。なお、十進法の浮動小数点数でも、本来なら 0.9999999999999999 のような数になる。しかし、電卓などでは特別扱いをしていて、表示画面には出ない内部での計算の最後の3桁が「999」の場合だけは繰り上がりをする、といったものがある。[2]

ビット・バイト・ニブル

コンピュータにおける二進法による非負整数値の表現を説明する。

ビット

ビット(bit)の概念は、1 または 0、on または off、yes または no などの二値として解釈でき、何らかのトグルスイッチを符号化したものといえる。単一のビットは下表の2種類の状態のどちらかを必ず表す。コンピュータの論理回路である電子回路では、電位の高い (High) 低い (Low) を使うことも多いから、HとLという表現が使われることも多い。[3]

1ビットの値は2種類ある。

状態 数値
L 0
H 1

単一のビットでは2種類の値しか表せないが、2ビットの組合せでより多くの値が表せる。

2ビットの値は4種類ある。

二進表現 整数値
00 0
01 1
10 2
11 3

3ビットでは、さらに2倍の種類の値を表せる。

3ビットの値は8種類ある。

二進表現 整数値
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 は、下表のとおりになる。

[math]2^b=N[/math]
ビット数 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ビットではなく、5ビット〜9ビット程度をバイトとするコンピュータもあったことなどから、「オクテット」という語もある。オクテットは必ず8ビットであり、任意のコンピュータを接続する可能性を考慮しているネットワーク用語などでは「オクテット」を好んで使うことも多いが、近年はバイトの語も、ほぼ8ビットを指すようになっている。

また、1964年のSystem/360以降のコンピュータでは、アドレス付け(アドレッシング)の単位も、ほとんどがバイト単位であり、例えば4バイトから成るワード単位でアクセスするには、アドレスの数値を4ずつずらす、といったようにしてアクセスする、といったものが多い。

ニブル

オクテットの半分、すなわち4ビットのビット列をニブル(nibble)と呼ぶ。16種類の値を符号化でき、非負整数に当てはめれば 0 から 15 になる。

二進表現 整数値 二進表現 整数値
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

なぜ二進法なのか?

  • コンピュータが使う論理はブール論理であり、これは2値の論理である。したがって二進法の2値とブール論理体系における2状態が直接対応可能である。
  • 3値以上の値を識別するハードウェアは2値のハードウェアよりも複雑になる。
  • 二進法は十進法よりもかなり効率がよい。初期のコンピュータは十進(二進化十進表現)を使っているものも多かった。しかし、電気回路のオンオフをそのまま利用できる二進法のほうが圧倒的に効率が良い。近年では、10ビット(二進法では 0〜1023 の1024通りが表現可能)を使って 0〜999 の1000通りを表現する、効率の良い Densely packed decimal といったものも考案されている。なお、初期のコンピュータの場合(特にリレー式)、入出力機器と比べ本体がたいして速くないため、入出力を十進でおこなうのであれば、二進←→十進の変換はそれなりに計算量が必要なので、むしろそのまま十進で扱ってしまったほうが効率が良い、という場合もあった。なお「二進に比べて回路量が増え、信頼性が低くなるためである」といった嘘の説明をする者がいるので注意が必要で、むしろ冗長さを利用してエラーを検出する手法を併用し信頼性を上げている例がある(最初期のFACOMなど。リレーは電子的ではなく機械的であるための接触不良など(コンピュータの素子としては)信頼性の点で工夫が必要だった)。
  • 他の底を採用しようとした例もある。三進法を使ったコンピュータも、二進法よりも効率がよいのではないかと期待され、実験的に開発されたことがある。一般に数値を記号で表現するとき、三進法が最も効率がよいとされるが、二進法(と四進法)もそれとほぼ同程度の効率である(よく言われる話ではあるのだが、実のところこの効率に関する計算は、そもそも仮定に問題があり(n状態の表現にはn個の素子が必要、という仮定がある)現実的には二進法を1素子の2状態で表現するのが圧倒的に有利なのである。三進法#経済性の記述も参照)[4]

その他

目的によっては、以上のような方法とは異なる表現法が便利なこともある。例えば、グレイコードなどがある。

八進と十六進

八進法十六進法は二進法の3桁と4桁に直接対応するので、コンピュータ関連の数を表現するためなどによく使われている。たとえば、整数値を二進法で 1001001101010001 などと表示しても、相当訓練された人間でもぱっとその値を把握することはできない。なので、八進法や十六進法がよく使われる。

十進の体系では、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 十六進

しかし、十進には、これらのような単純な書き換えで変換することはできず、整数値を経由して普通に変換することになる。

符号付数値表現

符号付きの数値の表現法(負の数も表現できる表現法)はいくつか存在する。必ずしも「符号ビット」があるとは限らない(ゲタ履き表現では、ゲタの値によっては「符号ビット」としては扱えない)。符号ビットがある場合は、最上位ビットが符号ビットのことが多い。なお、符号ビットについて「1なら負の数を表し、0なら正の数を表す」といったように考えるのが自然なのは「符号と絶対値」表現の場合だけであり、たとえば「2の補数」表現であれば、最上位ビットは −(2N) の重みを持つ桁である、と解するのが自然である。

符号と絶対値

「符号と絶対値」表現は、1ビットの符号ビットと、絶対値を表す残りのビット群からなる。例えば、4ビットの場合(MSBの、0が正、1が負を示すものとすると)、

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の補数表現ではごくあたりまえの、0をまたいだ計算(正確には、負の整数と非負整数にまたがった計算)であることのほうが、慣れるまでは非直感的かもしれない。

小数の表現

固定小数点数

固定小数点数形式は、金銭勘定など浮動小数方式と相性が悪い場合(いわゆるCOBOL脳では勘違いしやすいらしいが、「浮動小数点数の精度では十分ではない場合」ではない。単精度の場合に足りないようであれば、それは同じ長さの固定小数方式であっても足りてないのであって、浮動小数方式だからではない。倍精度ならたいていの場合十分にあるが、例えば金銭勘定においては、10-123 などというような細かい値は必要なく、適切な所で要求通りに丸めて打ち切らねねばならないから、である)、すなわちビジネスにおける計算(表計算ソフトやCOBOLなど)でよく使われる。

整数部と小数部のビット数は、必要とされる精度や範囲に十分なように選ばれる。例えば、32ビット形式では、整数部に16ビット、小数部に16ビットといったように設定される。

桁位置の重み付けは、整数部と小数部で連続的となる。例えば整数部が、8の位、4の位、2の位、1の位となっている場合、小数部は0.5の位、0.25の位、0.125の位と続く。

例:

                          整数ビット群   小数ビット群
   0.5    =   ½  =  00000000 00000000.10000000 00000000
   1.25   =  1¼  =  00000000 00000001.01000000 00000000
   7.375  =  7⅜  =  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 を正確に表すことはできていない。

つまり、十進で有限小数で表せる数が二進で有限小数になるとは限らない。これを回避するトリックとして、小数ではなく分子と分母を別々に格納した一種の分数として内部で保持する方式がある(四則演算などを分数として行う。なお、トリックというよりは王道である)。しかし、平方根を求めるなどといった演算はできないし、分数同士の加減算では通分によって分母が表現できないほど大きな値になる危険性もある(そもそも普通は「有理数型」というデータ型として、分母分子ともにいわゆる big integer(多倍長整数)で表すので、大きな数になりうることは普通は危険性とは考えられていない)。

浮動小数点表現

絶対値が非常に大きな数や非常に小さな数を扱うには、浮動小数点方式を使う。

まず、コンピュータの内部表現ではなく、日常的な十進での表記で説明する。浮動小数点方式では、数を次のように表記する。

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 があり、一例として以下のような「倍精度」の二進浮動小数点数形式(binary64)を定義している。

  • 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桁程度の精度しかない。演算結果の桁数がそれより多い場合、誤差が生じる。例えば、非常に大きな数に非常に小さな数(ゼロに近い数)を加算すると、有効数字の桁の範囲が違いすぎるため元の大きな数が得られる場合がある。なお、拡大解釈して「浮動小数点数を使った演算では常に誤差が生じる」などというのは間違いである。たとえば、1.0 + 1.0 は厳密に 2.0 になる。「これを放置して計算を続けていくと、誤差が蓄積して誤った結果が得られる場合がある。」などと言う者もいるが、そもそも数値計算で「正しい」と「誤っている」ということをどう定義するのか明確でなしにそのように言っても何の意味もない。

また、二進の浮動小数点表現の問題として、地球人が多用する十進の小数表現でいわゆる「きりが良い」数との相性が悪い場合の存在がある。すなわち、0.75 のような十進でも二進でも有限小数で表せる数なら何の問題もない。しかし、例えば 0.1 という十進の小数は、二進では 0.000110011... というように無限小数になる。

プログラミング言語

低水準言語では、符号の有無や固定小数点か浮動小数点かを気にする必要がある。例えば浮動小数点の加算なのか整数の加算なのかによって、使用する命令が全く違ったものとなる。

LISPPythonといった高水準言語では数値のデータ型はより抽象的であり、rationalbignumcomplex などがある。それらの言語では、低水準言語と比べ、より抽象化され扱いやすいものとしてそれらの値が提供される。しかし「どんな数値であっても算術演算を正しく処理することが期待できる」などというのは大間違いで、コンピュータでは真の実数を扱うことはできないのだから、これらのデータ型でも同様に、それぞれの性質に注意して扱わねばならないことに違いは無い。演算子オーバーロードなどによって、符号無し整数、符号付整数、浮動小数点数、複素数など、複数のデータ型に対して、同じ見た目の式になるような言語もある(ただ単に見た目の話であり、「使用する命令が全く違ったものとなる」という点はなにひとつ違わないのだが)。十進法の浮動小数点数をサポートしている言語もあるが、それで「予期しない結果を防ぐことができる」と主張するウィキペディア編集者は (10.0/3.0) * 3.0 という計算すら予期しないのであろう。

脚注

  1. 実際のところ、自称「詳しい人」でもたいがい正確ではないことが多い。
  2. そのためか、十進法の浮動小数点数では誤差が発生しないという誤解をしている、自称「詳しい人」に注意が必要である。
  3. この対応は、逆でもよい(負論理に相当する)。電子回路では正逆を交互に使うと便利なこともある。
  4. Brian Hayes, "Third Base", American Scientist 89(6): 490-494 (2001), [1]

関連項目

外部リンク

この記事は、パブリックドメインを表明しているvectorsiteの記事をベースとして英語版Wikipediaで執筆されたものを翻訳したものです。