#1 変数を覗いてみよう (6) (98年11月7日 初版)

1.6 浮動小数点数 double型の内部表現  浮動小数点数は、符号、仮数部と指数部からなります。コンピュータが使用する形式 は、   ±a × 2^b   ±a × 8^b   ±a × 16^b というように表されます。aを仮数部(mantissa)、bを指数部(exponent)といいます。 仮数部は、実数の精度を、指数部は大きさを表しています。実数を指数で表現すること で、非常に大きな数も小さな数も一定の正確さで同程度に扱うことができます。数値が 指数部の上限値を越えると、オーバーフローとなり、下限値よりも小さいとアンダーフ ローが生じ、エラーとなります。  IEEE(アイ・トリプル・イー)標準形式(注)によるdouble型の内部表現は          ±1.[仮数部] ×2^[指数部] と、仮数部の整数値を1とした小数点以下の部分で表して、下図
のようになっています。全体で8バイト(64ビット)のうち、仮数部の符号ビットが 1ビット、指数部が11ビットおよび仮数部に52ビット割り当てられます。また指数 部は1〜2046の値をとるようになっていて、この値から指数バイアス(正規化値)1023 を引くことにより本当の値が -1022〜1023の範囲をとるように定められています。指数 部の値0と2047は例外処理のために予約されています。 (注)IEEE(Institute of Electrical and Electronics Engineers)の定める規格。  では、浮動小数点型のデータのビットパターンをみるプログラムを考えます。ビット演 算子は整数型や文字型(文字型は実際には整数型と同じもの)に対してしか使用できませ んので、シフト演算子を直接浮動小数点型の変数に使うことができません。  そこで、共用体union)を使うことにします。次のような共用体を考えます。 union { double d; // 8バイト unsigned char c[8]; // 1バイト×8 } data; 共用体の変数名 data は double型と unsigned char型の2つのタイプの要素を持ち ます。構造体と違って共用体ではデータの記憶場所は1つです。double型とunsigned char型の要素が1つの領域を共有しています。そのためデータの読み書きが、双方のデ ータ型で行うことができます。  つまり、double型の数値を読み込んで、それをunsigned char型のデータとして参 照します。共用体では、データの領域割り当てはもっとも大きい要素のデータサイズとな ります。double型は8バイトの記憶領域を持ちますので、1バイトの記憶領域を持つ unsigned char型の配列要素8個で割り当てます(同じサイズにする必要があります)。 そうすることで、浮動小数点数を1バイト単位に参照することができます。  共用体の中にある要素にアクセスするには,レコード選択子(.)を使います。つまり、 data.d に実数値を代入しておいて data.c[i] ( i = 7 〜 0 ) をビット演算子を使って、ビットパターンを読みます。それが、次の関数 dec2bin() です。 ============================================================================= // 10進→2進へ変換してビットパターンを出力する関数 void dec2bin(double x, int bit[]) { // 共用体 union { double d; // 8バイト(64ビット)の領域 unsigned char c[8]; } data; data.d = x; //double型で代入 int i,j, k = 0; // k は配列 bit[] のインデックス for(i=7; i >= 0; i--) { // 文字型で1バイトごとに取り出す unsigned char ch = data.c[i]; // 要素iを取り出す // 1バイト単位で2進数に変換 for(j = 7; j >= 0; j--){ // 要素i中の j+1番目のビットを調べる if( (ch >> j) & 1 ) // この部分は整数のときと基本的に同じ bit[k] = 1; else bit[k] = 0; // ビットを出力する cout << bit[k]; k++; //配列のインデクスを1つ進める } cout << " "; // 1バイトごとに区切りを入れる } cout << endl; // 最後に改行 } ========================================================================= この関数の引数リストは、浮動小数点型 x と整数型の配列 bitです。xは入力の実数 値です。xのビットパターンを配列 bit に納めて返します。C/C++では配列を関数に渡 すときは、ポインタが渡されることに注意しましょう。ポインタが指す配列要素の実体 は関数を呼び出す側( main() 関数 )にありますから、関数内で値を書き換えることが できます。  さらに、配列に読み出した浮動小数点数のビットパターンから、仮数部と指数部を10 進数に戻してみます。それを計算するのが、下のプログラムの中の関数 bin2dec() です。  計算の中身は、まず配列要素 bit[0] は符号ビットを表し、正が0で負のとき1なの で1−2*bit[0]で±1の符号が求まります。  指数部( exponent )は要素bit[1]〜bit[11]に納められていますから、 bit[11]×2^0+bit[10]×2^1+bit[9]×2^2+.....+bit[2]×2^9+bit[1]×2^10. この値から指数バイアス1023を引いたものが指数部です。  また、仮数部は小数点以下( fraction )が要素bit[12]〜bit[63]までに納められて います。 bit[12]×2^-1+bit[13]×2^-2+....+bit[62]×2^-51+bit[63]×2^-52. これに1を加えたものが仮数部となります。指数を計算するには、関数 pow() を使いま す。2^i は pow(2, i)です。
// double.cpp 
//  double型の浮動小数点数のメモリ格納状態を見る 
#include <iostream.h> 
#include <iomanip.h>  // setw()を使う 
#include <math.h>     // 指数関数pow()を使う 

void dec2bin(double , int []); //10進数の浮動小数点型を2進数に変換 
void bin2dec(int []);          //浮動小数点数の仮数部と指数部を10進数に変換 

int main() 
{ 
    int bit[64]; // ビットパターンを格納する 
    double x; 

    cout << "実数値を入力して下さい: "; 
    cin  >> x; 

    cout << "\ndouble型の入力データの内部表現(2進数表示)\n\n" 
         << " [指数部    ][ ---- 仮数部" << setw(45) << "----- ]" << endl; 

    dec2bin(x, bit);//  10進→2進へ変換しbit[]に格納する 

    cout.setf(ios::fixed, ios::floatfield); //固定小数点形式で表示 
    cout << "\n入力データ: " << x << " = "; 

    bin2dec(bit);//  仮数部と指数部の2進→10進へ変換 

    return 0; 
} 

//  10進→2進へ変換して出力 
void dec2bin(double x, int bit[]) 
{ 
 // ビット演算は、整数型または文字型に対して使用しなければならないので、 
 // 共用体を使って浮動小数点型のデータを文字型(整数型)として参照する 
    union { 
        double   d;            // 8バイト(64ビット)の領域 
        unsigned char c[8]; 
    } data; 

    data.d = x; //double型で代入 

    int i,j, k = 0; 
    for(i=7; i >= 0; i--) { // 文字型で1バイトごとに取り出す 
        unsigned char ch = data.c[i]; 
        for(j = 7; j >= 0; j--){//  1バイト単位で2進数に変換を行う 
           if( (ch >> j) & 1 ) 
                bit[k] = 1; 
           else 
                bit[k] = 0; 
            // ビットを出力 
            cout << bit[k]; 
            k++; 
        } 
        cout << " "; // 1バイトごとに区切りを入れる 
    } 
    cout << endl; // 最後に改行 
} 

//  仮数部と指数部を2進→10進へ変換して出力する 
void bin2dec(int bit[]) 
{ 
    const int BIAS = 1023; // 指数バイアス 
                           // 指数部は0〜2047の値で表されている、この値から 
                           // BIASを引くことで、-1023〜1024の範囲で 
                           // 本当の値が求まる 

    int    exponent = 0;         // 指数部 
    double fraction = 0.0;       // 仮数部の小数点以下 
    int    sign  = 1 - 2*bit[0]; //符号ビット0:正、1:負なので 
                                 // 0 -> 1  1 -> -1 となる 
    // 指数部を10進整数に変換 
    int i; 
    for (i=11; i>=1; i--)   // 指数部は11ビット 
        exponent += (int)(bit[i]*pow(2, 11-i)); 

     // 仮数部を10進小数に変換 
    for (i=12; i<=63; i++)   // 仮数部は52ビット 
        fraction += bit[i]*pow(2, 11-i); 

    cout.precision(12); //浮動小数点数の精度 
    cout << sign * (1.0 + fraction) << "×2^"  << ( exponent - BIAS ) << endl; 
 }
 ソースファイル(double.cpp)

[実行結果]
実数値を入力して下さい: 0.1 

double型の入力データの内部表現(2進数表示) 

 [指数部    ][ ---- 仮数部                                      ----- ]   
00111111 10111001 10011001 10011001 10011001 10011001 10011001 10011010   

 入力データ: 0.100000 = 1.600000000000×2^-4 
 結果を検討しましょう。この例で入力した実数値0.1は2進数で書くと 0.1の2進数表現 = 0.0001100110011............. , と循環小数になります。これをIEEE標準形式で表すと、最初に1が現れるケタの次のとこ ろへ小数点を動かして、 1.1001100011001100....... ×2^−4 , となります。仮数部は小数点以下の部分で表すので 10011001100..... , というビット並びになります。そして最後は52ビット内に収まらない次のケタ(53ビ ット目)の値1を丸めて(四捨五入、実際は0捨1入)その結果 .....11010 で終わりま す。 [演習問題 1.6] 非常に大きな桁数の実数値を計算するにはどうしますか、考えな さい。例えば、円周率πを1000桁まで求めるには、どのようにデータを記憶させれ ばよいですか。

| 目次 | 前のページ | 次のページ |

Copyright(c) 1998,1999 Yamada, K