Akira's Commentary


基数

ここで扱うのは、数値を数字列で表現する方法(基数法)と、 異なった記数法の間での相互変換の方(基数変換)法です。 普通の生活では10を基数とした表現方法(10進法)しか使いませんが、 コンピュータを扱う場合には、2、8、16を基数とした表現方法も使われます。

基数法

10進表記

普通に数値を表現する時に使っているのは10進表記 (decimal notation)です。10進表記では各桁が 10nの重みをもっていて、桁の数字は その重みの数を示しています。 例えば、10進表記の数字列145は、

1 4 5
102 101 100

102×1+101×4+100×5

という数値(10進表記で145!)を表現しています。 10進表記の場合、各桁の重みは10の累乗になっている (左の桁は10倍の重みを持つ)わけですが、 この10を底とか基数(radix)と呼びます。 普段10を基数とした数値表現を使っているのは、 たまたま指が10本だったためです。 指の数が異なっていれば、 それに見合った基数を使っていたことでしょう。

2進表記

基数をnとした表現では、n個の数字が必要になります。 10進表記では0-9の10個の数字を使いますね。 ところで(デジタル)コンピュータでは、 最も基礎的なレベルではオンオフの二つの状態を持ちます。 つまりコンピュータにとって指の数は2本です。 このためコンピュータにとっては2を基数とする数値表現、 すなわち2進表記が一番便利な表記法になります。 コンピュータの回路的には数字はオンオフの二つなのですが、 通常は1と0の二つの数字で表現します。 例えば2進表記の10010001

1 0 0 1 0 0 0 1
27 26 25 24 23 22 21 20

27×1+26×0+25×0+24×1+23×0+22×0+21×0+20×1

という数値(10進表記なら145)を表現しています。

さて、上に出てきた10進表記の145と2進表記の10010001とは 同じ数値を表しているのですが、10進表記は3桁、 一方の2進表記は8桁になっています。コンピュータにとっては 桁数が増えても苦にならないのですが(ある程度までは)、 人間にとっては読み難くなってきます。そこで、多くの場合には 2進表記をそのまま使うのではなく、3桁づつ、あるいは4桁づつ、 区切って扱います。

8進表記

3桁づつ区切った場合は 2進数3桁は23=8なので 8を基数とした表記(8進表記)になります。 8進表記では8種類の数字が必要になります。 通常は10進表記の数字のうち、0-7を使います。 2進表記の3桁分が以下のように8進1桁に対応することになります。

2進3桁8進1桁
0000
0011
0102
0113

 

2進3桁8進1桁
1004
1015
1106
1117

上の2進表記10010001は 3桁づつ区切ると10、010、001なので、 上の表に当てはめると、 2、2、1となり、 8進表記では221となります。これは

2 2 1
82 81 80

82×2+81×2+80×1

ですので、10進数でなら (64×2)+(8×2)+(1×1)で145になることが判ります。

16進表記

一方、4桁づつ区切った場合は 2進数4桁は24=16なので 16を基数とした表記(16進表記)になります。 16進表記では16種類の数字が必要になります。 通常は10進表記の数字(0-9)とアルファベット(A-F)を使います。 2進表記の4桁分が以下のように16進1桁に対応することになります。

2進4桁16進1桁
00000
00011
00102
00113

 

2進4桁16進1桁
01004
01015
01106
01117

 

2進4桁16進1桁
10008
10019
1010A
1011B

 

2進4桁16進1桁
1100C
1101D
1110E
1111F

上の2進表記10010001は 4桁づつ区切ると1001、0001なので、 上の表に当てはめると、 9、1となり、 16進表記では91となります。これは

9 1
161 160

161×9+160×1

ですので、10進数でなら (16×9)+(1×1)で145になることが判ります。

n進小数の表現

一般にn進数は

C4 C3 C2 C1 C0
n4 n3 n2 n1 n0

として表記され、

C4×n4+C3×n3+C2×n2+C1×n1+C0×n0

という数値を表現しているわけですが、 ここで桁の重み(nの累乗)をマイナス方向に拡張すると 小数の表記になります。

C4 C3 C2 C1 C0 C-1 C-2 C-3
n4 n3 n2 n1 n0 n-1 n-2 n-3

C4×n4+C3×n3+C2×n2+C1×n1+C0×n0+C-1×n-1+C-2×n-2+C-3×n-3

例えば2進数101.011

1 0 1 . 0 1 1
22 21 20 . 2-1 2-2 2-3
4 1 . 0.25 0.125

という数値、すなわち(10進数で)5.375を表現していることになります。

基数変換

基数変換は異なった基数表記間での変換です。 情報処理試験で基数変換そのものが問題として出ることは まずありませんが、 問題を解くために途中で基数変換を必要とするものが結構あります。 変換に慣れていないとそこで時間が掛かってしまって、 他の問題を解く時間がなくなることがあるようです。 基礎自体はごく簡単なものですので、ある程度慣れておくといいでしょう。 特に2進、8進、16進の相互変換は良く出ますので慣れておきましょう。

n進数から10進数へ

ある基数で表記された数値を10進表記(普通の表記)に 変換するには、上で何度も記述しているように 桁の重み×桁の数値 の和を求めるだけです。素早く計算するには以下のような 表を作って各桁の数(Ci)、底(n)を 当てはめて計算するといいでしょう。

C4 C3 C2 C1 C0 C-1 C-2 C-3
n4 n3 n2 n1 n0 n-1 n-2 n-3

C4×n4+C3×n3+C2×n2+C1×n1+C0×n0+C-1×n-1+C-2×n-2+C-3×n-3

特に2進数を扱う場合には、各桁の数字は1か0か(有るか無いか)だけ ですので、2の累乗和で求めることができます。 プログラマは2の累乗を(かなりの範囲まで)覚えているものです。

20 1 211 2048
21 2 212 4096
22 4 213 8192
23 8 214 16384
24 16 215 32768
25 32 216 65536
26 64
27 128 220 ≒106(100万)
28 256 224 ≒1600万
29 512 230 ≒109(10億)
210 1024 232 ≒40億

