【C言語】浮動小数点数における「数値⇔内部データ(符号部・指数部・仮数部)」の変換

浮動小数点数における数値と内部データの変換方法解説ページアイキャッチ

このページにはプロモーションが含まれています

このページでは浮動小数点数型における、数値と内部データの変換について解説していきます。

内部データとコンピュータ内部で実際に扱われているデータのことです。

浮動小数点数型としてはC言語では float 型と double 型が挙げられます。小数点以下の値を扱いたい時に便利ですよね!

これらの浮動小数点数型では、コンピュータ内部では単なる数値ではなく、下の図のような構造のデータとして扱われています。

float型の内部データ

例えば 0.625 は、コンピュータ内部で扱うデータ(内部データ)としては下記のような 10 からなるデータとして扱われています。

内部データからの数値への変換

このページでは、なぜこのような内部データが 0.625 といった数値として扱うことができるのかや、内部データと数値の変換方法について解説していきたいと思います!

MEMO

今回説明する浮動小数点数に対する内部データに関しては IEEE 754 という規格で定められた形式のものになります

おそらく一番一般的な浮動小数点数に関する規格です

浮動小数点数とは

まずは浮動小数点数とはどのようなものなのかについて説明したいと思います。

小数点の位置を動かすことができる値の表現方法

まず浮動小数点数とは値の表現方法の1つで、1番の特徴は「小数点の位置を動かすことができる」ところです。

例えば下の図のような値の表現方法を見たことがあるのではないでしょうか?

浮動小数点数の例

これは 1500 を浮動小数点数として表した例になります。1.5103 乗を掛けているので、計算すると 1500 になりますね。

ではここで、この 1.5 に掛けられている「103 乗」部分に注目してみましょう!

単に 1000 倍してるだけじゃないの…?

計算としては単に 1000 倍しているだけですが、小数点の位置に注目してみると、元々の 1.5 から小数点の位置が 3 桁分右側に「動いている」と捉えることもできます。

小数点が動く様子

下の図のように 1.510-3 乗を掛けた場合はどうでしょう?

浮動小数点数の例2

値としては 0.0015 となりますので、小数点の位置が 3 桁分左側に「動いている」と捉えることができます。

小数点が動く様子2

こんな感じで、10進数の場合は、ある値に 10n 乗を掛けることで、その値の小数点の位置を n 桁分動かすことができます(n が正の値であれば右側に、n が負の値であれば左側に動かすことができます)。

10 の部分は基数です。2進数の場合は 2n 乗を掛けることで小数点の位置を n 桁分動かすことができます。

2進数の浮動小数点数

この「基数の n 乗を掛ける」ことにより小数点の位置を動かすことができるという性質を利用して表現された数値が「浮動小数点数」になります。

ポイントは、普通の数値表現とは異なり、「小数点の位置をどれだけ動かすか」が指定されるところです。これは次に説明する「指数」によって指定されます。

スポンサーリンク

基数と仮数と指数と符号

浮動小数点数においては、次の4つのデータによって数値が表現されます。

  • 基数
  • 仮数
  • 指数
  • 符号

例えば、下の図の浮動小数点数においては、基数が 10、仮数が 1.5、指数が 3、符号が - になります。

符号小数点数の各部

各データの意味は下記のようになります。

基数

各桁を何種類の数字で表すかを表すデータです。10進数なら 10、2進数なら 2 になります。

仮数

仮数は浮動小数点数における仮の数値を表すデータのことです。

仮なのは、次に説明する指数によって小数点の位置が動かされるからです。

指数

指数は浮動小数点数における仮数の小数点の位置をどれだけ移動させるかを指定するデータです。

符号

値が負の値か正の値かを示すデータです。

浮動小数点数の内部データ

ここまで話してきた浮動小数点数の話は数学的な話です。

ここからはこの浮動小数点数がC言語においてどのように扱われるかについて解説していきたいと思います。

浮動小数点数型の floatdouble

C言語において浮動小数点数を扱える型は下記の2つになります。

  • float
  • double

スポンサーリンク

浮動小数点数の内部データ

では、この floatdouble の変数において、浮動小数点数をどのように扱っているのでしょうか?

ここについて突き詰めていきたいと思います。

整数型の内部データ

と、その前にまずは整数型の変数でどのようにして数値が扱われているかについて解説したいと思います。

整数型では、数値そのものだけ or 数値と符号を表すデータが変数に格納されるようになっています。

