飯島 裕一(株式会社 SIT11)
これは、10進数が、そもそもCOBOL以外の言語ではほとんど見かけないということと、 10進数は理解ができるものの「外部」「内部」などのなじみない言葉が独特なデータ 表現形式を想像してしまい、「理解や使用が難しいのではないか」という思い込みから 生じているのではないかと思います。
実は、10進数は、人間が理解しやすい形で取り扱うデータ型なのです。
以下順に説明していきます。
たとえば、5桁の数字を扱うデータの場合、外部10進数は PIC 9(5) あるいは PIC 99999 という表現で定義します(*2)。 そして各桁が1バイトの領域を使います。つまり、PIC 9(5) で定義された数字データ場合、 5バイトのメモリ領域を使用することになります。 また、表現できる数値の範囲は、0〜99999となります。
さて、それぞれのバイト内にはどのような値が格納されるのでしょうか?
実は、0から9の数字に対応して、それぞれ'0' から '9' までの文字が格納されます。 文字コードで表現すると、WindowsやUNIXではASCIIコード系なので0x30〜0x39 (*3)が、メインフレームであればEBCDICコード系 なので、0xF0 〜0xF9 が格納されます。
具体的に例を挙げると、
01 A PIC 9(5) VALUE 123.
という定義がある場合、Windows/UNIXではデータ名A の値は 0x3030313233 として メモリ上に表現されます。
外部10進数の便利さは、ファイルやメモリのダンプを取った際に、その数値が直接 文字として表示されることです。これにより、数値を直観的に理解し、デバッグを 容易にすることができます。
(*1) ゾーン10進数、アンパック10進数とも呼ばれます。
(*2) PICはPICTUREの短縮形です。9(5)や99999をPICTURE文字列
といいます。
(*3) 0xNNはNNが16進数であることを示します。
ここで疑問が出てきます。「はて符号はどうやって表現するのだろうか?」と。
少し考えていただくとわかりますが、符号だけで1バイトの領域を使うのは得策では ないですよね。なんとか、符号まで含めて5バイトにして、しかも文字のまま扱いたい ですよね。そこで、考えられたのが数字の一番右(つまり1の位)の数字に対応する 文字を正負によって変えるという表現方式でした。これがいわゆる重ね符号という ものです(*4)。
メインフレーム時代に考えられた方式なので、EBCDICコード系で説明したほうが
わかりやすいでしょう。
具体的には、1の位の数字’0’〜’9’を、正の場合は、’A’から’I’の文字で、
負の場合は、’J’から’R’の文字で表現します。コード値で表現すると、1の位の
数字0xF0〜F9 を、 正の場合は、0xC0〜C9で、負の場合は、0xD0〜D9で表現することに
なります。
符号なし 0 1 2 3 4 5 6 7 8 9 文字 '0' '1' '2' '3' '4' '5' '6' '7' '8' '9' 16進数 F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 正の場合 +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 文字 '{' 'A' 'B' 'C' 'D' 'E' 'F' 'G' 'H' 'I' 16進数 C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 負の場合 -0 -1 -2 -3 -4 -5 -6 -7 -8 -9 文字 '}' 'J' 'K' 'L' 'M' 'N' 'O' 'P' 'Q' 'R' 16進数 D0 D1 D2 D3 D4 D5 D6 D7 D8 D9
例えば、+12345 は、 ‘1234E’ と表現され、-12345は、’1234N’と表現される
ことになります。
ここで、+0と-0が出てきて少し戸惑うかもしれませんが、これは例えば、 +100は、
‘10{‘ -100は、’10}’と表現するという意味です。
(*4) 符号は重ねずに独立した領域をもたせることも可能です(SIGN IS SEPARATE)。 また、符号部の位置を右端(最下位桁)ではなく左端(最上位桁)に持たせることも可能です(SIGN IS LEADING)。
続いて内部10進数について見ていきたいと思います。
COBOLが誕生した1959年当時、コンピュータの性能は現代に比べて格段に低く、 利用可能なメモリも限られていました。これらの技術的制約の中で、より多くの データを効率的に扱うために内部10進数が登場しました。
では、内部10進数はどのようにしてメモリ節約に貢献したのでしょうか?
実は、0から9までの数字は、二進数で表すと4ビット(0b0000から0b1001)
(*6)で十分です。この表現方法は
BCD(Binary-coded decimal、二進化十進数表現)と呼ばれます。内部10進数では、
このBCD表現を用いて各桁を4ビットで表現し、最下位(1の位の右側)に更に
符号領域4ビットを加えることで、数字を定義しました。
この方法により、内部10進数で表現される数値は外部10進数に比べてメモリ使用量が 約半分になります。例えば、外部10進数で5バイトを使用する数値は、内部10進数では 約2.5バイト(正確にはさらに符号部の4ビットが必要)で表現可能です。
しかし、内部10進数表現は外部10進数と異なり、数値が文字として表現されないため、 ダンプ中の内部10進数の数値を直感的に理解するのは難しいかもしれません。それでも、 メモリ効率の観点から見ると、内部10進数の導入は当時の技術的な制約を克服する重要な ステップでした。
(*5) パック10進数とも呼ばれます。
(*6) 0bNNNNは、NNNNが2進数であることを示します。
内部10進数では、符号がある場合でもない場合でも、最下位に4ビットの符号領域が 確保されます。符号なしの場合、この領域は0xF(固定)となります。一方、符号あり の場合、正の値には0xC、負の値には0xDが設定されます。
具体的な数値で見ていきましょう。
内部10進数は、外部10進数と同じくPICTURE句で数字の桁数や符号の有無を指定し、
さらにUSAGE PACKED-DECIMALあるいはCOMP-3(COMPUTATIONAL-3)を指定して、内部10進数
であることを明示します。
たとえば、5桁の符号なし内部10進数を定義する場合、以下のようになります。
01 A PIC 9(5) COMP-3.
ここでデータ名Aのメモリサイズは、5桁の数字が5×4ビット=20ビット分と、符号領域の 4ビット分で合わせて24ビット、つまり3バイトになります。例えば、「12345」という 値は、メモリ上に0x12345Fとして格納されます。
符号付きの内部10進数の場合、定義は以下のようになります。
01 B PIC S9(5) COMP-3.
データ名Bのメモリサイズはデータ名Aと同じく3バイトです。ただし、数値の表現が異なり、 「12345」はメモリ上に0x12345Cとして格納され、一方「-12345」は0x12345Dとして 格納されます。
10進数は「人間にとって理解しやすい」という理由だけでなく、コンピュータの中で 誤差を最小限に抑えるためにも重要です。コンピュータは基本的に2進数を使用して 数値を表現しますが、2進数では0.1のような一部の10進数小数を正確に表現できません。 これは、2進数で表せる小数が1/2, 1/4, 1/8のような2nの表現形式に限られる ためです。
ビジネス領域で金銭の計算を行う際、このような誤差は許容できません。そのため、 10進数での計算がサポートされる必要があったのです。
COBOLでは、小数点付きの10進数も扱うことができます。整数部がn桁、小数部がm桁の 数値は、PIC 9(n)V9(m)として定義されます。Vは小数点位置を示しますが、実際の領域 としては確保されません。例えば、以下のように定義された場合、
01 C PIC S9(2)V9(3) VALUE 12.345
データ名Cはメモリ上で5バイトとして領域が確保され、0x31323334C5という値が格納 されます(ここで0xC5は重ね符号です)。このように、COBOLでは小数部の有無に関 わらず、数値は内部表現上同じ方法で扱われ、小数を持つ10進数同士の演算はまず 小数点位置で桁合わせが行われた後に計算が実行されます。
COBOLは、その誕生以来、ビジネスアプリケーションにおける精密な数値処理の要求に 応え続けてきました。この言語の核となるのは、人間にとって直感的で、かつ計算上の誤差 を最小限に抑えることができる10進数のデータ表現です。外部10進数はその明瞭さと デバッグの容易さで、内部10進数はメモリ効率の良さで、それぞれが重要な役割を 果たしています。符号の扱いや小数点の表現においても、COBOLは柔軟でありながら正確な 計算を可能にしてきました。