コンピュータの数値表現

トップページプログラミング技術等

N進数の構造

現在の一般的なコンピュータのCPU(中央演算装置)やメモリ(記憶装置)は、0と1の二つの文字しか、直接扱う事は出来ない。これは、簡単に言えば電気の点灯(オン=1)、消滅(オフ=0)を情報の最小単位(1ビット)としているからで、コンピュータ内部では数字、文字、色、図形、音、プログラム等の全ての情報を、0と1を並べた数字で表現している。例えば、仮に「01101101」で或る情報Xを表現している。
2で桁上がりする数字を2進数といい、0と1しか出て来ない。人間は10進数に慣れているが、コンピュータの世界では2進数が基本となる。10進数では数字は0~9までしかなく、9より一つ増えたら桁上がり(桁数が一つ増える)して10となる。2進数では0と1しかないので、1より一つ増えたら桁上がりして、10となる。これは10進数の2と同じ数字を表現している。2進数の10=10進数の2、という事になる。
桁上がりする数(2進数の2、10進数の10)を基数と呼ぶ。基数は通常2以上のいくつでもよい。一般化して、基数NのN進数の構造を下図に示す。g~mは何らかの数字を表し、n進数であれば0~n-1のどれかが入る。一桁(ケタ)目、二桁目といった数え方の数を桁数とすると、整数部分は基数の(桁数-1)乗×その桁の数字、小数部分は基数の-(マイナス)桁数乗×その桁の数字に分解される。
小数部分は小数点に近い方から一桁目、二桁目という数え方になる。或る数(0は除外)の0乗は全て1になるので、整数部分の一桁目はその桁の数がそのまま入る。それ以外の桁で、数に掛けられる「基数の何乗か」を「桁の重み」と呼ぶ。なお唯、10と書かれた場合、本当はそれが2進数の10なのか、10進数の10なのかは区別出来ない。数字を括弧でくくり、右下に基数を書くのが正確な表記となる。

N進数の構造

N進→10進変換

16進数は16を基数とする数字で、10=A、11=B、12=C、13=D、14=E、15=Fと割当て、これらのアルファベットを数字として用いる。例えば16進数のA2Dは、16×16×10(A)+16×2+13(D)という事になる。もし17進数を考えれば、もう一つ16=Gを導入すればよい。コンピュータの世界では、例えばMACアドレスやIPv6によるアドレスに用いられる等、16進数は幅広く利用されている。
2進数の10は10進数の2で、この二つは同じ数である。N進数で表された或る数を、基数の異なるM進数で表現し直す事を、基数変換と呼ぶ。数自体を変換するのではなく、何進数で表すかという表現方法を変える事に相当する。N進数から10進数への基数変換は、単純に各桁の数字に各桁の重みを掛けて、全ての桁の総和を求めればよい。下図は、小数点付きの2進数と16進数を、10進数に変換した例となる。
16進数の方では、左の桁から16×16×10(A)+16×0+1(16の0乗)×7という整数部になる。小数点以下一桁目は(16分の1)×8、同二桁目は(16分の1)×(16分の1)×14(E)、同三桁目は(16分の1)×(16分の1)×(16分の1)×4なので、こんな数になる(16の-2乗は、(16分の1)×(16分の1)=256分の1、同様に16の-3乗=4096分の1)。
基数が3等の場合、小数部は割り切れない数字(循環小数)になる場合もある。この場合は適当な所で、四捨五入等をする事になる。

N進→10進変換

10進→N進変換(整数部)

10進数を、任意のN進数に変換する方法を考える。10進数をN進数で表現し直すと、下図の2行目の様に、N進数の各桁の重みに何らかの数(?)が掛けられた総和になるはずである。何桁のN進数になるかも、計算しないと分からない。しかし、10進数とNが決まれば、其々の?も決まっているはずなので、それらを何らかの手順であぶり出して行けばよい。10進数の19を、2進数に変換する例で考えてみる。
其々の?を、pからtまでの未知数とする。2(基数)の0乗の重みに掛かるtは、19を2(基数)で割った余りである事は明らかなので、t=1と分かる。ここで19=を、18=と1=に分解し、18=の式の両辺を、また2(基数)で割る。9=の式が出来、同様にs=1であると分かる。8=の両辺を、また2で割る。4=の式では、4を2で割った余りは0なので、r=0と分かる。同様にq=0、p=1も次々に判明する。
10進→N進の整数部のあぶり出しは、10進数を基数Nで割った余りを出し、商をまた基数で割って余りを出し・・を繰返す。商が0になったら終了し、其れまでに得た余りを、得た順に右から左へ並べると変換後のN進数となる。