10進数からn進数へ

一般的には10進数を底(基数)で割っていって、その剰余を 並べることによってnを基数とする表現に変換することができます。 10進数538を5進数、7進数に変換してみましょう。 10進数538を基数5で割っていくと以下のようになります。

10進数 基数 剰余
538 5 3
107 5 2
21 5 1
4 5 4
0

従って10進数538は、5進数では上の計算で出てきた剰余を 下から並べた4123として表記されます。 これを下の表のように10進変換すると元の値になっていることが確認できます。

4 1 2 3
53 52 51 50
500 25 10 3
500+25+10+3=538

また、10進数538を基数7で割っていくと以下のようになります。

10進数 基数 剰余
538 7 6
76 7 6
10 7 3
1 7 1
0

従って10進数538は、7進数では1366と表記されます。 これも10進変換すると元の値になっていることが確認できます。

1 3 6 6
73 72 71 70
343 147 42 6
343+147+42+6=538

上の説明で解りにくかった方は、下のフォームで10進数と基数を 入力して変換ボタンを押してみてみてください。 計算経過が表示されます。

変換する10進数と基数を入力して変換ボタンを押してください。

10進数 基数 結果
変換経過

2進、8進、16進数の相互変換

2進、8進、16進数の相互変換は原理的にはとても簡単です。 2進から8進、16進数に変換するには2進数を3桁づつ、 4桁づつ区切っていって、2進3桁を0-7に、あるいは 2進4桁を0-9A-Fに置き換えるだけで済みます。

2進数 11 111 000 001
8進数 3 7 0 1

2進数 111 1100 0001
16進数 7 C 1

また、8進、16進から2進への変換では、それぞれの 桁の値を対応する2進3桁、2進4桁に置き換えるだけです。

8進数 3 7 0 1
2進数 11 111 000 001

16進数 7 C 1
2進数 111 1100 0001

と、このように原理的にはごく簡単な対応付けだけで済むのですが、 慣れていない人だと(一々計算することになって)意外と時間が かかるようです。個々の処理はごく単純対応付けですので 下の表の2進数、8進数、16進数の対応付けぐらいは覚えておきましょう。

2進3桁 8進1桁 2進4桁 16進1桁 2進4桁 16進1桁
000 0 0000 0 1000 8
001 1 0001 1 1001 9
010 2 0010 2 1010 A
011 3 0011 3 1011 B
100 4 0100 4 1100 C
101 5 0101 5 1101 D
110 6 0110 6 1110 E
111 7 0111 7 1111 F

ネットマスク計算(FE,AP)

2進数と10進数の相互変換は実際上はほとんど使われないのですが、 現時点で唯一に近いケースがIPネットワークのネットマスク設定です。 IPネットワークでは32ビットのアドレスをネットワークアドレス部と ホストアドレス部に分割して使います。 この時にどこまでがネットワークアドレスかを ネットマスクという値で指定します。 概念的にネットマスクは32ビットのIPアドレスを2進数で表記した時に、 ネットワークアドレス部分を(2進数の)1で示したものです。

ですがどのような理由か、ネットマスクを指定する場合には伝統的に 8ビット毎に区切って10進数で指定することになっています。 このため2進数でイメージされるマスク値を 10進数に変換しなければなりません。 ただこれも限られたパターンですので、 ネットワーク系のエンジニアであれば、 以下のパターンを暗記しているものです。

サブネット長ネットマスク10進表現
8ビット000000000
7ビット10000000128
6ビット11000000192
5ビット11100000224
4ビット11110000240
3ビット11111000248
2ビット11111100252
1ビット11111110254

出題傾向と過去問題

レベル 出題傾向 過去問題
IP シラバスでは2進10進変換レベルとなっていますが、実際には 小数点を含む2進数、16進8進変換が出題されたことがあります。 しか出題されないこともありますので、必須知識というより、 知っていれば点数が稼げる、といったレベルの扱いのようです。 2010年春 2009年秋
FE FEレベルでは当然知っているべき知識として扱われ出題されています。 このところ、最初の問題が16進小数についてになっています。 2010年秋
2010年春
AP APレベルでは知っていて当然の知識という扱いのようです。 ですが、基数法/基数変換についての知識を直接に問う 問題は出題されないようです。

数値の表現

この節ではコンピュータ上で数値がどのように表現されるのかを 説明していきます。 コンピュータ上であらゆるデータはオンオフの2値(ビット)の 組み合わせで表現されます (ですから上で出たように2進表現が重要になります)。 無限長の2進表現が使えるなら任意の数を表現することができるのですが、 実際には使えるビット数(桁数)に制限がありますし、 ハードウェアの制約、処理の都合により、 ある決まった形式で表現されるようになっています。 なお、これらの形式によって表現される数値は、 それぞれの表現形式によって決まってくる、 表現可能な範囲、精度、といったものがあります。 以下では表現形式と範囲、精度を含めて説明していきます。

自然数の表現

コンピュータ(プログラム言語)では 0を含めた自然数(natural number)を 符号なし整数(unsigned integer)と呼んで 一群のデータの種類として扱います。 これはまた序数(ordinal number)、個数(cardinal number)とも 呼ばれます。

自然数は(多くの場合)、 コンピュータ上ではひと塊のビットの集まりを使い、 ビットの状態0、1による2進数として表現されます。 多くのコンピュータでは8ビットを単位として扱っているので、 自然数も8ビット、16ビット、32ビット、64ビット、 の2進数で表現されます。

8ビット符号無し整数は 以下のように8ビットで自然数を表現します。 表現可能な数値は2進表現で 00000000から11111111、 10進では0から255となります。

27 26 25 24 23 22 21 20

16ビット符号無し整数は 以下のように16ビットで自然数を表現します。 表現可能な数値は2進表現で 0000000000000000から1111111111111111、 10進では0から65535となります。

215 214 213 212 211 210 29 28 27 26 25 24 23 22 21 20