例えば int 型の変数では、内部では 31 ビット分の数値を表すデータと 1 ビット分の符号を表すデータが格納されています。

int型の内部データ

各ビットには 0 もしくは 1 の値のみが格納されます。

例えば整数型の変数に10進数の値を格納したとしても、実際には上記のように 32 ビット分の 0 と 1 の値に変換されてメモリに格納されることになります。

また整数型の変数の値を表示するような場合は、上記のデータから10進数の値が算出されて、その算出された値が表示されることになります。

内部データからの数値の算出

なぜわざわざこのようなデータの変換が行われるのかと言うと、コンピュータが 01 のデータしか扱えないためです。

逆にプログラミング時に 0 と 1 の値しか扱えないと不便なので、上記のような変換を自動的に行われるようになっています。

なので、変数に実際にどのような形式でデータが格納されているかは通常のプログラミング時には意識する必要はありません。

このように、コンピュータの内部で実際に変数に格納されるデータの形式を「内部データ」と呼びます。

ここまでは整数型について解説してきましたが、浮動小数点数においてもコンピュータ内部では「内部データ」として扱われます。

浮動小数点数型の内部データ

整数型には仮数や指数といったデータは不要なので、前述のような感じで数値のみを格納しておけば良いです。

では浮動小数点数型の場合はどうでしょう?

浮動小数点数においては前述の通り、「仮数や指数などから計算される」ことで数値が決まります。

したがって、floatdouble 型の変数においては数値そのものではなく「符号」「仮数」「指数」の基になるデータが格納されます。

具体的には float 型の内部データは下の図のような構成になります。

float型の内部データ

そして、この内部データから、次の式により10進数の数値が計算されます。より具体的な説明は後述します。

内部データから数値への変換式

また double 型の内部データの構成は下の図のようになります。

double型の内部データ

そして、この内部データから、次の式により10進数の数値が計算されます

double型の内部データから数値への変換式

floatdouble とで、ビット数や計算式は異なるものの、どちらも下記の3つの部から構成されています。

  • 符号部
  • 指数部
  • 仮数部

それぞれ「符号部」が符号、「指数部」が指数、「仮数部」が仮数の基になるデータになります(詳細は次の節で説明します)。

基数は…?

基数の基になるデータは浮動小数点数型の内部データには存在しません。

前述の通りコンピュータで扱うデータは 0 もしくは 1 のみです。したがって浮動小数点数型のデータの各ビットに格納される値も 01 のみで、基数は 2 であることが明白だからです。

符号部と指数部と仮数部

続いて符号部と指数部と仮数部がどのようなデータであるかについて解説していきたいと思います。

下の図は2進数における浮動小数点数表現であり、符号部と指数部と仮数部は下の図における符号と指数と仮数の基になる値になります。

2進数における浮動小数点数表現

また符号部と指数部と仮数部のそれぞれのサイズは型によって異なります。

下の図は float 型における内部データであり、

float型の内部データ

符号部と指数部と仮数部のビット数はそれぞれ下記のようになります。

  • 符号部:1 ビット
  • 指数部:8 ビット
  • 仮数部:23 ビット

double 型は上記のビット数が異なるのですが、各部の役割等は基本的に float 型と同じです。

ですので、ここからは float 型の符号部と指数部と仮数部について解説することとし、double 型と大きな違いがあるところだけ補足していこうと思います。

符号部

まずは符号部は「符号の基になるデータ」です。

符号の基になる符号部

符号部の値に応じて下記のようにその値の符号が変わります。

  • 0+ の値(正の値)
  • 1- の値(負の値)

下記のように -1 を符号部の値分かけた値として考えることもできます。

符号の基になる符号部

指数部

指数部は「指数の基になるデータ」です。

指数の基になる指数部

指数は前述の通り小数点の位置を移動させるパラメータですね!

指数の値分小数点の位置が動く様子

float 型の場合、指数部の値(指数部を符号なしの2進数として考え、それを10進数に変換した値)から指数は下記のように算出されます。

指数 = 指数部の値 - 127
MEMO

double 型の場合は下記のように算出されます。

指数 = 指数部の値 - 1023

float 型の場合は符号部のサイズが 8 ビットですので、指数部の値は 0255 の範囲の値となります。つまり指数としては -128 〜 127 の範囲の値となります。

例えば指数部の 8 ビットが下記の場合、