10進→N進変換(整数部)

10進→N進変換(小数部)

10進数を任意のN進数に変換する際の、小数部分のあぶり出し方法は、まず小数部分(例では0.3125)=基数のマイナス乗の重みを持つ項の和とする。整数部では次々と商を基数で割って余りを取り出したが、小数部では逆に基数を掛ける。基数を掛けると、一番左の項は重みが基数の0乗になる。この項の未知数(例えば0.625=の式のp)は、左辺の整数部の数値に等しい事は明らかである。
0.625=の式では、基数の0乗の重みが掛かるpは0だと分かった。次に、最初の小数に基数を掛けた数(例では0.625)の小数部分(ここでは同じ0.675)だけを取り出して左辺に置くと、右辺は重みが基数のマイナス1乗から始まる項の和に変わる。また、基数を掛ける。例では1.25=の式が出来、右辺の重みが基数の0乗が掛かる未知数qは、1である事が判明した。この手順を繰返していく。
基数を掛けて得られた値の、小数部分が0になったら終了となる(最後の1.0=の式)。この例では未知数tは、出番が無かった事になる。小数部は判明した順に、小数点以下一桁目から右へ並べる。整数部と結合して、N進変換の完成となる。

10進→N進変換(小数部)

10進→N進変換(循環小数になる場合)

10進数をN進数に変換する際も、やはり小数部分に於いて循環小数になる場合がある。例では10進数の0.1を2進数に変換しようとしているが、上から2行目で出た0.2が下から2行目で再び現れる。こうなると、明らかに計算はエンドレスになる。他にも、この様な例は無数にある。コンピュータで扱う場合、何処かで四捨五入や切上げ・切捨て等をして有限桁にする。従って、真実とは若干異なる値になり、この誤差を丸め誤差と呼ぶ。
循環小数は、0.3333333・・・(1/3)のように繰返す数が単一(3)の場合には限らない。0.142857142857142857・・・(1/7)のように、或る一群の数字列(142857)の繰返しもある。繰返される範囲の最初と最後の数字(単一の場合はその数字)の上に点を付して循環小数である事を示す。
有効桁数(丸めていない正しい数字の桁数)が大きくても、絶対値の近い正負の数(例えば、365.2522と-365.2511等)を足すと、急激に有効桁数が減る(この例では、0.0011となる)事があり、これを桁落ちと呼ぶ。

10進→N進変換(循環小数になる場合)

2進と16進等の相互変換

或るX進数を基数の異なるY進数に変換する場合は、X進数をいったん10進数や2進数に変換してからY進数にする手もあるが、丸め誤差が出る場合もある。これに対し、例えば2進数と16進数の相互変換では、中間に別の基数を置かなくても直接、簡単に変換が出来る。3F72.08A2という16進数を2進数に変換する場合は、16進数の各桁を其々2進数に変換し(Fは1111、7は0111等)、そのままの順序で並べればよい。小数部も同様である。
何故、そのまま並べれば良いのかは、10進数を例に考えると分かりやすい。整数部で二桁以上の数字は「10がいくつあるか」を示す数字と言える。三桁以上は「100=10×10がいくつあるか」を示す。16進数も同様に、二桁以上は「16がいくつあるか」、三桁以上は「16×16=256がいくつあるか」を示している。例えば16進数の30Aは、「16×30+A(10)」(16が30個と、一桁目の10がある)を示している。
同様に考えて、2進数の五桁以上は「2の4乗=16がいくつあるか」、九桁以上は「2の8乗=256がいくつあるか」を示している。だから、例えば2進数の五桁以上と16進数の二桁以上をそのまま置き換える事が出来る。小数部も、2進数の小数点以下一桁目~四桁目は、「1/16がいくつあるか」を示し、16進数の小数点以下一桁目の意味「1/16がいくつあるか」と一致するので、単純に置き換えられる。
図の2進数で、通常の表記ならば省略される桁に赤い下線が引いてある。小数部の端は、四桁になるように0を補わないと、正しい値に変換出来ない。2進→16進の変換では、小数点を境に整数部は左へ、小数部は右へ四桁ずつ変換する。2進→8進なら三桁ずつになる。3進と9進、27進等、一般に、或る基数が別の基数の何乗かになっている関係であれば、単純に置き換えるこの方法で変換出来る。

