小数の表現

今度は小数をどのように表現するかを考えよう。まず、数学で小数を2 進表記するやり方について考える。そのあとで、コンピュータの中で 限られたケタ数(例えば32ビットとか64ビット)で小数を表現する方法を考え よう。

数学における2進小数

数学的には、2進法であっても10進法であっても小数の表現に大した違いはない。 まず、10進法の場合、数を10で割るとケタが1ケタ右にずれるから、 2進法の場合も2で割ればケタが1ケタ右にずれるべきである。 例えば、1を 10進法の 10 で割ると、0.1 になるが、 これは 1 を1ケタ右にずらしたものである。 これと同じように考えると、 1 を 2 で割った 1/2 (= 10進の0.5)は、 2進では 1 を1ケタ右にずらしたもの、つまり、0.1 という表現で表される。 もう一度2で割れば、 1/4 (= 10進の0.25) が 2進では 0.01 になることがわかる。

そこで、例えば2進小数の 0.11 という表記は0.1 と 0.01 を合わせたもの なので、10進で書けば、0.5 + 0.25 = 0.75になることがわかる。 101.11 であれば、整数部の 101 が10進では 5だから、101.11(2進) = 5 + 0.75 = 5.75 (10進) であることがわかる。

あるいは、次のように考えることもできる。いったん、小数点を取り除い て考えよう。101.11 の小数点を取り除くと 10111 になる。 10111 は 10進の23である。 これを4で割れば、2進では右に2ケタずれるので、101.11 になるはずである。 従って、101.11(2進) = 23(10進)÷4(10進) = 5.75 になる。 このように考えても、2進小数を10進に直すことができる。

(では、10進小数を2進小数に直すのはどうするかだが、 それを簡単に説明するのは、今の段階では無理である。 あとで除算を説明すれば、直しかたがわかる。 というのは、10 (= 2進の 1010)で割るやり方がわかるようになるからである。)

演習

2進小数 110.101 を10進小数に変換しなさい。

では、コンピュータの中で限られたケタ数で小数を表現する方法を考えよう。 よく使われる表現は、固定小数点表記と浮動小数点表記の2つである。 (「表記」と言っても「表現」と言ってもよい。)

固定小数点表記

小数を表す一つのやり方は、小数点の位置を決めておく方法で、 固定小数点表記と呼ばれる。

簡単のために、まず10進で考える。 例えば、整数部分を5ケタに決めておいて、 小数部分も5ケタに決めておく。 つまり、下から5ケタ目と6ケタ目の間に小数点がある、 と決めておく。 例えば、3.1415(円周率の近似値)という数であれば、 00003.14150 と表す。 整数部分を5ケタに揃えるために、頭に0を補って00003 とし、 小数部分も5ケタに揃えるために、末尾に0を補って14150としている。 (逆に、小数部分が長すぎる場合は、5ケタに揃えるために、 6ケタ目以降を切ることもある。 例えば、3.141592 の場合、最後の2ははみ出すので、 切ることになるから、00003.14159 となってしまう。) こういう表現を固定小数点表記(表現)という。

小数点の場所を固定したのだから、小数点を記録する必要はない。 つまり、0000314150 と表せばよい。これで、10進10ケタを使って 小数を表すことができた。 このように小数点を省略した場合もやはり固定小数点表記と言う。

もちろん、整数部のケタ数と小数部のケタ数は違っていても構わない。 例えば、整数部を4ケタ、小数部を6ケタとすれば、上の 3.1415 は 0003.141500、小数点を省略すれば 0003141500 となる。

さて、上のことをふまえて、今度は2進法で考えることにしよう。

上と同じように、2進法でも、例えば整数部5ケタ+小数部5ケタで表す、 などと決めておく。 (5ケタというのは現実的にはちょっと少な過ぎる。 あくまで例として見てほしい。)

すると、例えば2進小数の 101.1101 という数(これは円周率とは関係ない)は、

        00101.11010

あるいは、小数点を省いて、

        0010111010

のように表せばよいことになる。 するとこの場合で、 10ビットあれば 2進の小数101.1101を表現できることになる。

固定小数点表記のデメリット

固定小数点表記は、数値の精度を確保する分には不便である。 話をわかりやすくするために、10進で考えよう。

