2013年2月10日日曜日

浮動小数点数の中身をC++で見る( ゚д゚ )クワッ!!

最近、基本情報技術者とかC++とか勉強しているでしへ。

先のエントリーでは浮動小数点数の精度の話をC++と一般的なPCでネイティブに扱えるfloat及びdoubleを用いてIEEE754のBinary32とBinary64を使用する例を挙げて紹介しました。

さて、ところでそもそも浮動小数点数の中身を確認する術は理解していますか?

例えば、IEEE754 Binary32であれば32bitsのストレージ(=記憶域)符号部1bit指数部8bits仮数部23bitsと内部を3つの領域に分けて使う事は既に知っているでしょう。

C++でfloat型の中身を計算上の浮動小数点数の値としてではなく、ストレージの全てのビット状態を確実に確認する方法としてreinterpret_castの応用を紹介します。

#include <iostream>

int main()
{
  do
  {
    float value;
    std::cout << "input value as a floating number > ";
    std::cin >> value;
    std::cout
      << "your input    : " << value << "\n"
         "  hexadecimal : 0x" << std::hex << *reinterpret_cast<uint32_t*>(&value) << "\n"
      ;
  } while (true);
}

reinterpret_castC++の型変換の1つ。黒魔術ではありません(╹◡╹)

&valuefloat型のvalueのアドレスをfloat*型で取得し、それをreinterpret_castによりuint32_t*型に型変換(≈コンパイラーの型解釈の変更)し、*単項演算子(=indirection operator)により、結果的にfloat型のvalueのストレージをuint32_t型としてstd::coutへ渡しています。

std::hex表示を16進数にするC++のマニピュレーターで、付けないと単純に10進数の整数値が表示されます。また、uint32_t型は32bitの非負整数型一般的なPCではunsigned(int)型です。float一般的なPCではIEEE754 Binary32となり32bitsですから、同じストレージ幅の整数型に割り当てて16進数で中身を見たと言う訳です。

読んで分かった気にならず、実際に実行して遊んでみましょうね(╹◡╹)
input value as a floating number > 1
your input    : 1
  hexadecimal : 0x3f800000
input value as a floating number > 2
your input    : 2
  hexadecimal : 0x40000000
input value as a floating number > 3
your input    : 3
  hexadecimal : 0x40400000
input value as a floating number > 3.14
your input    : 3.14
  hexadecimal : 0x4048f5c3
input value as a floating number > -3.14
your input    : -3.14
  hexadecimal : 0xc048f5c3
input value as a floating number > ^C
先のコードはシンプルですが、終了手段は用意していないのでCTRL+Cとかで終わって下さい(╹◡╹)

さて、まだ16進数に慣れていないとすればこの表示ではIEEE754 Binary32を確認するのは少々頭の体操が必要になるかもしれません。そこでuint32_t型ではなく、IEEE754 Binary32を覗く為の型を作って見ましょう。

#include <iostream>

struct ieee754_binary32
{
  uint32_t fraction : 23;
  uint16_t exponent :  8;
  bool     sign     :  1;
};

int main()
{
  do
  {
    float value;
    std::cout << "input value as a floating number > ";
    std::cin >> value;
    std::cout
      << "your input    : " << value << "\n"
         "  hexadecimal : 0x" << std::hex << *reinterpret_cast<uint32_t*>(&value) << "\n"
      ;
    auto rv = *reinterpret_cast<ieee754_binary32*>(&value);
    std::cout
      << "  ieee754     : sign     : " << std::boolalpha << rv.sign << "\n"
         "              : exponent : " << std::dec << rv.exponent << "\n"
         "              : fraction : " << rv.fraction << "\n"
      ;
  } while (true);
}

先のソースコードにieee754_binary32型を追加して、出力の辺りでreinterpret_castでfloat型をieee754_binary32型に再解釈して変数rvに保持、それを表示しています。

ieee754_binary32型の定義ではbit-field(参考: WisdomSoft C読本 7.5 ビットフィールド)と言う機能を使用して、クラスのオブジェクトが保持するストレージ(≈メンバー変数のストレージ)をビット容量単位で定義しています。

さて、これも忘れずに自分で実行してみましょう。少しは分かりやすくなりましたか?(╹◡╹)

また、2進数で見たいのならば、次の様にすれば良いでしょう。

#include <iostream>

struct ieee754_binary32
{
  uint32_t fraction : 23;
  uint16_t exponent :  8;
  bool     sign     :  1;
};

int main()
{
  do
  {
    float value;
    std::cout << "input value as a floating number > ";
    std::cin >> value;
    auto uv = *reinterpret_cast<uint32_t*>(&value);
    std::cout
      << "your input    : " << value << "\n"
         "  hexadecimal : 0x" << std::hex << uv << "\n"
      ;
    auto rv = *reinterpret_cast<ieee754_binary32*>(&value);
    std::cout
      << "  ieee754     : sign     : " << std::boolalpha << rv.sign << "\n"
         "              : exponent : " << std::dec << rv.exponent << "\n"
         "              : fraction : " << rv.fraction << "\n"
      ;
    std::cout
      << "  binary      : "
      ;
    for(auto n = sizeof(value) * 8; n; --n)
      std::cout << (( uv >> n - 1) & 1);
    std::cout << std::endl;
  } while (true);
}

input value as a floating number > 3.14159265358979323846
your input    : 3.14159
  hexadecimal : 0x40490fdb
  ieee754     : sign     : false
              : exponent : 128
              : fraction : 4788187
  binary      : 01000000010010010000111111011011

もちろんこれも忘れずに自分で実行してコードも理解しておきましょうね(╹◡╹)

ビット単位の計算について曖昧ならWisdomSoft C読本 2.10 ビット処理 など参考にすると良いでしょう。

・・・さてさて、もういい加減に飽きるほど浮動小数点数やIEEE754の内容について十分に理解できましたか?現代計算機の基礎的な仕組みを学ぶ事で自分が何を扱っているのか理解し安心してプログラムを組み上げると良いでしょう。

参考として、今回紹介した様なreinterpret_castの応用はデバッグ的な役割ではなく、例えばメルセンヌツイスターを応用した乱数アルゴリズムの高速な実装であるSFMTの実装など特に数値を扱う低レベル処理に用いられています。参考として知っておくと良いでしょう(╹◡╹)

0 件のコメント:

コメントを投稿