2進と16進等の相互変換

N進数の加減乗除

下記の2進数の加減乗除は、必ずしもコンピュータ内部の計算ではなく、人間の筆算の手順を示している。Nが幾つでも、間に小数点があっても同じ手順となる。加算は、各桁同士の其々の和の総和になるが、例えば或る桁の和がN(例では2)以上になれば、左隣の桁に1(図では赤字)を加える(桁上がり)。減算も各桁の其々の差の総和を求めるが、或る桁で引く数の方が大きければ、桁借りをする。
桁借りは、借りた方の桁をN(例では2)増やし、左隣の桁を1減らす。例のように左隣が0ならばさらに左から桁借りする(図の青数字)。乗算は、②の数字の一桁目は①の数字が(例では1つ)ある事を、②の二桁目は①がN×(例では0個)ある事、②の三桁目は②がN×N×(例では1つ)ある事を示し、これらの総和となる(③~⑤)。例えば110が2×2個ある事は、2進数では末尾に0を二つ付ければよい(⑤)。
除算は、2進数の111を101で割る例では111は101より大きいので、まず1が立つ(①)。余り(例では10.0)を求め(②)、その中に101の1/N(例は2進数なので10.1)があるか見る(③)。無いので0が立つ(④)。余りは変化しない(10.00)ので、今度は余りの中に101の1/(N×N)=1.01があるか見る(⑤)。あるので1が立つ(⑥)。この手順の繰返しだが、ここで打ち切れば商=1.01、余り=0.11となる。
商(例では1.01)の各桁に除数(101)を掛けた其々の値と、余り(0.11)の総和が被除数(111)と等しい事を考えれば、除算の手順は理解出来る。

N進数の加減乗除

論理シフト演算

例えば10進数で728の末尾に0を付け、7280にすると元の数の10倍になる。一般にN進数で末尾に0を付けると、元の数のN倍になる。従って、2進数では2倍になる。10進数で末尾に00を付ける元の数の100倍になるように、N進数では末尾に00を付けるとN×N倍になる。N進数で末尾に0を何桁か加えると、元の数×Nの加えた0の桁数乗の数になる(2進数で4桁の0を付けると、元の16(2の4乗)倍)。
これは、左図のように例えば2桁の0を末尾に付ける場合は、左に2桁ずらし、空いた末尾に0を入れる事に相当する。この様な演算をシフト演算といい、コンピュータ内部での計算を効率化する。逆に、右に何桁かずらすと、元の数をNの桁数乗で割った数になる(これも10進数の小数点移動を考えてみると分かり易い)。中図の赤字で示した1は小数点以下一桁目になる(整数しか扱わないならば切り捨てる)。
任意の掛け算を、複数のシフト演算に分解する事も出来る。例えば5×12の場合、5×8+5×4に分解出来る。×8、×4のように、2の何乗かになっている数の掛け算の和に分解すれば、2進数のシフト演算が使える。5は2進数で101なので、右図のように3桁ずらしと2桁ずらしを作り、足せばよい。例えば7÷32を2進数で求める場合は、111を右に5桁(2の5乗が32)ずらして、0.00111が答えとなる。
上記の例では、扱う数値は全て正だったが、符号なし整数(又は2進数で表現される数値以外のデータ)を扱うシフト演算を、特に論理シフト演算と呼ぶ。負数を扱う場合は、算術シフト演算という少し違う方法を用いる。

