質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.47%

  • プログラミング言語

    765questions

    プログラミング言語はパソコン上で実行することができるソースコードを記述する為に扱う言語の総称です。

  • アーキテクチャ

    91questions

    アーキテクチャとは、情報システム(ハードウェア、OS、アプリケーション、ネットワーク等)の設計方法、設計思想、設計思想に基づいて構築されたシステム構造をアーキテクチャと呼びます

浮動小数点数についてです。

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 976

strike1217

score 564

以前、浮動小数点数について質問したのですが、今回はフォーマットについてです。

double型のメモリ領域
面倒なので今回もdouble型に限定しますね。

#include<iostream>
#include<math.h>

int main(){
    double x = 1.7320508075688774152212090484681539237499237060546875;
    int exp;

    double k = frexp(x, &exp);

    printf("%1.80lf = \n%1.80lf x 2 ^ %05d\n", x, k, exp);
    return 0;
}
1.73205080756887741522120904846815392374992370605468750000000000000000000000000000 = 
0.86602540378443870761060452423407696187496185302734375000000000000000000000000000 x 2 ^ 00001


この結果をよーーくみると変ではないでしょうか?
私の本のIEEE754の書式によりますと、以下のようになるはずです。

浮動小数点数 = (-1)^S x (1 + 仮数部) x 2^(指数-127)
つまり、最小は、1.0000 0000 0000 0000 0000 ・・・(2)x2 ^ (-1072)

仮数部の最小値は1以上になるはずです。
しかし上記のプログラムの仮数部はどうみても1から始まってないでよね?

この書式の違いは一体どういう事でしょうか?

詳しく理解できなかったのですが、先頭に1がある表記を正規化した科学記数法というそうです。
なので上記のプログラムは不正規化数ということになると思います。

不正規化数が演算の対象に出てきた場合には例外を発生させて、あとはソフトウェアで補うようにしているコンピュータが多い。

ちょっと、何が言いたいのかわからないのですが、frexp関数による書式がIEEE754のフォーマットに準拠していない理由は何でしょうか?非常に分かりにくいです。
混乱しています。
また歴史が絡む問題でしょうか?IEEE754が規定される前に作られた関数なのかな・・?と考えましたが・・・

2、誤差に関する確認事項です。
超簡単にするために、0.02568という数字を考えます。
何らかの演算の結果の仮数部の数字と考えて下さい。

この場合、
ガード桁:5
丸め桁:6
スティッキー・ビット:8

これで正しいですか?
スティッキービット = 丸め桁の右側に0でないビットがあることを示すものである。
つまり、double型の仮数部の52bitの最後の3桁に特別な名前を付けただけという事ですかね?

2番については間違いがあったらご指摘をお願いします。

「追記」

#include <iostream>
#include <math.h>

int main()
{
    float value = 1.73214;

    int exp;
    float x = std::frexp(value, &exp);

    printf("%1.60lf = \n", value); 
    printf("%1.60lf * 2^ %08d\n", x, exp);

    union { float f; int i; } a;
    a.f = value;

    printf("%f ( %08X )\n", a.f, a.i);

    /* ビットの列を表示します */
    for (int i = 31; i >= 0; i--) {
        printf("%d", (a.i >> i) & 1);
    }
    printf("\n");

    /* 指数部( 1ビット )、指数部( 8ビット )、仮数部( 23ビット )を取り出します */
    printf("符号部 : %x\n", (a.i >> 31) & 1);
    printf("指数部 : %x\n", (a.i >> 23) & 0xFF);
    printf("仮数部 : %x\n", a.i & 0x7FFFFF);
}


1.732139945030212402343750000000000000000000000000000000000000 =
0.866069972515106201171875000000000000000000000000000000000000 * 2^ 00000001
1.732140 ( 3FDDB6C3 )
00111111110111011011011011000011
符号部 : 0
指数部 : 7f
仮数部 : 5db6c3

結果はこのようになりました。

問題はここからで、
仮数部の16進数を10進数に直すと、6141635となります。
(指数部を10進数にすると127でした。)

frexp関数の仮数部とは大きく異なる値です。
そして、この6141635から0.866069972515106201171875にどうやって変換しているのかさっぱりです。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

checkベストアンサー

+1

EEE754が規定される前に作られた関数なのかな・・?と考えましたが・・・ 

