組み込みC/C++

C/C++リテラシー向上のためのページ

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を割り当てたもの。