論理シフト演算

負の補数表現

コンピュータは0と1しか直接扱えないので、-記号を1、+を0として、符号を表す0か1+数字自体で、正負付きの数を表現出来る。例えば2進数の+7は0111、-7は1111として区別出来る。しかし、一般的なコンピュータでは、負の数を以下のように表現(補数表現)する場合もある。正ならば4~7となる100~111が-4~-1とされているのは、ここでは-4~3の範囲にある数しか対象にしていないからである。
負の数を補数表現すると、例えば3-4を3+(-4)とするように、減算を加算で代行出来、減算回路が不要となる分、効率的な計算が可能となる。例えば2-1は、2(010)+(-1(111))となって、答えは2進数の010+111=1001となる。この下三桁は001なので、対応表の1に該当する。つまり、2-1=1が正しく成立している。実際、答えが-4~3に収まる計算は、全て下三桁が正しい答えを指す。
例えば-1(111)を加える操作は、ブルーの7つのマスを埋める事に対応する。4桁目以上を無視した続きの中で、同じ数字が出るには8つのマスを埋める必要がある。従って、111を加える操作は-1の働きをする。-2、-3、-4も同様に埋めるマスが一つずつ減る。なお、この表現(正確には2の補数表現)では、或る数に-を付けた数にするには、或る数の0と1を全て反転させて、1を加えればよい。
勿論、扱える数字が-4~3しかないコンピュータなんて役に立たない。十分に大きな数が扱えるよう桁数を増やしているが、基本原理は変わらない。

負の補数表現

算術シフト演算

下図の様に5桁の2進数で、-16~15の範囲の数を表現する。補数表現なので、一番左の桁は正数が0、負数が1となる。この符号(正負)付きの数であっても、答えが定義した値の範囲(この例では-16~15)に収まる場合は、シフト演算により、元の数に基数(この場合は2)の何乗かで掛けたり、割ったりする演算が出来る。掛ける時は左へずらし、割る時は右にずらすのは論理シフト演算と同じとなる。
符号付きの数に対するシフト演算を、算術シフト演算と呼ぶ。論理シフト演算では、ずらして溢れた桁は捨て、空いた桁には0を入れた。算術シフト演算でも、溢れた桁を捨てるのは同様だが、負数を右にずらす場合、左に出来たスペースは一番左の符号ビットである1で埋めて行くのが異なる(右図の青数字)。正数を右にずらす場合は、左に出来たスペースには、論理シフトと同様に0で埋めればよい。
左にずらす演算では、負数は当然、より小さな数になる(左図の-4×4=-16等)。また、溢れて捨てた桁に1がある場合、正数では例えば00101(5)÷2=00010(2)となって、2.5の小数点以下を切り捨てた形になるが、負数では10011(-13)÷2=11001(-7)、10011(-13)÷4=11100(-4)となって、其々-6.5、-3.25の(符号を取った)絶対値を切り上げる形となる。
なお、左へのシフトで、一番左の桁が0→1、又は1→0と反転するような場合は、答えは定義外の値になる。

算術シフト演算

固定小数点表示

下図の2進数は、整数と同様に補数表現が行われ、減算を加算で出来る。小数点以下の桁数が増えると、数字の刻みは小さくなる(0.25→0.125)。整数部の桁数と小数部の桁数を固定させた数字の表現を、固定小数点表示という。下図の上段は整数2桁、小数2桁の表示、下段は整数1桁、小数3桁の表示となる。小数部の無い整数も、小数0桁の一種の固定小数点表示と捉える事が出来る。
2進数の固定小数点表示で表現出来る数字の範囲を、整数部と小数部に分けて考えてみる。整数部がN桁(符号を示す最上位桁は除く)あるとすると、最上位桁が0の正数は0~2のN乗-1、最上位桁が1の負数は-1~-(2のN乗)の範囲になる。正負合わせて、-(2のN乗)~2のN乗-1となる(3桁では、-8~7)。小数部は範囲というより刻みを表し、N桁あれば1/(2のN乗)の刻みを表現出来る。
正数の小数部(N桁)が全部1であれば、小数部だけで表現している数字は、1-1/(2のN乗)となる。負数の小数部のN桁が全部1であれば、小数部だけで表現している数字は、-1/(2のN乗)となる。固定小数点表示は、浮動小数点表示よりも演算が速く、誤差も少ないため、桁が比較的揃った数字の演算には向いている。最上位桁(符号付きでは符号ビット)をMSB、最下位桁をLSBと呼ぶ事がある。
なお、コンピュータ内部では0、1があるだけなので、小数点は直接、記述出来ない。小数点を省略した0と1のビット列を見て、ユーザやプログラム側がどの位置に小数点があるかを適宜、判断する事になる。