整数部5ケタ、小数部5ケタで表記するとして、 3.14159265 を表現したいとする。 これは全部で9ケタの数だが、小数部が長いので、 下のほうのケタを切らない限り、表現できない。 10ケタ分の場所があるのに、9ケタの数値がおさまらず、 00003.14159 にしかできない。 下の3ケタはすてるしかなく、そのくせ、 整数部には5ケタ使えるのに頭の4ケタは0で埋められていてムダになっている。 精度の確保の面から言えばもったいない話である。

つまり、固定小数点表記は、 精度を必要とするような数値計算には向かないことになる。 (精度があまり必要でない場合、あるいは、整数部分・小数部分の両方に 非常に大きなケタ数を用いても許されるような場合は固定小数点表記でも よい。) そこで、小数点の位置をずらせるようにしようという考えが出てきた。 それが、下で説明する浮動小数点表記というものである。

浮動小数点数 (floating point number)

物理学で光の速度を表すのに 299800000 m/s とは書かず、 2.998 × 10 ^ 8 m/s などと書く。 このような書き方(に相当する表現)をコンピュータでも使おう、 というのが浮動小数点数の考え方である。 つまり、整数部分が1ケタ(ただし非0)になるように小数点を移動し、

        x.yyyyyyyyy × 10 ^ z

のような書き方をする。 ただし、コンピュータでは、普通2進で計算するので、

        x.yyyyyyyyy × 2 ^ z

という形にするのが普通である。

0はこの形で表せないが、0をどう表すかはあとで説明する。

負の数を表す時は、符号を表すビットを用いる。 つまり、符号があれば 1, なければ 0 という1ビットのケタを用意して、 符号を表す。

さて、ここで2進法のメリットが出る。 0以外の数を浮動小数点表記にした場合、 上記の x の部分(整数部分)は1しかあり得ない。 数字が0と1しかなく、 整数部分は0以外の1ケタの数というのだから、1しかないのである。 しかし、1に決まりきっているのなら、 その1は書かなくてもわかるはずである。 つまり、メモリ中で、xの部分は記録しなくてもよいことになり、 ここで 1ビットだけメモリを節約できる。 このようにして、1ビット節約する表現を「けち表現」という。

結局、浮動小数点表記は、次の3つの部分からなる:

式で書けば、次のような形で小数を表そうというのである:

        (-1)^s × (1 + m) × 2 ^ e
                  ↑この1は、x の部分(整数部分)

ただしここで、s, m, e の意味は以下の通り:

        s: 符号 (0か1)            (s は、「符号」を表す英語「sign」の頭文字)
        m: 上の yyyyyyyyy の部分  (m は、「仮数」を表す英語「mantissa」の頭文字)
        e: 指数(上の z の部分)    (e は、「指数」を表す英語「exponent」の頭文字)

よって、s , m , e をそれぞれ、何ケタかの2進数で表してやれば 浮動小数点数は表せることになる。 何ケタ使うかは方式によって違うが、 国際的な規格があり、 コンピュータ上では大抵は規格にのっとった形で小数を表現している。

正規化

一般に、n 進表記の実数(ただし、0以外)を

        (-1)^s × (1ケタの整数 + m) × n ^ e 

の形にすることを正規化と言う。 ただし、1ケタの整数の部分は、0でない数にとる。 2進表記に限らず、何進表記であっても、正規化と呼んでいる。 ただし、s, m, e, n は

        s: 符号 (0か1)
        m: 0 ≦ m < 1 なる小数
        e: 指数
        n: 底

である。

(2進の場合なら、すでに見たように (-1)^s × (1 + m) × 2 ^ e となる。)

ここで、m だけを仮数と呼ぶ場合と、 1ケタの整数 + m の部分を仮数と呼ぶ場合があり、 文献や文脈によって違っている。 2進以外では、けち表現が使えないので、その場合、正規化された数を

        (-1)^s × (仮数) × n ^ e 

のように書くしかない。 2進の場合もそれに合わせて書いてある本だと、 1 + m を仮数と呼ぶことになるだろう。 しかし、けち表現によって省略された部分は仮数に入れない、 という書き方の本では、m が仮数になってしまう。 どちらの書き方かは文脈を見ればわかるはずだが、一応注意しておく。