10000010

10進数に変換すると 130 になりますので、指数としては 3130127)になります。

したがって、仮数の小数点の位置を右に 3 桁分移動することになります。

仮数部

仮数部は「仮数の基になるデータ」です。

仮数の基になる仮数部

仮数部の値は小数点以下の値になります。

仮数部の最上位ビットが2進数における小数点以下第 1 桁、次のビットが2進数における小数点以下第 2 桁といったように、仮数部の最上位ビットから順に2進数で考えた時の上位の桁に対応しています。

さらに仮数はその仮数部に対して +1 することで算出されます。

つまり、仮数は仮数部を用いて下記のように表すことができます。

仮数 = 1.仮数部

これはすなわち、仮数は 1.abc… といったように整数部は必ず 1 であることを意味しています。

ん?どういうこと?

値はなんでもいいのですが、例えば 13(2進数で 1101)を2進数の浮動小数点数で表した時の仮数について考えてみましょう。

実はこの 13 の浮動小数点数としての表し方は無数のパターンが存在します。

下記は指数の値による仮数の変化を表したものになります。

指数(10進数) 仮数(2進数)
5 0.01101
4 0.1101
3 1.101
2 11.01
1 110.1

要は「指数によって仮数も変わる」ということです。

ただし、float 型においては(double 型においても)、「仮数の整数部は必ず 1」という決まりがあります。

したがって、仮数と指数の組み合わせは必ず1パターンのみになります。

13 の例であれば必ず仮数は 1.101 になります。

さらに、ちょっとややこしいのは、この整数部の 1 は仮数部の表現としては「省かれている」という点です。

仮数部には整数部の1が含まれない様子

前述の通り整数部は必ず 1 という決まりがあるため、わざわざそれをデータとして保持しておく必要がないので省かれています。

整数部が 1 であり、それが省かれていることは忘れがちで、自力で浮動小数点数を10進数変換するときなどに計算間違いすることもあるので気をつけましょう!

仮数の先頭が 1 だとすると、数値の 0 が表現できないんじゃ…。

いい気づきだね!

IEEE 754 では、0 などの特別な値については例外的な内部データで表現するようになっているよ!

例えば、指数部と仮数部が全て 0 の場合は数値の 0 を表現するようになっているんだ

なるほど

IEEE 754 では 0 などのような特別な値は、ここまでの解説とは例外的に異なる内部データで表現するように決められています。

ここから紹介するプログラムでは、例外的な値は 0 にしか対応していないので注意してください。

浮動小数点数内部データから数値への変換

ではここまで解説してきた内容を踏まえて「浮動小数点数型の内部データ」から実際の「数値」へ変換する方法について解説していきたいと思います。

内部データからの数値への変換

スポンサーリンク

スポンサーリンク

考え方

まずは考え方について解説していきますが、おそらくここまで解説した方内容で浮動小数点数型の内部データから数値への変換方法は思いつくのではないのかと思います。

要は、内部データにおける符号部・指数部・仮数部から、それぞれ符号・指数・仮数を算出し、最後にこの3つから値を計算すれば良いです。

例えば float 型の内部データは前述の通り下の図のようになります。

float型の内部データ

浮動小数点数はここまで解説してきたように符号・指数・仮数を用いて下のような形式で表す数値の表現方法です。

2進数の浮動小数点数

なので、この右辺が計算できれば数値は求まります。

この右辺を計算するためにまずは符号・指数・仮数を求める必要があります。

そして、これらは符号部・指数部・仮数部から求めることができます。

ということで、ここまでの復習も兼ねてまずは符号部・指数部・仮数部から符号・指数・仮数の求め方について解説し、続いてこれらを用いた数値の計算の仕方について解説していきます。

符号の算出

符号の算出は簡単です。

符号部の値によって下記のように最終的な数値の符号を決めれば良いだけです。

  • 0+ の値(正の値)
  • 1- の値(負の値)

プログラムとしては、符号部が 1 の場合に数値に -1 を掛ければ良いだけです。

指数の算出

指数は指数部より前述の通り下記の式で求めることができます。

指数 = 指数部 - 127

ただし内部データとしては指数部には 01 のどちらかが指数部のサイズ分格納されているだけですので、このデータを “正の整数” の2進数と考えて10進数に変換する必要があります。

仮数の算出

前述の通り仮数は仮数部の値より下記のように求めることができます。