32ビット符号無し整数64ビット符号無し整数は、 桁が多すぎるので途中を省略してますが、 以下のように32ビット、64ビットで自然数を表現します。

231 230 229 228 227 226 225 224

・・・

27 26 25 24 23 22 21 20

263 262 261 260 259 258 257 256

・・・

27 26 25 24 23 22 21 20

それぞれ表現可能な範囲は

32ビット符号無し整数 0 232-1 およそ4×109(40億)まで
64ビット符号無し整数 0 264-1 およそ16×1018(1600京)まで

となります。

整数の表現

自然数を拡張して 負の数も表現できるようにしたものが整数(integer)です。 負の数を表現するにはいろいろな方式が考えられますが、 コンピュータでは以下の方式が使われてきました。

  • 符号+仮数方式
  • 1の補数表現
  • 2の補数表現
  • バイアス表現

現在、整数値を表現するには、 (コンピュータのハードウェア的に都合がいいので) もっぱら2の補数表現が使われています。 他の表現方法は、このような方法でも表現できる、 程度のものであまりまじめに覚える必要はありません。

全く使われていないわけではありません。 後述の浮動小数点数表記で、 指数がバイアス表現で格納されています。

符号+仮数方式は、符号無し整数の1ビットで 正負の符号を示し、残りのビットで数値(仮数)表現する方式です。 8ビット長の場合には以下のようになります。 表現可能な範囲は-127 ~ +127です。

+/- 26 25 24 23 22 21 20

1の補数表現は正の整数のすべてのビットを 反転させたものを負の整数とするものです。 8ビット長の場合には以下のようになります。 表現可能な範囲は-127+127です。

正の数負の数
+0 0 0 0 0 0 0 0 0   -0 1 1 1 1 1 1 1 1
+1 0 0 0 0 0 0 0 1   -1 1 1 1 1 1 1 1 0
+127 0 1 1 1 1 1 1 1   -127 1 0 0 0 0 0 0 0

2の補数表現は、1の補数に1を加えたものになります。 8ビット長の場合には以下のようになります。 表現可能な範囲は-128+127です。

正の数負の数
+0 0 0 0 0 0 0 0 0   -0 0 0 0 0 0 0 0 0
+1 0 0 0 0 0 0 0 1   -1 1 1 1 1 1 1 1 1
+127 0 1 1 1 1 1 1 1   -127 1 0 0 0 0 0 0 1
  -128 1 0 0 0 0 0 0 0

バイアス表現は、 本来の値に一定の値を加えて自然数にしたものを コンピュータ上での値とするものです。 逆に本体の値はコンピュータ上の値(自然数)から バイアス値を引いたものになります。 バイアス値は使う方の都合に合わせてどのような値でも選べるのですが、 一般的には、n桁の自然数に格納する場合には 2(n-1)を 使います(正負の範囲がほぼ同じになるように)。 8ビット長の自然数で格納する場合には2(n-1)=128を 加えて以下のように表現します。バイアス値128の場合には 表現可能な範囲は-128+127です。

ビット列 コンピュータ上の値 実際の値
最大 1 1 1 1 1 1 1 1 255 127
+1 1 0 0 0 0 0 0 1 129 1
0 1 0 0 0 0 0 0 0 128 0
-1 0 1 1 1 1 1 1 1 127 -1
最小 0 0 0 0 0 0 0 0 0 -128

これでは違いが解り難いかと思いますが、 並べて見ると違いがはっきりします。

符号+仮数 1の補数表現 2の補数表現 バイアス表現
最大値 01111111(127) 01111111(127) 01111111(127) 11111111(127)
1 00000001 00000001 00000001 10000001
+0 00000000 00000000 00000000 10000000
-0 10000000 11111111
-1 10000001 11111110 11111111 01111111
最小値 11111111(-127) 10000000(-127) 10000000(-128) 00000000(-128)

今時のコンピュータではほぼ2の補数表現によって 負の数を表現するようになっています。 これは、2の補数表現を使うと、 減算と負の数の加算とが同じ計算ハードウェアで実現できるためです。 下の表は、5-35+(-3)を 自然数と同じ論理で計算した場合の結果です。2の補数表現の時だけ (キャリーを無視すれば)正しい答えが得られています。 2の補数表現以外の形式ではそれぞれ専用の 計算ハードウェアを用意しないと計算が成り立ちません。

符号+仮数 1の補数表現 2の補数表現 バイアス表現
5 00000101 00000101 00000101 10000101
3 00000011 00000011 00000011 10000011
5-3 00000010(=2) 00000010(=2) 00000010(=2) 00000010(=-126)
5 00000101 00000101 00000101 10000101
-3 10000011 11111100 11111101 01111101
5+(-3) (0)10001000(=-8) (1)00000001(=1) (1)00000010(=2) (1)00000010(=-126)

最後に各サイズの符号無し整数、および整数(2の補数表現)での 表現可能な範囲をまとめておきます。

サイズ 最小 最大
8ビット符号無し整数 0 28-1 (255)
8ビット整数 -(27) (-128) 27-1 (127)
16ビット符号無し整数 0 216-1 (65535)
16ビット整数 -(215) (-32768) 215-1 (32767)
32ビット符号無し整数 0 232-1 (≒42億)
32ビット整数 -(231) (≒-21億) 231-1 (≒21億)
64ビット符号無し整数 0 264-1 (≒1844京)
64ビット整数 -(263) (≒-922京) 263-1 (≒922京)

小数の表現

小数を含んだ数値を表現するには、 数値を整数部と小数部に分離して表現する 固定小数点表記と、 数値を仮数と指数に分離して表現する 浮動小数点表記とが使われます。 コンピュータのハードウェアレベルでは多くの場合 浮動小数点表記が使われています。 固定小数点表記は 一部のライブラリ、ランタイムでのみ使用されています。

浮動小数点数