10進数 1984.5 を正規化すると、1.9845 × 103 となる。また、 -0.0327を正規化すると、-3.27 × 10-2 となる。 2進数 -1011011 を正規化すると、-1.011011 × 26となる。 2進小数 0.001011 を正規化すると、1.011 × 2-3 となる。

演習

2進小数111001.1 を正規化しなさい。また、2進小数 0.0110101を正規化しなさい。

IEEE 754 浮動小数点規格 (IEEE 754 floating-point standard)

IEEE は普通 I triple E と読む。 電気・電子工学関係の業界団体である。 その IEEE が定めた浮動小数点表記の規格が IEEE 754 というもので、 よく普及している。754 は規格の番号である。 単精度(single precision)浮動小数点形式と、 倍精度(double precision)浮動小数点形式の2つが定められている。

通常、C 言語の float 型の値は単精度浮動小数点形式であり、 double 型の値は倍精度浮動小数点形式である。

単精度浮動小数点形式

32ビット(= 4バイト)用いて数を表現する。 0 は、全32ビットを0にすることで表現する。 0以外の数については、正規化した上で、以下のようにする: 符号 s に1ビット(当然!)、指数に8ビット、仮数に23ビット用いる。 これらビットは下図のように並べる:

          ← 上位ビット                  下位ビット →
                 _________________________________
                |   |              |              |
                | s |      e       |      m       |
                |___|______________|______________|
                 1ビット  8ビット       23ビット  
                 ↑        ↑              ↑
          符号フィールド  指数フィールド  仮数フィールド

もちろん、けち表現を使っている指数部分は127をゲタとしたゲタばき表現で、 -126 から +127 までの数を表す。

-126 に 127だけゲタをはかせると -126 + 127 = 1 になる。 また、127 に 127だけゲタをはかせると、127 + 127 = 254 になる。 つまり、指数部分 e は 1 (= 00000001(2進))から 254 (= 11111110(2進)) までのビットパターンで表現されていることになる。

仮数フィールドについては、 単に小数第1位から順に「上位ビット→下位ビット」の順で並べただけである。

結局: この表現では、

        (-1)^(符号フィールド)×(1+仮数フィールドが表す小数)
        × 2^(指数フィールド - ゲタ)

を表していることになる。

例題1

正規化された2進小数 -1.01011×25 を単精度浮動小数点形式で表してみよう。

以上の考察より、求める浮動小数点表現は

        1 10000100 01011000000000000000000

である。ただし、見やすさのため、フィールドの区切りにスペースを入れた。

例題2

下記の単精度浮動小数点表現は10進ではどんな数を表すか? (フィールドの境目にスペースを入れてある。)

        1 10000001 01000000000000000000000

答: 符号ビットは1だから負の数。指数フィールドは 129。 仮数フィールドは、2進小数 0.01 を表す。 0.01(2進) は、1 を2ケタ右にずらしたものだから、 10進では、1 ÷ (2 ^ 2) = 1/4 = 0.25。 よって、上の表現が表す数は

        (-1)^1×(1+0.25)×2^(129 - 127)
        = -1 × 1.25 × 2^2
        = -1.25 × 4
        = -5.0

である。

演習

  1. 10進数の 6.5 をIEEE 単精度浮動小数点数で表しなさい。
    ヒント: 6.5 = 13/2 に注意。まず、13 を2進数で表し、 6.5 は、それを2で割ったもの(つまり、右に1ケタシフトしたもの)で あることを使って、正規化表現をまず求め、それから、IEEE 規格の 表現にする。
  2. 以下のようなビットパターンで表わされた IEEE 単精度浮動小数点数を 10進数で表しなさい:
            0 10000001 00110000000000000000000
          

Q: 何故指数フィールドに 0 (が8ケタ並んだパターン)を使わないのか?

A: 指数フィールドに 0 も使ってしまうと、全32ビットが0の場合は、 0を表しているのか、 (1+0) × 2 ^ (0 - 127) (符号無し、仮数が0、指数フィールドも0の場合) を表しているのかわからなくなってしまう。

Q: 何故指数フィールドに 255 (1ばかり8ケタ並んだパターン)を使わないのか?