仮数 = 1.仮数部

指数部同様に仮数部に格納されているデータは2進数として扱い、10進数に変換する必要があります。

ただし指数部と異なり仮数部は全て小数点以下の桁になりますので、小数点以下の値の2進数を10進数に変換する必要がある点に注意です。

さらに、その10進数に変換した値に対して +1 したものが仮数となります。

数値の算出

最後にここまで算出してきた符号と指数と仮数を下記の式に当てはめて計算すれば、浮動小数点数型の内部データを数値に変換することができます。

数値の算出式

プログラム

ここまで説明してきた考え方に基づいて「float 型の内部データ」から実際の「数値」へ変換するプログラム例は下記のようになります。

数値への変換
#include <stdio.h>

#define NUM_BIT 32

int main(void) {

    unsigned int data[NUM_BIT] = {
        0, /* 符号部 */
        0,1,1,1,1,1,1,0, /* 指数部 */
        0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 /* 仮数部 */
    };
    float mantissa; /* 仮数 */
    int exponent; /* 指数 */
    int sign; /* 符号 */
    float plus_value; /* 符号なしの数値 */
    float value; /* 数値 */
    int i;
    int shift_num;

    /* 仮数を計算 */
    mantissa = 1; /* 整数部は 1 */
    for (i = 9; i < 32; i++) {
        if (data[i] == 1) {
            /* ビットが1の箇所に対応する2の冪乗の値を加算 */
            shift_num = i - 8;
            mantissa += (double)1 / (double)(1 << shift_num);
        }
    }

    /* 指数を計算 */
    exponent = 0;
    for (i = 1; i < 9; i++) {
        if (data[i] == 1) {
            /* ビットが1の箇所に対応する2の冪乗の値を加算 */
            shift_num = 8 - i;
            exponent += (1 << shift_num);
        }
    }
    /* 最後に 127 を引く */
    exponent = exponent - 127;

    /* 符号を計算 */
    if (data[0] == 0) {
        sign = 1;
    } else {
        sign = -1;
    }

    /* 符号と指数と仮数から数値を計算 */

    /* 仮数*2^指数を計算 */
    plus_value = mantissa;

    if (exponent >= 0) {
        for (i = 0; i < exponent; i++) {
            plus_value = plus_value * 2;
        }
    } else {
        for (i = 0; i > exponent; i--) {
            plus_value = plus_value / 2;
        }
    }

    /* 最後に符号を掛ける */
    value = sign * plus_value;

    /* 内部データの表示 */
    printf("data = ");
    for (i = 0; i < NUM_BIT; i++) {
        printf("%u", data[i]);
    }
    printf("\n");

    /* 数値の表示 */
    printf("value = %f\n", value);

    return 0;
}

基本的に考え方はここまで解説してきたとおりです。

仮数や指数を計算する際に内部データに格納された2進数を10進数に変換するのがちょっと厄介ですね…。

仮数の計算を行なっているのはソースコードの下記部分になります。

内部データとしては仮数の小数部の値が2進数として格納されているため、小数点以下の2進数の値を10進数に変換することで仮数を計算しています。

仮数の計算
/* 仮数を計算 */
mantissa = 1; /* 整数部は 1 */
for (i = 9; i < 32; i++) {
    if (data[i] == 1) {
        /* ビットが1の箇所に対応する2の冪乗の値を加算 */
        shift_num = i - 8;
        mantissa += (double)1 / (double)(1 << shift_num);
    }
}

指数の計算を行なっているのはソースコードの下記部分になります。

指数の計算を行う際には、整数部の2進数を10進数に変換する処理が必要になります。小数部の2進数を10進数に変換する必要のあった上記の仮数の計算とは、2進数を10進数に変換する処理が異なることが確認できると思います。

指数の計算
/* 指数を計算 */
exponent = 0;
for (i = 1; i < 9; i++) {
    if (data[i] == 1) {
        /* ビットが1の箇所に対応する2の冪乗の値を加算 */
        shift_num = 8 - i;
        exponent += (1 << shift_num);
    }
}
/* 最後に 127 を引く */
exponent = exponent - 127;

この変換は、10進数の値は2の冪乗の和で計算できるという事実に基づいて行っています。

具体的なプログラムなどは公開していませんが、考え方を理解するのには下記のページが役に立つと思います。