コンピュータのハードウェアレベルでは浮動小数点表記によって 小数(を含む数値)が表現されます。 浮動小数点表記は数値を(符号と)仮数、指数によって表記する方式で、 10進数の世界では 1.024×103 といった具合に表記する形式です。 ここで、1.024が仮数(fraction)、 10が基数(base or radix)、 3が指数(exponent)と呼ばれます。 数値は、基数、指数、仮数(および符号)を使って 数値=符号×仮数×基数指数 で表現されます。

コンピュータ上では2または10を基数とした浮動小数点数が使われます。 いくつかの記録形式が使われてきましたが、現在では、 IEEE(アイトリプルイー)で規定された IEEE 754が 事実上の標準形式になっています。 IEEE754では以下の7形式を規定しています。 よく使われるのは単精度、および倍精度の二つですが、 最近になっては4倍精度も使われるようになってきています。 Decimal形式はIEE754の2008年改定で追加された形式で 今のところあまり使われていません。 また複雑なエンコーディングを使っていますので、ここでは 説明を省略しておきます。詳しく知りたい方は 英語版WikiPediaの解説 (decimal32decimal64decimal128) をご覧になってください (日本語版では今のところ記載されていません)。 日本語での解説は、まだ規格案の頃の記事ですが、 10進浮動小数点演算10進数の演算ハードウェア が参考になるでしょう。

形式名 通称 基数 指数の桁数 指数の範囲 仮数の桁数
binary16 半精度 2 5 2-14~2+15 10+1
binary32 単精度 2 8 2-126~2+127 23+1
binary64 倍精度 2 11 2-1022~2+1023 52+1
binary128 4倍精度 2 15 2-16382~2+16383 112+1
decimal32 10 - 10-95~10+96 7
decimal64 10 - 10-383~10+384 16
decimal128 10 - 10-6143~10+6144 34

バイナリ形式はごく簡単な構造で、以下の図のように、 1ビットの符号部、精度毎に異なった桁数(ビット数)の 指数部、仮数部から構成されています。 指数部、仮数部の桁数は上の表に記載されている通りです。

符号指数部仮数部
0/1

バイナリ形式の場合、浮動小数点数は仮数部の先頭が1になるように 仮数、指数が正規化されて表現されます。例えば、2進表記で 0.01011となる数の場合には

仮数指数
0.010110
0.1011-1
1.011-2

というよう変形されて、仮数が1.011、 指数が-2が正規形になります。 正規化された場合、仮数部の先頭は常に1になりますので、 表記上は先頭の1を省略し、残りの部分(小数点以下の部分)だけを 記載します。 これによって、仮数部では桁数+1の仮数が表現されることになります (これが上の表の桁数で+1されている部分です)。

指数部も2進数で記録されますが、 指数自体の正負があると扱いが面倒になるため、 指数部はバイアスを加えた符号なしの整数として記録されます。 バイアス値は、指数部の桁数をeとした時に 2e-1-1 となります。

固定小数点数

固定小数点表記は数値を整数部と小数部に分けて表記する方式です。 例えば、8ビットの整数部と8ビットの小数部を使うと数値は

整数部 小数部
27 26 25 24 23 22 21 20 2-1 2-2 2-3 2-4 2-5 2-6 2-7 2-8

という形式で表記されます。 固定小数点表記の場合、 表現可能な数値の範囲は整数部のビット数で、 精度は小数部のビット数で決まります。 上のような8ビット整数部、8ビット小数部の場合には 以下のようになります。

整数部小数部精度 最大値最小値
8ビット8ビット 2-8 27-2-8 -(27)

こうして表にすると複雑な形式に見えますが、固定小数点数表記は、 本来の数値を2n倍して整数化して メモリ上に格納しているだけに過ぎません。 普通の整数では小数点は桁の末尾に位置しているわけですが、 固定小数点数では、小数点位置が左に移動しているだけです。 従って、固定小数点数の計算は整数の計算ロジックをそのまま 流用することができますので、極めて高速に(浮動小数点数との比較で) 実行されます。 このため、精度はそれほど重要視されないものの 速度が重視されるようなケース、例えばグラフィック系の座標計算、 などで良く使われています。

VBのMoney型は、整数部60ビット、小数部4ビットの固定小数点表記に なっているようです。 ちなみに、VBから.NETへの移行でMoney型の代用として推奨されている .NETのDecimal型はまた別の もう少し複雑な形式 (整数小数の割付が可変だそうで)になっています。

BCD表現

BCDは Binary Coded Deciaml の略で、 二進数でコード化された10進数という意味になります。 概念的には10進数の各桁を2進表現して、 元の10進数の桁分並べたものです。 例えば 1729 という10進数では、 それぞれの桁を2進数で表現すると

10001
70111
20010
91001

となりますので、これを並べたものが 10進数 1729 の BCD 表現ということになります。

実際に使われる状況では上のように単純に並べるのではなく、 計算機システム毎に(大体はメーカ毎)微妙に異なった並べ方になります。 最初にBCDが使われたIBMのシステム(IBM 1401 )は 処理単位が6ビットのマシンだったので、 上の4ビットの数値表現部(ディジットビット)に 2ビット分のゾーンビットと呼ばれる領域を付加して表現されていました。

現在のマシンでは処理単位は概ね8ビットになっていますので、 ゾーンビット4ビット+数字ビット4ビットの 8ビット/桁で表現されています。 このように未だにゾーンビットと組み合わせて使われる形式を ゾーンBCD,または、アンパックドBCDと呼ばれています。

しかし、処理単位が8ビットになったなら、 その中に数字部(4ビット)を二つ詰め込んで メモリを節約することもできます。 このようにゾーンビットを省略して詰め込んだ形式は パックドBCDと呼ばれます。並べると以下のようになります。 ゾーンBCDの最後の 1100 は符号の表現です (がこれは各社勝手に割りつけていて互換性はありません)。

10進数 1 7 2 9
ゾーンBCD 1111 0001 1111 0111 1111 0010 1100 1001
パックドBCD 0001 0111 0010 1001

今ではBCDは滅多に使われなくなっています。 が、BCDを使うと外部で使用される10進数が そのまま内部でも使われますので 基数変換に伴う誤差(後述)を防止することができます。

