doubleを掘り下げる
doubleの中身を確認し整理しておこうと思います。浮動小数点演算標準としてIEEE754で定義されていますが、少し難解な印象があります。まず前提としてすべての実数\(x\)は
\begin{align*} x=(-1)^{(2-\delta)} \cdot \alpha \cdot 2 ^n \tag{1}\end{align*}
として表せます。ここで\(\delta\)は0か1(符号)、nは整数(指数)、\(\alpha\)は\(1\le\alpha\lt 2\)である実数(端数)です。\(1\le\alpha\lt 2\)は
\begin{align*} S_n = 1 + \sum_{k=1}^n \frac{1}{2^k} \tag{2}\end{align*}
が\( \lim\limits_{n->\infty}S_n=2 \)ですので、(2)の\( \frac{1}{2^k} \)の和の組み合わせで表現されます。
doubleはこの符号(sign) と指数(exponent)と端数(fraction)を決められたbitに配置する型です。IEEE754ではこれらを下記のように配置する事を決めています。
63 | 62 | 61 | 60 | 59 | 58 | 57 | 56 | 55 | 54 | 53 | 52 | 51 | 50 | 49 | 48 | ~ | 3 | 2 | 1 | 0 |
sign | exponent | fraction |
1. sign
符号は1をマイナス、0をプラスと決めています。
2. exponent
指数部は2進数11桁なので2048個の数値を持ちます、値の持ち方が独特で実際にはunsignedで値を保持しながら、オフセット(バイアス)で-1023する事になっています。それにより -1023~1024 までを表現する事が出来ます。例えば、
62 | 61 | 60 | 59 | 58 | 57 | 56 | 55 | 54 | 53 | 52 |
1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
はunsingedでは\(2^{10}\)で1024ですが、オフセットで-1023するので実際は1となります。
3. fraction
端数部は52bitの中に小数点以下の情報を保持します。オフセットとして+1を持ちます。例えば
51 | 50 | 49 | 48 | 47 | 46 | 45 | ~ | 2 | 1 | 0 |
\(2^{-1}\) | \(2^{-2}\) | \(2^{-3}\) | \(2^{-4}\) | \(2^{-5}\) | \(2^{-6}\) | \(2^{-7}\) | ~ | \(2^{-50}\) | \(2^{-51}\) | \(2^{-52}\) |
1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
であれば、\(1 + 2^{-1} + 2^{-3} = 1.625 \)となります。
ではテストプログラムで確かめてみましょう。
#include <iostream> #include <bitset> typedef union UnDouble_{ int iNum; struct StAna{ unsigned int lfraction:20; unsigned int exponent:11; unsigned int sign:1; } stAna; } UnDouble; typedef struct StDouble_{ UnDouble uLsb; UnDouble uMsb; } StDouble; int _tmain(int argc, _TCHAR* argv[]) { double foo = -11.0; StDouble *stAc = (StDouble*)&foo; std::bitset<32> bit[2]; bit[0] = stAc->uMsb.iNum; bit[1] = stAc->uLsb.iNum; std::cout << bit[0] << bit[1] << std::endl; std::cout << "sign=" << stAc->uMsb.stAna.sign << std::endl; std::cout << "exponent=" << stAc->uMsb.stAna.exponent-1023 << std::endl; stAc->uMsb.stAna.sign = 0; stAc->uMsb.stAna.exponent = 1023; std::cout << "fraction=" << foo << std::endl; return 0; }
構造体変数のポインタでキャストする事で直接signとexponentを取り出しています。fractionは52桁もありますのでdoubleじゃないと表現できませんsignとexponentの部分を意図的に消し込むことで抽出しています。なおこのプログラムはバイトオーダに依存するかと思いますので注意ください。このプログラムの入力はfoo = -11.0 です。実行結果は
1100000000100110000000000000000000000000000000000000000000000000 sign=1 exponent=3 fraction=1.375
となります。この結果を(1)の式に入れてみましょう。
\begin{align*} x=(-1)^{(2-1)} \cdot 1.375 \cdot 2 ^3 \end{align*}
ちゃんと -11.0 になっています。
まとめ
・ doubleは実数を(1)の式のsign, exponent, fractionに分けて、それぞれに決まったbitを割り当てたもの。