その通り、歴史的理由です。
C言語がUnix上で発展してきた事はご存じでしょうが、初期のUnixは今は無きDEC(COMPAQが吸収合併さらにHPと合併)のPDP-11というコンピューター(ミニコン)上で動いていました。
PDP-11や後継のVAX-11が使っていた浮動小数点は、仮数部が0.5以上1未満になるように正規化されていました。
Unix上のCで扱う浮動小数点型の関数はそれをモデルにしていますので、その後に定められたIEEE754(仮数部が1以上2未満)とその点が合いません。

以前色々検索したのですが、PDP-11の浮動小数点フォーマットを記した日本語ページは見つかりませんでした。インターネット以前のIT界情報は少ないです。
私の記憶では、上記以外は、ビット構成など同じだったはずです。
IEEE754を知った時、「え?仮数部が1以上2未満なの?」とビックリした記憶があります。

追記に対して

仮数部の16進数を10進数に直すと、6141635となります。

そこがそもそも間違っています。

0x5db6c3は、小数部ですが、23bitなので、省略された24bit目に整数部1を付けて、0xddb6c3  が仮数です。小数点は、先頭ビットの次にありますので2進数で表すと、0b1.101 1101 1011 0110 1100 0011 。指数は 0x7fから127を引いて0ですね。
仮数部を0.5以上にしないといけないので、仮数を2で割って、指数に1を足します。全体の値としては変わりません。仮数を2で割るというのは、2進数で小数点を1桁左へ移動させるということなので、0b0.1101 1101 1011 0110 1100 0011
仮数を10進数に直すと、0.5+0.25+0.0625+(以下略) = 0.8660699~
これに2の1乗を掛けた物が、元の数値になります。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/06/20 10:30

    なるほど!
    そうなんですか!

    キャンセル

  • 2017/06/20 10:34

    frexp関数通りにビット列がメモリ上に格納されている訳ではないんですよね?
    0.86602540378443870761060452423407696187496185302734375000000000000000000000000000
    これがメモリ上に入っていた場合は、
    +1されるので、1.866025403784438... と解釈されてしまいますよね?

    キャンセル

  • 2017/06/20 10:43

    > frexp関数通りにビット列がメモリ上に格納されている訳ではないんですよね?
    メモリ上は、「そのCPUの扱う浮動小数点フォーマット」でしょう。
    x86だとIEEE754ですね。

    > +1されるので、1.866025403784438... と解釈されてしまいますよね?
    +1とは何の事ですか?

    キャンセル

  • 2017/06/20 10:46

    浮動小数点数 = (-1)^S x (1 + 仮数部) x 2^(指数-127) IEEE754の仮数部の「+1」のことです!

    frexp関数で内部を覗こうとしてもx86CPUだとあてにならないということですね!
    あまり使えない関数ですね・・・

    キャンセル

  • 2017/06/20 12:19

    内部を覗くなら、メモリを直接見ないと駄目です。
    printf("%016X\n", x);

    fexpが返すのはメモリに格納されている内容とは異なり論理的な値です。これはPDP-11でも同じ。

    キャンセル

  • 2017/06/20 14:07

    浮動小数点数 = (-1)^S x (1 + 仮数部) x 2^(指数-127)
    とは、メモリの中の2進数のビットを10進数に直す方法の1つという事ですか?

    キャンセル

  • 2017/06/20 15:51

    10進数というか、表す数値に直す方法です。2進数か10進数かはそれをどう表現するかで決まります。
    「方法の1つ」とお書きですが、方法は形式ごとに唯一です。
    お書きの物は、IEEE_754単精度の場合ですね。倍精度だと違いますし、PDP-11形式でも違います。

    キャンセル

  • 2017/06/20 16:15

    IEEE_754形式でもPDP-11形式でもメモリの内容は同じなんですか?
    表現の仕方か異なるという事ですよね?

    キャンセル

  • 2017/06/20 16:24

    > IEEE_754形式でもPDP-11形式でもメモリの内容は同じなんですか?
    違いますよ。「数値をメモリに格納する形式に変換する方法」「メモリ内容を数値に変換する方法」が、形式ごとに違います。IEEE_754でも形式は何通りもあります。
    方法が違えばメモリ内容も違う。

    キャンセル

  • 2017/06/20 16:30

    IEEE754形式のx86cpuでなぜPDP-11形式であるfrexp関数が使用可能なのでしょうか?
    無理やり変換しているんですかね?

    キャンセル

  • 2017/06/20 16:43

    > IEEE754形式のx86cpuでなぜPDP-11形式であるfrexp関数が使用可能なのでしょうか?

    別にfrexpはPDP-11形式というわけではありません。
    「仮数部が、0.5以上1未満」という仕様がPDP-11などに由来するということだけです。

    キャンセル

  • 2017/06/20 17:27

    誤解のポイントが判ったような気がします。
    frexp関数が内部形式を返す関数だと思っているのですね。違いますよ。

    キャンセル

  • 2017/06/20 17:45

    「frexp関数が内部形式を返す関数だと思っているのですね。」
    そうです!!

    違うんですか?

    キャンセル

  • 2017/06/20 17:52

    frexp関数とは昔の形式に直すだけですか?
    ちょっとよくわからないですね。

    キャンセル

  • 2017/06/20 18:17

    細かいところを省くと、浮動小数点数を、『「0.5以上1未満の数」×2の「整数」乗』に変換して返す関数です。
    内部形式とたまたま一致する場合もあるし、一致しない場合もある。

    細かいところ・・・符号、ゼロの場合、無限大の場合、非数の場合

    キャンセル

  • 2017/06/20 18:30

    あ、やっと分かりました。
    x86_cpuはIEEE 754に則っているが、frexp関数を使用すると昔の形式に由来されているので昔の形式で表されるわけですね!

    キャンセル

  • 2017/06/20 18:50

    ベストアンサーにかなり迷いましたが、させてもらいますね。
    chironianさんの方にも+1しておきました。

    キャンセル

  • 2017/06/20 20:16

    ・・・しかしよく考えると、まだピンと来ないですね・・・

    内部表現がIEEE 754なのにどうして『「0.5以上1未満の数」×2の「整数」乗』の形式に変換できるのかが分からないですね・・・・??

    キャンセル

  • 2017/06/20 20:38

    どんな形式でも変換できますよ。なぜできないと思うのか不思議でなりません。

    キャンセル

  • 2017/06/20 20:59

    そうなんですか!
    ・・・

    メモリ内の2進数がIEEE 754に則っているなら、異なる形式に変換すると別の数値になってしまうんじゃないかと思うんですが・・・

    キャンセル

  • 2017/06/20 21:19

    すいませんが、どこで詰まってらっしゃるのわからないので、説明できません。

    キャンセル

  • 2017/06/20 21:26

    追記いたしますね。少々お待ちください。

    キャンセル

  • 2017/06/20 21:57

    追記しました。

    キャンセル

  • 2017/06/21 07:22

    やっと理解できました。
    お手数をかけました。

    キャンセル