例えば、10進数の 0.1 は2進数では循環小数になってしまうので、 実は二進変換を行なっただけで誤差が発生してしまいます。

0.5 0
0.25 0
0.125 0
0.0625 10.0625
0.03125 10.09375
0.015625 0
0.0078125 0
0.00390625 10.09765625
0.001953125 10.099609375
0.00097656250

このため10進数レベルの誤差が困るような状況、 実際問題としては金額を扱う場合が殆どですが、 では変換誤差が発生しないBCD形式が未だに使われ続けています。

余談:パンチカード

今では知らない人の方が多くなっていそうですが、 IBMは元々はパンチカードによる統計、計算システムを 制作していた会社で、 データをパンチカード (参考画像) に記録していました。 パンチカードは縦に12段のパンチ欄があり、 下10段が数字に対応していてこれがディジット部、 上2段がゾーンと呼ばれていました。 そう、これがBCDのゾーン、ディジットの言葉の由来です。 ちなみに1行80桁というのもパンチカードの名残です。

余談:EBCDICコード

BCDは基本は数字だけしか表記できませんが、IBMはこれを拡張して EBCDIC(Extended BCD Interchange Code)を定義して 文字コードとして使用していました。汎用機の世界では 未だにこのコード(あるいはその各社独自の拡張)が使われ続けています。 当初EBCDICでは数字とアルファベットの大文字しか 規定されていませんでした。 現在使われているEBCDIC系のコードでは 小文字やその他の記号も使えるようになっていますが、 これらは各社が勝手に拡張しているものなので、 数字、大文字以外については互換性が保証されません。

余談:大文字の世界

昔の映画や小説なんかで、コンピュータとのやりとりが 全部大文字になっているのはこういう 歴史的背景によるものです。 MS-DOS、Windowsでファイル名で大文字小文字が区別されないのも、 このような大文字だけのシステムがあった所為です。 ちなみにUNIX系は当初から大文字小文字混在の ASCIIコードベースになっていたため、 ファイル名で大文字小文字が区別されるようになっています。

出題傾向と過去問題

レベル 出題傾向 過去問題
IP ここしばらくは出題されていません。基本的にこの部分の知識は コンピュータ内での表現、保持、についてのものですので、 パスポートレベルでは不要と判断されているのかも知れません。
FE 基本知識と看做されているのかあまり出題されません。 少し難しめのところで浮動小数点数の形式を問う問題が たまに出題されるようです。 逆にいうと、浮動小数点数の形式以外は 当然の知識と看做されているのでしょう。 2009年秋
AP こちらも当然の知識と看做されていて滅多に出題されていません。 しかしFEと同様に、少し難しめの浮動小数点数の正規化と格納に ついての問題が出題されたことがあります。 あと、なぜAPレベルで、ですが2の補数表現の理由を問う問題が 出題されたこともあります。 2010年春
2009年秋

算術演算と精度

この節では、上のような表現でコンピュータ上に格納された数値に対して、 どのように算術演算(加減乗除)が行なわれるかを説明していきます。 対象となるのは 自然数(符号無し整数)整数(符号付き)浮動小数点数の3種類です。

自然数の演算

ここでは自然数(正の整数、あるいは符号無し整数)についての 、 および シフト演算 について説明します。 減算、除算では結果が自然数の範囲外になることがありますので この節での説明は演算の一部だけに限定しています。

自然数の加法

基本的には10進数の加法と同じ形式で行なうことができます。 違うのは(2進数ですから)2で桁上がりが発生することだけです。

1110
101
1 1 Carry
1 0011

自然数の減法

これも基本的には10進数の減法と同じ形式で行なうことができます。 ただここでは x≥ y の場合の x-y についてのみ説明します。 結果が負になる減算については 整数の演算の節で扱います。

1110
0 Borrow
101
1001

自然数の乗法

これも基本的には10進数の乗法と同じ形式で行なうことができます。 ただ2進数の場合には乗数の各桁の値が0か1しかありません。 0なら何もなし、1ならそのまま、ですので、この段階での計算は 10進数に比べるとずっと簡単になります。

1110
× 101
1110
11 10
100 0110

自然数の除法

これも基本的には10進数の除法と同じ形式で行なうことができます。 自然数の除法では結果は自然数の商と剰余の組で表されます。 結果が小数点数になる除算については 浮動小数点数の演算の節で扱います。

10
101 1 1 1 0
1 0 1
100 剰余

自然数のシフト演算

2進数についてはシフト演算も良く使われます。シフト演算は、 2進の数字列を左右に移動させる演算です。 例として2進数 10010100 を左右にシフトさせてみましょう。

左シフト

2102928 27262524 23222120
元の数 1001 0100
左へ1ビットシフト 1001 01000
左へ2ビットシフト 1001 010000

右シフト

2102928 27262524 23222120
元の数 1001 0100
右へ1ビットシフト 1001 010
右へ2ビットシフト 1001 01

ここで覚えておくべき点は、数値が、 左1ビットシフトによって元の数が2倍に、 左2ビットシフトによって元の数が4倍に、 そしてまた 右1ビットシフトによって元の数が1/2に、 右2ビットシフトによって元の数が1/4に、 なっている点です。 一般に、nビットの左シフトは元の数を2n倍することに、 nビットの右シフトは元の数を1/2nにすることに相当します。

ここで、 上の11102(1410)と1012(510)の 掛け算を思い出してください。

1110
x 101
(A) 1110
(B)1110
101101

掛け算 11102×1012 は 以下のように展開することができます。

11102×1012  =  11102×(1002+12)
 =  (11102×1002)+(11102×12)
 =  (11102×2102)+(11102×110)

こうして展開した (11102×2102)が上の式の(B)に、 (11102×110)が上の式の(A)に、 相当するのですが、×2nは(左)シフト演算で 置き換えることができます。ここで (B)は被乗数を左に2ビット、(A)は被乗数を左に0ビット シフトされたものと同じです。 つまり、自然数の掛け算は、シフト演算と加法の組み合わせでも 実現できる、ということです。