10進数の2進数への変換方法解説ページアイキャッチ 【C言語】10進数から2進数への変換(正の整数編) 小数点以下の値を2進数変換する方法の解説ページアイキャッチ 【C言語】小数点以下の値を2進数へ変換

数値から浮動小数点数型内部データへの変換

次は先ほどとは逆に数値から浮動小数点型の内部データへ変換する方法について解説していきます。

数値から内部データへの変換

スポンサーリンク

スポンサーリンク

考え方

基本的には「浮動小数点数型内部データから数値への変換」とは逆の手順で変換を行います。

つまり、符号と仮数と指数を求め、さらにこれらの符号と仮数と指数から符号部・仮数部・指数部を求めます。

符号部の算出

符号部は、その数値の符号から簡単に求めることができます。

  • + の値(正の値):0
  • - の値(負の値):1

仮数部の算出

数値から浮動小数点型の内部データへ変換する場合は、仮数部を先に求めるのが良いと思います。

まずは10進数の数値の「符号を取り除いたもの」を2進数変換します。

さらに、この2進数変換したものを float 型や double 型における「仮数の形式」に変換します。仮数の形式とは 1.仮数部 の形式のことです。要は、整数部が2進数で 1 になるように小数点の位置を動かします。

小数点を動かす様子

この時、1.仮数部仮数部 の部分がまさに、求めるべき「仮数部」になります。

ただし、細かく言うとビット数を考慮する必要があります。

例えば float 型などでは仮数部のビット数(2進数の桁数)が 23 なので、仮数部 の部分が 23 桁に足りない場合は下位側のビットに 0 を格納して補います。23 桁を超える場合は、下位側のビットを捨てることで 23 桁に丸めます。

まとめると、下記のような手順で浮動小数点型の内部データにおける「仮数部」を算出することができます。

  1. 数値の符号を取り除く(正の値にする)
  2. 1. の結果を2進数に変換する
  3. 2. の結果を 1.仮数部 の形式になるように小数点の位置を移動させる
  4. 3. の結果の 仮数部 部分を2進数に変換する
  5. 必要に応じて 0 を補ったり、桁を切り捨てたりする

指数部の算出

で、仮数部の算出時に小数点を移動した桁数が指数になります。左方向に移動した場合は、指数の値は負の値になります。

指数が求まれば、あとは指数部を計算すれば良いだけです。例えば float 型の場合、指数は下記の式で算出できます。

指数 = 指数部の値 - 127

指数部を求めるのであれば逆に指数に対して +127 してやれば良いことになります。

指数部の値 = 指数 + 127

ただし、内部データとしては 01 の値、つまり2進数で格納しておく必要があるため、上記の計算結果に対して10進数から2進数変換する必要があります。

符号部・仮数部・指数部が算出できれば、あとはその結果を配列などに格納しておけば、内部データを求められたことになります。

まとめると、下記のような手順で浮動小数点型の内部データにおける「指数部」を算出することができます。

  1. 1. 仮数部の算出時に移動した桁数を指数とする
  2. 指数から指数部を求める
  3. 2. の結果を2進数変換する

プログラム

ここまで説明してきた考え方に基づいて「数値」から「float 型の内部データ」へ変換するプログラム例は下記のようになります。

内部データへの変換
#include <stdio.h>

#define NUM_BIT 32
#define MANTISSA_BIT 23
#define EXPONENT_BIT 8

int oct2bin_int(unsigned int *, int, int);
int oct2bin_float(unsigned int *, float, int);

/*
 * 10進数の正の整数を2進数に変換
 * bin:2進数を格納する配列のアドレス
 * oct:2進数に変換する10進数
 * max_bit:最大ビット数
 * return:ビット数
 */
int oct2bin_int(unsigned int *bin, int oct, int max_bit) {
    int i;

    /* 第0桁から値を算出していく */
    i = 0;
    while (oct > 0 && i < max_bit) {
        /* 剰余算で2進数の第n桁を算出 */
        bin[i] = oct % 2;

        /* 除算で第n桁目を切り捨て */
        oct = oct / 2;

        /* 次の桁へ */
        i++;
    }
    return i;
}

/*
 * 10進数の小数を2進数に変換
 * bin:2進数を格納する配列のアドレス
 * oct:2進数に変換する10進数
 * max_bit:最大ビット数
 * return:ビット数
 */