固定小数点表示

浮動小数点表示

例えばアボガドロ数を約6.02×1023(6.02E23)と表記するように、極大極小の数は、基数の何乗かを掛けて簡単に記述出来る。左の式のように、基数(2)の指数を変えると同じ数の小数点が移動(浮動)する。符号と或る数(仮数と呼ぶ)×基数の何乗か、を浮動小数点表示と呼び、固定小数点表示に比べ、同じビット数で定義出来る数の範囲が格段に広がるので、計算による桁あふれが起きにくくなる。
浮動小数点数のビット列表現は様々に考えられるが、2進数の仮数部をそのままビット列で表現する場合、例えば仮数部が3桁、指数が-3~3で定義出来る最大数は+111000、最小数は-111000、ゼロに最も近い数は±0.001となる。この様に、基数N、仮数部の桁数M、最大指数emax、最小指数-eminとすると、絶対値の最大数は±(NM-1)×Nemax、ゼロに最も近いのは±1/Neminとなる。
IEEE754規格(単精度)では、最上位ビットに符号(正0、負1)、次の8ビットに指数+127の2進表記、右23ビットに仮数を記述する。仮数部は一番上位の1の次に小数点が来るように、指数を決める(正規化)。最上位は必ず1になるので省略して2桁目から記述、空きは0で埋める。補数は用いない。指数を127でバイアスする事で、指数(この場合最小で-126)が負の場合でも、大小比較が容易になる。
なお、ゼロは全ビットを0で埋める。指数部を11ビット、仮数部を52ビットに拡張した表現を倍精度と呼ぶ。加算等には指数部を揃える必要があるが、それにより仮数部が大きくシフトされ、数字が消える事を「情報落ち」という。

浮動小数点表示

二進化十進表現

10進数の0~9は、2進数では0~1001となり、4ビットあればこの9つの数字が表現出来る。10進数を各桁に分解し、其々を2進数にし(3桁以内でも上位桁に0を入れて4ビットにする)、10進数の桁の順番で並べた数を二進化十進数(BCD)という。BCDにはゾーン形式(ゾーン10進数)とパック形式(パック10進数)があり、使い分けられる。ゾーンは、アンパック(パックではない)と表記される事もある。
下図は+3650のBCDである。ゾーン形式は、各桁の4ビットの前に1111(ゾーンビットと呼ばれる)が付くが、一番右の桁の前は+を示す符号ビット1100に変わる。パック形式はゾーンビットを省略し、一番右桁の4ビットの右に符号ビットを移動する。コンピュータはバイト(8ビット)単位で領域を確保するため、全体で8で割り切れるビット数にならない場合は、最上位桁に4ビットの0を補う。
ゾーンビットはEBCDICというコードでは1111だが、ASCII(JIS8)では0011になる等、コードによって異なる。符号ビットは、コンピュータによってさらに様々な種類がある(EBCDICでは正1100、負1101)。単なる2進数変換とは別にBCDがあるのは、小数の丸め誤差の回避の他、人間が10進数を標準で使うため、コンピュータ内部でも、10進数のまま数値を扱った方が、便利な場合も多いからである。
ゾーンビットはそれが数字である事を示す働きもする。例えばキーボードで入力された数字がまずゾーン形式になり、次に計算に適したパック形式、固定小数点数や浮動小数点数になる等、状況に応じて変換されるのである。

二進化十進表現