一般にコンピュータでは、乗算よりもシフト演算の方が圧倒的に速く 処理できるようになっています。このため、掛け算をシフトと加法の 組み合わせで実現する方法は、高速に計算するために良く使われる 手法になっています。

このためか、情報処理試験でもシフト演算を使った掛け算に関する 問題が、かなりの高確率で出題されています。

自然数のオーバーフロー

実際にコンピュータ上では自然数は決まった桁数の2進数で格納、 保持されます。このため、演算の結果がその桁数を超過してしまって、 結果が意図したものと異なってくることがあります。 このような、結果の桁数がコンピュータの制限を越えてしまうことを オーバーフローと呼び、 これによる計算の誤差(意図との乖離)をオーバーフロー誤差と呼びます。

自然数の演算では加法、乗法で オーバーフローが発生する可能性があります。 オーバーフローの発生はCPUレベルで検出可能になっていることが多いのですが、 多くの場合、特に自然数、符号無し整数の演算ではだいたい無視されます。 というのも、前もって出現する数値の範囲が判っていれば オーバーフローの発生を防ぐことができる、オーバーフローを無視した 計算システムにもそれなりの使い道がある、ためで、 計算の度に一々オーバーフローチェックするのは 無駄だと考えられているためです。

オーバーフローを無視して固定のビット数で2進演算を行なう 計算システムは剰余系と呼ばれるものです。これは、8ビットの 2進数でオーバーフローを無視すると、その結果は本来の数値の 28による剰余に相当するためです。剰余系は 乱数生成や暗号系でよく使われています。また整数で負の数の 表現に利用されている2の補数表現も剰余系を利用したものです。

2の補数表現を使って減算(補数に変換して加算)を行なう場合、 計算の度にオーバーフローが発生していることになります。 これを一々チェックしていると、無駄が多くなってしまいます。

整数の演算

ここでは符号付き整数についての 、 および シフト演算 について説明します。 減算、除算では結果が整数の範囲外になることがありますので この節での説明は演算の一部だけに限定しています。

整数の表現

コンピュータ上で整数は決まったビット数(8、16、32、64)の  ビット列で表現され、先頭ビットが0の範囲を正の整数として扱い、 負の数は正の整数に対する2の補数によって表現されます。例えば ビット長が8の場合には以下のように表現されることになります。

±262524 23222120
最大値 0111 1111
1 0000 0001
0 0000 0000
-1 1111 1111
最小値 1000 0000

先頭ビットは、正の数では0、負の数では1になり、 ここを見るだけで正負の判断ができます。しかし これは、常に規定のビット数で扱わなければならない、 ということでもあります。整数の演算では、そのデータが 何ビットの整数かを常に意識していなければなりません。

以下の計算例では表記の都合上、基本的には8ビット整数で 例を示していきます。多く使われる整数は8、16、32、そして 最近では64ビット長ですが、考え方はどれでも同じです。

整数の加法

計算そのものは自然数の加法と全く同じです。しかし、整数の場合には 超過したビットが切り捨てられますし、先頭ビットが変化した場合には 正負が反転したものと評価されます。

超過ビットの切り捨て
0110 0110 ... 10210
+ 1100 1101 ... -5110
1 0011 0011 ... 5110
正負反転
0110 0110 ... 10210
+ 0011 0011 ... 5110
1001 1001 ... -10310

整数の減法

自然数の減算と同じように計算することもできますが、 2の補数表現で正負を反転させて、負の数の加算として 計算することもできます。コンピュータでは一般に 正負反転と加算の組み合わせを使います (こうすれば減算用の回路を用意しないで済みますので)。  

減算としての計算
0110 0110 ... 10210
- 0011 0011 ... 5110
0011 0011 ... 5110
正負反転させて加算
0110 0110 ... 10210
+ 1100 1101 ... -5110
1 0011 0011 ... 5110

整数の乗法

これも基本的には自然数の掛け算と同じ方法で計算できます。 問題になるのは負の数が絡んだ時です。ただ、負の数が 2の補数表現であれば、そのまま自然数とおなじように計算して 超過したビットを切り捨てても、負の数を2の補数で正負反転させて 計算して、最後に再度正負を反転させても、同じ結果が得られます。

負の数のまま乗算
0000 1101 ... 1310
× 1111 1101 ... -310
0000 1101
0000 1101
0000 1101
0000 1101
0000 1101
0000 1101
0000 1101
0000 1101
0001 1001 1101 1001
1101 1001 ...-3910
正負反転させて乗算後再度正負反転
0000 1101 ... 1310
× 0000 0011 ... 310
0000 1101
0 0001 101
0 0010 0111 3910
1101 1001 -3910

整数の除法

整数の割り算は、正の数同士の場合には自然数と同じなのですが、 負の数が関わってくるとなかなか判りにくくなります。 例えば、(-5)÷2は、商-3余1なのか、商-2余-1なのか実に混乱しやすい 構造になっています(有理数に拡張されてしまえば簡単なのですが)。 一般的に割り算 m÷n=q...r は、

m = q×n + 1 但し 0 ≦ r < n

という具合に剰余に非負条件を付けて規定されています。 これにより、(-5)÷2は、商-3余1 が正しい答えということになります。 しかし、除数nが負の場合には上の式を満たすrは存在しません。 このため、実際には上の付帯条件(但しの部分)とは別の条件で 規定される除算も広く使われています。

実際問題としては、負の数が絡んだ整数の割り算がどのように 計算されるか(どのような商と剰余が得られるか)は、マシン、 システム、によって異なっていることがありますので、 実際にどのような値になるかを確認した上で使う、あるいは そのような不安定な演算は使わないようにする、といった 工夫が必要になってきます。

ちなみに手元のVS2010のC#では、
-5 ÷ 2-2 ... -1
5 ÷ -2-2 ... +1
となります。 自然数で商を求めてから、商の符号、剰余を調整する流儀のようです。