+1

こんにちは。

仮数部の最小値は1以上になるはずです。

IEEE754ではその通りですね。

しかし上記のプログラムの仮数部はどうみても1から始まってないでよね?

frexp関数が返却する値がIEEE754で決められている仮数部をそのまま返却しなければならない理由は特にないと思います。
IEEE754で定められた仮数部を2で割った値を仮数部とし、指数部を+1しても大した問題はないと思います。

そして、正規化しない場合、同じ値浮動小数点値に対して、仮数部と指数部の組み合わせが複数発生し、取り扱いが非常に面倒です。IEEE754やfrexp関数は正規化しています。

IEEE754は0でない場合、最上位ビットを小数点の左隣に定めました。
frexp関数は、0でない場合、最上位ビットを小数点の右隣に定めました。[1/2, 1)は、0.5以上1未満です。これは小数点の右隣がちょうど1になるな範囲です。
つまり、正規化方法が異なるだけです。

もしも、frexp関数の非0の時の仮数部を[1, 2)と定めていればIEEE754の正規化と一致しますが、恐らくfrep関数の仕様の方が先に決っているはずです。
あ、上記のリンク先の最後にそのように書かれてました。

Why does frexp() not yield scientific notation?
frexp()が戻り値を[1, 2)の範囲ではなく、[0.5, 1)の範囲に収めるようにしている理由は、IEEE 754およびISO/IEC 60559が策定される前に作られた関数であることが理由と考えられる

次の質問ですが、

ガード桁:5
丸め桁:6
スティッキー・ビット:8

Guard Bit, Round Bit, Sticky Bitの意味なら、全く違うようです。

仮数部の下に,ガード ビット(Guard bit),ラウンド ビット(Round Bit),スティッキービット(Sticky Bit)の3つのビットが用意されている.これら3つのビットの値はFPUが演算中に自動でセットするもので,ユーザーが値をとりだして演算に利用することはできない(できなくなっているのが普通).