int oct2bin_float(unsigned int *bin, float oct, int max_bit) {
    int i;
    int int_oct;
    float under_oct;

    i = 0;
    while(oct != 0 && i < max_bit) {
        /* 冪数を+1 */
        oct = oct * 2;

        /* 整数部分のみを取り出す */
        int_oct = oct;

        /* 剰余算で 2 の 0 乗の項の値を取得し、
           小数点以下第 i + 1 桁の値として決定 */
        bin[i] = int_oct % 2;
        
        /* 小数点以下の値のみを取り出す */
        under_oct = oct - int_oct;
        
        /* 次の桁へ */
        i++;

        if (under_oct == 0) {
            /* 小数点以下が 0 なら終了 */
            break;
        }
    }
    return i;
}

int main(void) {

    unsigned int data[NUM_BIT] = { 0 }; /* 最終的な内部データ */
    unsigned int int_bin[MANTISSA_BIT+1] = { 0 }; /* 整数部の2進数変換結果 */
    unsigned int float_bin[MANTISSA_BIT+1] = { 0 }; /* 小数部の2進数変換結果 */
    unsigned int sign_part; /* 符号部 */
    unsigned int mantissa_part[MANTISSA_BIT] = { 0 }; /* 仮数部 */
    unsigned int exponent_part[EXPONENT_BIT]; /* 指数部 */
    float value; /* 数値 */
    float plus_value; /* 符号なしの数値 */
    int int_value; /* plus_valueの整数部 */
    float float_value; /* plus_valueの小数部 */
    int mantissa_bit;
    int exponent; /* 指数 */
    
    int int_bit; /* 整数部の桁数 */
    int float_bit; /* 小数部の桁数 */
    int ab_flag;
    
    int i;
    int shift_num;

    value = 0.625;

    if (value == 0) {
        /* 内部データの表示 */
        printf("data = ");
        for (i = 0; i < NUM_BIT; i++) {
            printf("0");
        }
        printf("\n");

        /* 数値の表示 */
        printf("value = %f\n", value);
        return 0;
    }

    /* 符号を取得し、符号を取り除く */
    if (value >= 0) {
        sign_part = 0;
        plus_value = value;
    } else {
        sign_part = 1;
        plus_value = -value;
    }

    /* 整数部と小数部に分割 */
    int_value = plus_value;
    float_value = plus_value - int_value;

    /* 整数部を2進数変換 */
    int_bit = oct2bin_int(int_bin, int_value, NUM_BIT+1);

    /* 小数部の桁数を計算 */
    float_bit = NUM_BIT+1 - int_bit;

    /* 小数部を2進数変換 */
    oct2bin_float(float_bin, float_value, float_bit);

    /* 全体の2進数変換結果を bin に格納 */
    ab_flag = 1;
    mantissa_bit = 0;
    for (i = int_bit - 1; i >= 0; i--) {
        if (ab_flag == 1) {
            /* 最初の1は省略する */
            if (int_bin[i] == 1) {
                ab_flag = 0;
            }
        } else {
            /* 上位の桁から順にmantissa_partに詰める */
            mantissa_part[mantissa_bit] = int_bin[i];
            mantissa_bit++;
        }
    }
    for (i = int_bit; i < MANTISSA_BIT+1; i++) {
        if (ab_flag == 1) {
            /* 最初の1は省略する */
            if (float_bin[i] == 1) {
                ab_flag = 0;
            }
        } else {
            /* 上位の桁から順にmantissa_partに詰める */
            mantissa_part[mantissa_bit] = float_bin[i - int_bit];
            mantissa_bit++;
        }
    }

    /* 指数を求める */
    exponent = 0;
    if (int_bit > 0) {
        /* 整数部が0以外の場合 */
        /* 上位ビットから1を探す */
        for (i = int_bit - 1; i >= 0; i--) {
            if (int_bin[i] == 1) {
                /* 最大のビットを指数とする */
                exponent = i;
                break;
            }
        }
    } else {
        /* 整数部が0の場合 */
        /* 上位ビットから1を探し、最大のビットを指数とする */
        for (i = 0; i < float_bit; i++) {
            if (float_bin[i] == 1) {
                /* 最大のビットをマイナスにした値-1を指数とする */
                exponent = -i - 1;
                break;
            }
        }
    }
    
    /* 指数から指数部(2進数)に変換 */
    for (i = 0; i < EXPONENT_BIT; i++) {
        shift_num = EXPONENT_BIT - 1 - i;
        if ((exponent + 127) & (1 << shift_num)) {
            exponent_part[i] = 1;
        } else {
            exponent_part[i] = 0;
        }
    }

    /* 内部データに各部のデータを格納 */
    data[0] = sign_part;

    for (i = 0; i < EXPONENT_BIT; i++) {
        data[i + 1] = exponent_part[i];
    }

    for (i = 0; i < MANTISSA_BIT; i++) {
        data[i + 9] = mantissa_part[i];
    }

    /* 内部データの表示 */
    printf("data = ");
    for (i = 0; i < NUM_BIT; i++) {
        printf("%u", data[i]);
    }
    printf("\n");

    /* 数値の表示 */
    printf("value = %f\n", value);

    return 0;
}