シフト演算

シフト演算についても、対象が符号付き整数となると、 符号をどのように扱うかによって、演算方法が変わってきます。

最も単純な演算は符号を無視する(気にしない)もので、 論理シフト演算(logical shift operation)と呼ばれます。 左右にはみ出したビットは捨てられ、移動によって空いた桁には0が入ります。

左論理シフト
符号無し整数 符号付き整数
元の数 0110 0011 9910 9910
1ビット左シフト 0 1100 0110 19810 -5810
2ビット左シフト 01 1000 1100 14010 -11610
右論理シフト
符号無し整数 符号付き整数
元の数 1100 0110 19810 -5810
1ビット右シフト 0110 0011 0 9910 9910
2ビット右シフト 0011 0001 11 4910 4910

符号ビット(左端のビット)を意識するシフト演算は 算術シフト演算(arithmetic shift operation)と呼ばれます。 困ったことに、右算術シフトの動作に関してはどのマシンでも 同じになっているのですが、左算術シフトに関してはマシンに よって動作が異なっています。

右算術シフトでは、符号ビット以外の部分を右にシフトし、 空いたビットには符号ビットと同じ値が入ります。

右算術シフト
符号無し整数 符号付き整数
正の数 0100 0110 7010 7010
1ビット右シフト 0010 0011 0 3510 3510
2ビット右シフト 0001 0001 11 1710 1710
負の数 1100 0110 19810 -5810
1ビット右シフト 1110 0011 0 22710 -2910
2ビット右シフト 1111 0001 11 24110 -1510

左算術シフトに関しては、左論理シフトと同じで符号ビットを無視して 左に移動するものと、符号ビットの内容は残してそれ以外の部分を 移動させるものとがあります。

左算術シフト - 符号ビット無視

符号ビットを無視するものは原理的に 左論理シフトと全く同じ動作になります。

符号無し整数 符号付き整数
正の数 0010 0110 3810 3810
1ビット左シフト 0 0100 1100 7610 7610
2ビット左シフト 00 1001 1000 15210 -10410
負の数 1100 0110 19810 -5810
1ビット左シフト 1 1000 1100 14010 -11610
2ビット左シフト 11 0001 1000 2410 2410
左算術シフト - 符号ビット維持

符号ビットを維持した左シフトは、符号ビット以外の桁について 左シフトを行なうものです。

符号無し整数 符号付き整数
正の数 0010 0110 3810 3810
1ビット左シフト 0 0100 1100 7610 7610
2ビット左シフト 01 0001 1000 2410 2410
負の数 1100 0110 19810 -5810
1ビット左シフト 1 1000 1100 14010 -11610
2ビット左シフト 10 1001 1000 15210 -10410

実際のところ、左算術シフトでは符号を無視しようと維持しようと、 いずれにしても数値の範囲を越えた場合には、 変な値になってしまいます。このため、符号を維持するための特別な 処理を省略してしまって、左シフトについては論理算術シフトを同じ 処理にしてしまっているものが多いようです。

いずれにせよ、シフト演算では簡単に数値の表現可能な範囲を越えて しまって予想外の値になってしまうことがあるので、 実際に使う場合には取り得る数値の範囲を充分に把握した上で 使うように注意しなければなりません。

ローテート演算

シフト演算では左右にはみ出した桁は単純に捨てられていましたが、 それを移動方向とは反対側の空きビットに入れていく演算もあります。 この場合にはビット列全体が輪になって移動するイメージなので、 ローテート演算と呼ばれます。ローテート演算では数値としての値は あまり意味を持ちません。ビットパターンとしてのみの扱いになります。

最も単純なローテート演算では、はみ出したビットが反対側の空きビットに 設定されます。

左ローテート
元の数 0110 0011
左へシフト 0 1100 011
右端へ移す 1100 0110
右ローテート
元の数 0110 0011
右へシフト 011 0001 1
左端へ移す 1011 0001

もうひとつはキャリー付きローテートと呼ばれるもので、 以前にはみ出したビットを保持しているキャリーの内容を 含めてローテートさせるものです。

キャリー付き左ローテート
C
元の数 1 0110 0011
左へシフト 0 1100 011
Carryを移動 0 1100 0111
キャリー付き右ローテート
C
元の数 0110 0011 0
右へシフト 011 0001 1
Carryを移動 0011 0001 1

浮動小数点数の演算

ここでは浮動小数点数についての について説明します。

浮動小数点数の表現

浮動小数点数の格納形式については こちらをご覧ください。 以下の演算の解説では(桁数が大きいと例示し難いので) 半精度の浮動小数点数を使って説明していきます。 半精度の浮動小数点数は、符号部1ビット、指数部5ビット、 仮数部10ビットで表現される浮動小数点数です。

符号指数部仮数部
0/1

符号ビットは0が正の数、1が負の数を示します。 指数部は5ビット幅ですので、25-1-1、 すなわち、15のバイアス表現による整数値で示されます。 仮数部には正規化された仮数(の先頭の1を除外したもの)が 格納されます。実際の値は以下のように格納されます。

符号指数部仮数部
0 10010 01000 00000
+18-15=31.0100000000

上のように格納された数は +1.012×2103、 すなわち、10102を表していることになります。

浮動小数点数の加法

浮動小数点数同士の加算では、 指数部が同じ値になるように仮数部を調整した上で、 仮数部同士の加算を行ない、 結果を正規化して格納し直す処理を行ないます。

例として1.010×23+1.100×2-2を 計算してみましょう。これらの数は以下のように格納されています。

符号指数部仮数部
1.010×23 0 10010 01000 00000
1.000×2-2 0 01101 10000 00000

指数表記に直すと以下のようになります。

1. 01000 00000 ×23
+ 1. 10000 00000 ×2-2

この段階では指数が異なっていますので仮数部を加算することはできません。 指数が同じになるように調整します。指数が同じになったところで 仮数部を加算します。

1. 01000 00000 ×23
+ 0. 00001 10000 ×23
1. 01000 11000 ×23