A: 0で割算したり、負の数の平方根を求めようとしたような場合、 普通の数を答にするわけにいかない。 そこで、 指数フィールドが255の場合はそういう「数でないもの」を表すものと決めて、 それを答として返すことができるようにしてある。 C でプログラミングをするようになったら、 たまに NaN という表現を見るようになると思うが、 これは指数フィールドを255にして表現される「数でないもの」の1つで、 非数と呼ばれている。 (NaN は Not a Number から来ている。) NaN 以外にも、無限大を表す表現があり、 それも指数フィールドを 255 にして、 他のフィールドを適当な表現にして表している。

Q: 指数フィールドは全ビット 0 だが仮数フィールドは 0 でない場合は?

    A: (-1)^s × (0 + m) × 2 ^ (-127)を表す。
                 ↑
                 ここが 0 だということは、正規化されていないということ。

正規化された数のうちで一番絶対値が小さい数よりももっと絶対値が 小さい数を表したい時にだけ使う。 でもこんなことは覚えなくてもよい。

倍精度浮動小数点形式

全部で64ビット(= 8バイト)用いる。 符号フィールド1ビット(当然!), 指数フィールド11ビット, 仮数フィールド 52ビットを用いる。指数部のゲタは1023。 あとは単精度の場合と同じ。 (やはり、正規化数の場合は指数フィールドが0の場合と最大の2047の場合を 使わない。)

単精度浮動小数点形式はどの程度の精度が出るのか?

けち表現で省略できた1ビットも精度に入るから、 2進で 24 ビットの精度がある。

Q: 24ビットとは10進で言うと何ケタくらいの有効数字になるのか?

A: まず、2 ^ 10 = 1024 が 1000 に近い事を思い出そう。すると、 2進10ケタはだいたい10進3ケタにあたることがわかる。 つまり、2進1ケタあたり大体10進0.3ケタの精度がある。 もっと正確に言うと、2進1ケタは10進で約0.3010ケタに相当する。 24×0.3010=7.2240 なので、24ビットは10進7ケタくらいの精度である。

ただし、浮動小数点数の場合、誤差の問題がある。 24ビットと言っても、最後の1ビット分くらいの誤差があるはずなので、 その分を差し引くと、23ビット分くらいの精度と考えられる。 23×0.3010=6.9230 なので、誤差を考えた場合は、 10進7ケタにわずかに足りない精度ということになる。 そこで、単精度浮動小数点数の精度は6ケタと説明されていることもある。 まあ、10進6〜7ケタの精度で、7ケタに近いと覚えておけば良かろう。

Q: その 0.3010 という数はどこから出て来たのか?

A: (log 2)/(log 10) である。 つまり、10を底とした 2 の対数。

倍精度浮動小数点形式はどの程度の精度が出るのか?

けち表現の1ビットと仮数フィールドの52ビットを合わせて53ビットある。 53ビットは24ビットの2倍より大きいから、倍精度と言っても実際には倍以上の 精度がある。

ただし、やはり誤差の問題を考えた場合には、1ビット差し引いて52ビットの精度と 考えたほうが良い。

Q: では、10進に直すと何ケタの有効数字になるのか?

A: 0.3010 を教えたのだから、自分で考えなさい。

除算

小数を学んだので、答が小数になるような除算をしてみよう。 1 ÷ 10(じゅう)は10進では 0.1 になる。10を2進に直すと 1010(2進) になる。 そこで、2進のまま 1 を 1010(2進)で割ってみよう。

                  0.000110011....
                  __________
            1010 )1.0000
                    1010
                  ------
                     1100
                     1010
                     ----
                       10000
                        1010
                       -----
                         1100
                         1010
                         ----
                           10....

        以上により、 1/1010 = 0.000110011001100110011001100110011001100110011001...

10進法では 1/10 が有限小数だったのに、2進では無限小数になってしまうことがわかった。! 無限小数をそのまま計算機内部に納めることはできないから、どこかで切って丸めるしかない。 これにより誤差が出る。 従って、目的によっては、2進法を使わないほうがよいこともある。

現実の社会では10進法を使っている。 例えば、金銭の計算がそうである。金利が1%と言えば、0.01倍のことだが、 0.01 を2進に直すと無限小数になってしまう。それを有限ケタで切ると誤差が出る。 金銭の計算ではたとえ 1円でも誤差が出てはいけないはずである。

このようなことから、2進法の優位性は絶対的なものではなく、 場合によっては10進を使ったほうがよい、ということが言える。