このページでは「内部データからの数値への変換」と「数値からの内部データへの変換」の2つのプログラムを紹介していますが、難易度はこちらの「数値からの内部データへの変換」の方が高いと思います。

難易度が高い理由は「10進数から2進数への変換」が難しいからです。

この変換は上記ソースコードでは整数の 2進数変換を oct2bin_int で、小数点以下の値の2進数変換を oct2bin_float の関数でそれぞれ実行しています。

これらの関数が何をやっているかよく分からない方は是非下記のページを参照していただければと思います。

10進数の2進数への変換方法解説ページアイキャッチ 【C言語】10進数から2進数への変換(正の整数編) 小数点以下の値を2進数変換する方法の解説ページアイキャッチ 【C言語】小数点以下の値を2進数へ変換

これらの2進数の変換結果は配列に格納するようにしていますが、プログラムの都合上、整数の場合は下位の桁から、少数の場合は上位の桁から配列に格納しているため、これを考慮してプログラムを作成しています。

配列への格納方向が逆なのでちょっとソースコードが読みにくくなっていると思います。この点に注意してソースコードを読んでいただければと思います。

まとめ

このページでは浮動小数点数、浮動小数点数型の内部データおよび浮動小数点数型の内部データと数値の相互変換方法について解説しました。

普段何気なく利用している float 型や double 型のデータがどのようなものであるかを理解していただけたのではないかと思います。

また情報系の科目を学んだり、基本情報技術者試験を受けたりした方は、符号部・仮数部・指数部という言葉はおそらくご存知だったのではないかと思います。

ただ実際に符号部・仮数部・指数部を計算したり、プログラミングで求めたりするような経験をしたことは割と少ないと思います。

私もそうでしたが、一度プログラミングしてみるとかなり浮動小数点数型について深く理解することができます。また2進数変換等もプログラミングする必要があるので結構難易度は高く、挑戦してみるとプログラミングの力を伸ばすこともできると思います。

是非皆さんも浮動小数点数型の内部データと数値の相互変換を行うプログラム開発に挑戦してみてください!

オススメの参考書(PR)

C言語学習中だけど分からないことが多くて挫折しそう...という方には、下記の「スッキリわかるC言語入門」がオススメです!

まず学習を進める上で、参考書は2冊持っておくことをオススメします。この理由は下記の2つです。

  • 参考書によって、解説の仕方は異なる
  • 読み手によって、理解しやすい解説の仕方は異なる

ある人の説明聞いても理解できなかったけど、他の人からちょっと違った観点での説明を聞いて「あー、そういうことね!」って簡単に理解できた経験をお持ちの方も多いのではないでしょうか?

それと同じで、1冊の参考書を読んで理解できない事も、他の参考書とは異なる内容の解説を読むことで理解できる可能性があります。

なので、参考書は2冊持っておいた方が学習時に挫折しにくいというのが私の考えです。

特に上記の「スッキリわかるC言語入門」は、他の参考書とは違った切り口での解説が豊富で、他の参考書で理解できなかった内容に対して違った観点での解説を読むことができ、オススメです。題名の通り「なぜそうなるのか?」がスッキリ理解できるような解説内容にもなっており、C言語入門書としてもかなり分かりやすい参考書だと思います。

もちろんネット等でも色んな観点からの解説を読むことが出来ますので、分からない点は別の人・別の参考書の解説を読んで解決していきましょう!もちろん私のサイトも参考にしていただけると嬉しいです!

入門用のオススメ参考書は下記ページでも紹介していますので、こちらも是非参考にしていただければと思います。

https://daeudaeu.com/c_reference_book/

同じカテゴリのページ一覧を表示