こうして得られた結果を正規化して (上のケースでは正規化された状態になっています) 格納し直すことによって加算が完了します。

符号指数部仮数部
0 10010 01000 10000

上のケースでは計算がうまく行きましたが、実は計算がおかしくなる ケースもあります。次に 1.1111010000×29(100010)と 1.0000000000×2-2(0.2510)を足してみましょう。

1. 11110 10000 ×29
+ 1. 00000 00000 ×2-2

指数を揃えるためには、下の数を仮数部を11ビット右に シフトさせることになります。そうすると、以下のように 加える数の方の仮数部が溢れて0になってしまい、 結果は元の数のままになってしまいます。

1. 11110 10000 ×29
+ 0. 00000 00000 ×29
1. 11110 10000 ×29

加算する浮動小数点数の指数の差が仮数部の桁数を超える場合には このようなエラーが発生します。ここまで行かなくても、指数の差に よって仮数部の一部が失われてしまうことがあります。 このような原因で発生する計算誤差は「情報落ち」誤差と呼ばれています。 これは浮動小数点演算には付き物のエラーなので、浮動小数点数を 使う場合には注意してください。 誤差については後ほどまとめて解説します。

浮動小数点数の減法

基本的には浮動小数点数の加算と同じ仕組で演算されます。 指数部が同じ値になるように仮数部を調整した上で、 仮数部同士の減算を行ない、 結果を正規化して格納し直す処理を行ないます。

例として1.010×23-1.100×2-2を 計算してみましょう。これらの数は以下のように格納されています。

符号指数部仮数部
1.010×23 0 10010 01000 00000
1.000×2-2 0 01101 10000 00000

指数表記に直すと以下のようになります。

1. 01000 00000 ×23
- 1. 10000 00000 ×2-2

この段階では指数が異なっていますので仮数部を加算することはできません。 指数が同じになるように調整します。指数が同じになったところで 仮数部を減算します。

1. 01000 00000 ×23
- 0. 00001 10000 ×23
1. 00110 10000 ×23

こうして得られた結果を正規化して (上のケースでは正規化された状態になっています) 格納し直すことによって減算が完了します。

符号指数部仮数部
0 10010 00110 10000

減算も手法的には加算と同じなので、 二つの数の指数が大きく異なっていた場合には、 指数を合わせるための仮数部のシフト処理によって、 情報落ち誤差が発生します。

浮動小数点数の乗法

浮動小数点数表記は乗法に適した表記方法でして、乗法が一番簡単に 処理できるようになっています。浮動小数点数での乗法は、二つの 数の、符号部同士の掛け算、指数部同士の和、仮数部同士の積、 を求めるだけで計算できます。但し結果についての正規化を 再度行なう必要があります。

ここでは例として 1.1111010000×29(100010)と -1.1000000000×2-2(0.37510)の積を求めてみましょう。 これらの数は以下のように格納されています。

符号指数部仮数部
1.1111010000×29 0 11000 11110 10000
-1.1000000000×2-2 1 01101 10000 00000

符号部は+(0)と-(1)の積なので-(1)になります。 指数部は本来の値に対してバイアス値15が加わっていますので、 両者の単純な和 100101 (にはバイアス値が2つ分含まれています) から 余分な15を引いた値、10110 になります。 仮数部同士の積は以下のように求められます。

1 11110 10000
1 10000 00000
1 11110 10000
1 11110 10000
10 11101 11000 00000 00000

仮数部は10桁なので、上の結果の下20桁が小数点以下になりますから 小数点を調整すると以下の値になります。

10. 11101 11000 00000 00000

これと指数部 10110 を正規化します。

10. 11101 11000 00000 00000 指数 10110
1.0 11101 11000 00000 00000 指数 10111

こうして得られた値を半精度浮動小数点数に設定すると以下の 結果が得られます。

符号指数部仮数部
1 10111 01110 11100

これは、 -1.01110111 × 28 すなわち -37510となります。

浮動小数点数の除法

除法の計算方法も基本的には乗法と変わりませんが、 指数については減算を行ない、仮数部については商を求めます。

ここでは例として 1.1111010000×29(100010)を 1.1001×24(2510で割ってみましょう。 これらの数は以下のように格納されています。

符号指数部仮数部
1.1111010000×29 0 11000 11110 10000
1.1010000000×24 0 10011 10010 00000

符号部は+(0)と+(0)の積なので+(0)のままです。 指数部は本来の値に対してバイアス値15が加わっていますので、 両者の単純な差 101 (にはバイアス値失われています) にバイアス値を加えた値、 10100 になります。 仮数部の商は以下のように求められます。

1.01
1.1001 1.1111 010000
1.1001
110 010000
110 01
000 000000

今回商は正規化された状態(第一桁が1)になっていますので、 得られた符号、指数、仮数を半精度浮動小数点数に設定すると 以下の結果が得られます

符号指数部仮数部
0 10100 01000 00000

これは 1.01 × 25、すなわち 1010002で 4010になります。

実際の浮動小数点数演算

浮動小数点数の演算は、 基本的には上で記述したような手法で計算できるのですが、 実際のコンピュータではHWの演算機構、あるいは専用に作成された 演算用のライブラリによって行なわれます。それらの演算機構、 ライブラリでは、上のような基本手法で素直に計算するのではなく、 各社それなりの工夫を凝らした手法で演算を行なっています。 特に、乗算、除算については、それらの性能がコンピュータの 基本性能として評価されることもあって、 未だに改良が続けられています。

浮動小数点演算の性能は FLOPS (Floating point number Operations Per Second) という 指標で示されます。ちなみに現行のマシンでは

PSP2.6-9.6 GFLOPS
Core i751.20GFLOPS
PS32TFLOPS
地球シミュレータ122.40TFLOPS
IBM Roadrunner1.105PFLOPS

といった性能になっています。現在は10P(Peta=1015)FLOPS あたりが開発の目標値になっています。

演算の精度と誤差

T.B.D

集合

T.B.D

論理演算

T.B.D