仮数部が53ビットの時、更にその下の3ビットのことですね。
仮数部の先頭ビットを第0ビットとした時、第53, 54, 55ビットです。
FPUが最近値丸め(Round to Nearest)処理を行うためのFPUの内部処理用のビットということです。FPUを設計するのでないかぎり、気にする必要はないだろうと思います。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/06/20 10:37

    「IEEE754やfrexp関数は正規化しています。 」
    正規化の方法が相対的に異なるのですね!
    厄介ですね。

    「仮数部の先頭ビットを第0ビットとした時、第53, 54, 55ビットです。」
    そうなんですか!
    分かりました。

    キャンセル

  • 2017/06/20 10:42

    浮動小数点数の仮数部が53bitとなっているのは、ユーザー側からみた場合ということですよね?
    メモリ上では、55bitまでの領域が存在していることになるのでしょうか?

    キャンセル

  • 2017/06/20 14:43

    普通にFPUを使う際には、スティッキービット等の追加の3ビットはFPUの内部にのみ存在します。主記憶には記録されません。

    例えばCPUが足し算処理する際にキャリービットを使いますね。
    そのキャリービットに近い意味合いです。CPU内部にのみ存在し、主記憶には記録されません。(キャリーはプログラムから見れますが、スティッキービット等は見れないようですけどね。)

    キャンセル

  • 2017/06/20 14:53

    なるほど!!
    わかりました。

    キャンセル

  • 2017/06/20 22:46

    追記いたしました。

    キャンセル

  • 2017/06/20 23:34

    float型でやったのですが、
    1.732140
    1.6141635(IEEE 754形式)
    かなり値が異なるようです・・・
    誤差が大きすぎませんか?

    キャンセル

  • 2017/06/21 00:06

    > 指数部 : 7f
    > 仮数部 : 5db6c3

    これは1.73213994503021240234375をIEEEE754形式で表現した時の指数部と仮数部の内部表現を16進数で表したものですね?
    元の値は1.73213994503021240234375ですから、誤差は全くないようです。(当たり前ですが。)

    指数部は127の下駄履き表現です。0x7f=127ですから、0x7f-127=0なので、指数の値は0です。
    指数の値が0ですから、仮数部の0x5db6c3はちょうど小数点以下のものです。それに1を加えると元の値になります。
    16進数で小数点以下の計算は難しいので2進数(00111111110111011011011011000011)から、変換してみて下さい。同じ値になることを確認できると思います。

    2進数で0.1は1/2です。0.01は1/4です。0.001は1/8です。以下同文。
    小数点下第i桁のビットが1ならば、その値は1/(2^i)です。
    1であるビットの値を全て足せば元の値を回復できます。

    キャンセル

  • 2017/06/21 07:22

    やっと理解できました。
    お手数をかけました。

    キャンセル

+1

追記にのみ回答

仮数部
0x_5db6c3は
二進表記で
0b_101 1101 1011 0110 1100 0011
IEEE754に基づくと、この値は(1+小数部分)の小数部分に当たるので
0b_1 + 0b_0.101 1101 1011 0110 1100 0011
= 0b_1.101 1101 1011 0110 1100 0011
= 0d_1.7321399450302124

指数部は127バイアスされているので127-127=0

そういうわけでちゃんと
1.7321399450302124 * 2^0になっています。

要約すると

仮数部の16進数を10進数に直すと、6141635となります。

が誤りで、ここは固定小数点数です。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/06/20 23:45

    おお!!
    変換の仕方に間違いがありましたか!
    すいません。

    frexp関数は、仮数部を1.7321399450302124 から 0.866069972515106201171875に変換しているだけということですか?

    キャンセル

  • 2017/06/20 23:50 編集

    そうですね
    メモリにはIEEE754形式でちゃんと値:1.73~が格納されていて(当然)
    frexp()はその値(1.73~)から彼なりの浮動小数点表記を計算して仮数部0.866~ と 指数1を返しますよってことです。

    キャンセル

  • 2017/06/20 23:59

    あああ
    やっとわかりました。

    frexp関数の仮数部を1以下にするように2で割る代わりに指数部を割るごとに増やせば、内部がIEEE754でも別の形式に変換できるわけですね!!

    ありがとうございます。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.47%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る

  • プログラミング言語

    765questions

    プログラミング言語はパソコン上で実行することができるソースコードを記述する為に扱う言語の総称です。

  • アーキテクチャ

    91questions

    アーキテクチャとは、情報システム(ハードウェア、OS、アプリケーション、ネットワーク等)の設計方法、設計思想、設計思想に基づいて構築されたシステム構造をアーキテクチャと呼びます