そもそも浮動小数点数ってなんなのか
概要
プログラミングの技術書を読んでいると、たびたび浮動小数点数という言葉が登場する。しかし、それに反して、浮動小数点そのものが何なのかについて踏み込んだ説明が少ないように思うので、書き残しておきたい。
動機
プログラミングの講義で小数点以下の20桁を出力せよという課題が出され、double型(C言語における、浮動小数点の変数のための型)では、無理じゃね?と思い調べ始めたところ、そもそも浮動小数点の規格について理解が浅かったことを思い知らされた。そこで、これについて未来の自分のために書き残すことにする。
内容
float,doubleは浮動小数点数を代入できる型です。と、多くの技術書には平然と書かれているが、そもそも浮動小数点とは何なのか。
浮動小数点数とは何なのか。
浮動小数点数は、その名の通り、小数点が浮動している数のことである。対義語は固定小数点数。コンピュータは0,1,で数字を表現している。ここでそれが整数であれば、0,1の並びの1通りと、一つの整数を一対一対応させれば表現できるのだが (例えば00000001 → 1とか)、小数でそれをするのは非常に非効率である。整数と小数では表現しなければならないパターン数に点と地ほどの差があるからだ (ビット列を小数と一対一対応させてたらメモリがどんだけあっても足りない) 。
そこで小数を符号、仮数、指数に分割してそれぞれ表現する方法が考え出された。
(2進数に変換後。IEEE754の定義は後に示す。)
このように表せば、符号を表すビット列、仮数を表すビット列、指数を表すビット列と分けて表現することで、柔軟に小数を表現できるようになる。
現代のコンピュータでよく使われている小数表現の規格「IEEE754」では、次のようなビット列で小数を表すことになっている。
(* 説明書きをしているが、説明書きだけではイメージがつかみづらいと思う。下に例題を用意したので、説明を見ながら追ってみてほしい。)
単精度(32 bit)
符号(1bit) | 指数(8bit) | 仮数(23bit) |
倍制度(64 bit)
符号(1bit) | 指数(11bit) | 仮数(52bit) |
4倍制度(128 bit)
符号(1bit) | 指数(15bit) | 仮数(112bit) |
符号をs,バイアス付き指数をe-bias,先行ビットをj,仮数部をfとすると、
浮動小数点は次のように表現される。
(oracle Cユーザーズガイドより抜粋)
符号sは - が 1 , + が 0と対応する。
先行ビットjは常に1になるので基本的には省略される。しかし、アーキテクチャや型によっては、明示的に示すこともある。
仮数部fは2進数変換後に一番左が1になるまでシフト演算を行い、その仮数の小数点以下を仮数部に格納する。
指数部に関して、バイアス付き指数ってなんなんだ、という話になると思う。これは指数部のeが符号なし整数であることに原因がある。だからバイアスをかけて、負の範囲も表現可能にしている。32bitの単精度では127、64bitの倍精度では1023がバイアスとなる。
例題 10進数小数を2進数に変換し、IEEE754規格に従ったビット列にしてみよう
ここでは、10進数小数19.24(これを書いている現在時刻)を2進数小数に変換し、それをIEEE754に従ってビット列(64bit)にしてみよう。
まずは、19.24を2進数に直す。すると
10011.00111101011100001010001111010111000010100011110101110000101・・
となる。小数点を含む10->2進数変換について、この計算には終わりがない。整数の範囲であれば2進数でもうまく使えるのだが、小数点が登場すると正確に表すことが不可能になり、誤差が発生する。(2の累乗であらわされる数か、またはその部分和である場合においては例外) ただ、ここでは誤差の修正については触れず、とりあえず気にせず先に進むことにしよう。(なぜなら死ぬほど記事が長くなるから。)
2進数が得られたら、仮数が1.~~になるようにシフト演算する。
出来たら、仮数部は完成したも同然なので先頭からどんどん格納していこう。前述のとおり、先頭ビットは省略されるから、仮数部は次のようになる。
12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
0 | 0 | 1 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 0 |
23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
1 | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 1 | 0 |
34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 |
1 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 0 |
45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
1 | 1 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 |
56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | ||
0 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 0 |
入りきらないものは切り捨てられる。悲しい。
続いて、指数部eを作っていこう。eは符号なし整数であるから、負の範囲を表現するにはbiasをかける必要がある。表現したい指数をOとすると、
今回のケースでは、 だから、
これを2進数に直して、指数部は
10000000011
となる。
格納していくと、
2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
最後に符号ビットを格納する。今回は正の数なので、0
1 |
0 |
よって、ビット列は
0100000000110110011110101110000101000111101011100001010001111010
となる。
double型の計算精度は何桁まで保たれるか
この記事を書き始めた動機として、double型の仮数の計算精度がどの程度か示しておきたい、というのがあるので考えてみる。
ありがちな近似として、というのがあるので、これを利用してみると、
よって、計算精度は15桁以上16桁未満の間で保たれることになる。
これに伴い、学校の演習課題で指示されたdouble型での小数点以下20桁出力はおそらく実現不可能であると考える。
まとめ
浮動小数点形式は、小数点表示においてメモリを効率的に扱う表現法。
64bitのdouble型では、仮数20桁の計算精度を保つことは不可能である。