ページ 11

float型のオーバーフローについて

Posted: 2016年1月08日(金) 11:12
by cattail
お久しぶりです。cattailです。
また少し教えて頂きたいことがあります。
float型のオーバーフローのことなのですが、

float test = 0.0f;

として、ミリ秒間隔で、

test = test + 0.001f;

としています。
オーバーフローを起こした場合、値は無限大(infinity)になるというのをネットで見つけたのですが、よくわかりません。
testのオーバーフローを検出するプログラムはどう書けばいいのでしょうか?
よろしくお願い致します。

Re: float型のオーバーフローについて

Posted: 2016年1月08日(金) 11:42
by 超初級者
そもそも、float型の最大値である
約10^38秒
も実行時間がかかるプログラムなのでしょうか。

Re: float型のオーバーフローについて

Posted: 2016年1月08日(金) 11:52
by cattail
超初級者さんご返事ありがとうございます。
お恥ずかしいことなのですが、
約10^38秒という意味が理解できないのです。
すごく長~い時間は桁あふれなんてしないということでしょうか?
ゲームで何時間かプレイしても大丈夫くらいでしょうか。

あ、今テスト中のプログラムが32768.000で止まりました!
これって、オーバーフローしてますか?
僕のプログラムの他の部分が悪いのかなあ?

Re: float型のオーバーフローについて

Posted: 2016年1月08日(金) 12:24
by cattail
オーバーフローを起こした場合、値は無限大(infinity)になるというのは、
最大値で止まるということなのでしょうか?
メッシュをアニメーションさせているのですが、25分くらいでアニメが高速になり、50分くらいで止まってしまいます。
32768.000で止まる前に16384.000のときが25分のときとすると合ってるのかな?
符号付きだとそうなるのでしょうか?
if(test >= 16384.0f){
     test = 0.0f;
}
として実験中です。

Re: float型のオーバーフローについて

Posted: 2016年1月08日(金) 13:43
by cattail
どうも解決したみたいです!
ありがとうございました!
float型の上限が32768.000?なのか16384.000?なのかわかりませんが、
if(test >= 16384.0f){
     test = 0.0f;
}
でオーバーフロー?しないで動いています。
ちょっと懐疑的なところもありますが、解決とさせていただきます。
ありがとうございました。

Re: float型のオーバーフローについて

Posted: 2016年1月08日(金) 14:12
by softya(ソフト屋)
えーと、浮動小数点の有効桁を理解しないといけません。
+ 0.001f;
が誤差扱いで加算できなくなっただけでオーバーフローはしていません。
floatの扱い方が間違っているだけです。

Re: float型のオーバーフローについて

Posted: 2016年1月08日(金) 16:50
by YuO
softya(ソフト屋)さんの書かれている通り,単純に情報落ちが発生しているのでしょう。

コード:

#include <stdio.h>

int main(void) {
	float current = 0.0f;
	
	for (float old = -1.f; old != current; old = current, current += 0.001f)
		;
	printf("%f\n", current);

	return 0;
}
というコードを実行すると,Ide.one上(gcc 5.1)では32768.000000と表示されます。
浮動小数点数の比較に!=を使っているので誤差の違いがあれば加算が続くようにしているのですが,それでも上記の値が表示されます。
# 普通はfabsとかで差をとって大きさ比較する
つまり,32768.00000f + 0.001f == 32768.00000fとなっています。

浮動小数点数を使う以上,誤差は必ず出てくるので,特有の誤差についてちゃんと調べておくことをお薦めします。

Re: float型のオーバーフローについて

Posted: 2016年1月08日(金) 17:20
by cattail
softya(ソフト屋)さん、
YuOさん、
ご返事頂いてありがとうございます!
浮動小数点の有効桁とか、浮動小数点の特有の誤差とか全然知りませんでした。
今ネットで検索して調べてみたのですが、本当の値と近似値とか難しくてまるでわかりませんでした。
大変恐れ入りますが、
+ 0.001f;
を誤差扱いで加算できなくならないようにするにはどうすればいいのでしょうか?
または、
if(test >= 16384.0f){
     test = 0.0f;
}
とやっても誤差内なのでしょうか?
申し訳ありませんが教えていただけないでしょうか?

Re: float型のオーバーフローについて

Posted: 2016年1月08日(金) 21:19
by softya(ソフト屋)
有効桁は2進数では正確ですが、10進数では約7桁とアバウトになります。まぁ、6桁で使うのが普通です。
で、桁数ですが小数部なくして考えてください。
+ 0.001f;なら1000倍しましょう。
1000倍した16384.0fは
16384000ですので、
12345678と8桁あります。
これは有効桁を超えるので利用できません。

と言うことで、加算で有効桁数が多く必要ならintを使いましょう。
floatは乗算除算に向いています。

Re: float型のオーバーフローについて

Posted: 2016年1月08日(金) 22:00
by cattail
softya(ソフト屋) さん、
教えて頂きありがとうございます!
以下、間違っていると恥ずかしいですが笑わないでー。

>10進数では約7桁とアバウトになります。
7桁以上が表現できないからということでしょうか?

>有効桁を超えるので利用できません。
1000倍してintにして計算の時だけ比較する、のかな?

算数が不得意なのです。
明日、実際にやってみたいと思います。

Re: float型のオーバーフローについて

Posted: 2016年1月08日(金) 22:10
by softya(ソフト屋)
元々2進数なので、10進数では6.9・・・と少数点の付く桁数になります。
なので、7桁使うと誤差出るかもしれないし、大丈夫かもしれない。組み合わせ次第です。
数学的には正確に問題が出るか計算できますが、そこまで知りたくないなら7桁以上はやばい!ぐらいでよいです。

>1000倍してintにして計算の時だけ比較する、のかな?
それで十分です。常に1000倍で表示だけ工夫すればよいでしょう。
ただ、どこまで上限値が行くのかで64bit整数が必要になるかもしれません。
提示された情報にはありませんでしたので。

Re: float型のオーバーフローについて

Posted: 2016年1月09日(土) 08:01
by cattail
softya(ソフト屋)さん、
おはようございます!
色々ありがとうございます!

>それで十分です。常に1000倍で表示だけ工夫すればよいでしょう。
>ただ、どこまで上限値が行くのかで64bit整数が必要になるかもしれません。
やってみる前から、すごく自信が出てきました!
これからやってみます!
後ほど報告させていただきますね。

Re: float型のオーバーフローについて

Posted: 2016年1月09日(土) 09:09
by cattail
なんか混乱して停滞中です。

>1000倍した16384.0fは
>16384000ですので、
>12345678と8桁あります。
ということは、

if(test >= 16384.0f){
     test = 0.0f;
}

の16384.0も16384.000まで使用しているので有効ケタではないということでしょうか?
つまり、
0に+0.001をする有効ケタは、0.0から999.999までの最大6ケタで、

if(test >= 999.999f){
     test = 0.0f;
}

とすれば安心でしょうか?

Re: float型のオーバーフローについて

Posted: 2016年1月09日(土) 10:05
by みけCAT
cattail さんが書きました: if(test >= 999.999f){
     test = 0.0f;
}

とすれば安心でしょうか?
浮動小数点数を工夫せずに比較するのは、演算誤差により「間違う」リスクがあり、安心ではありません。
(リーディングゼロを含めて)6桁固定の数を扱うのであれば、32ビットであることが保証された整数のint32_tを用いた「固定小数点数」を用いるといいでしょう。

Re: float型のオーバーフローについて

Posted: 2016年1月09日(土) 10:33
by cattail
みけCATさん、
ありがとうございます!

int32_tをVS8では使えないのでしょうか?
DWORDで以下のようにしたらどうでしょうか?

コード:

DWORD a = 0;
float b = 0;

b = test;   //testはミリ秒で0.001加算される変数
b = b * 1000;
a = DWORD(b)


if(a >= 999999){
     test = 0.0f;
}

Re: float型のオーバーフローについて

Posted: 2016年1月09日(土) 10:41
by みけCAT
cattail さんが書きました:DWORDで以下のようにしたらどうでしょうか?
あまり意味無いでしょう。そもそも、セミコロンが抜けていたり全角スペースが入っていたりする時点でダメです。
無駄な浮動小数点演算はなるべく避けましょう。

コード:

DWORD test = 0;
float b = 0;

b = test / 1000.0f; //testはミリ秒で1加算される変数
if(test >= 999999){
    test = 0;
}
もしくは、全くfloat型を使わずに文字列にするなら

コード:

DWORD test = 0;
char b[12];

//testはミリ秒で1加算される変数
//sprintf(b, "%03d.%03d", (int)(test / 1000), (int)(test % 1000));
//sprintfが使える環境なら、以下の8行の代わりに上のsprintfを用いるべき
//sprintfが使えない場合でも、ループにするなど改善の余地はある
b[0] = (test / 100000) % 10 + '0';
b[1] = (test /  10000) % 10 + '0';
b[2] = (test /   1000) % 10 + '0';
b[3] = '.';
b[4] = (test /    100) % 10 + '0';
b[5] = (test /     10) % 10 + '0';
b[6] = (test /      1) % 10 + '0'; // 1で割るのは無駄だが、コードの統一感を出す
b[7] = '\0';

if(test >= 999999){
    test = 0;
}
オフトピック
条件式はこれで大丈夫なのかな?999.999「の次」に000.000にしたいわけじゃないのかな?

Re: float型のオーバーフローについて

Posted: 2016年1月09日(土) 10:57
by cattail
みけCATさん、
色々書いて頂いてすみません。

コード:

DWORD test = 0;
float b = 0;
 
b = test / 1000.0f; //testはミリ秒で1加算される変数
if(test >= 999999){
    test = 0;
}
書いて頂いた上のコードでは、加算される変数自体をDWORDで持って処理するということでしょうか?

それからまさに000.000にしたいので、test = 0;のあとにb = 0;として
実際に処理に使用するfloat変数をbにすればいいということでしょうか?

Re: float型のオーバーフローについて

Posted: 2016年1月09日(土) 11:09
by みけCAT
cattail さんが書きました:書いて頂いた上のコードでは、加算される変数自体をDWORDで持って処理するということでしょうか?
はい。
cattail さんが書きました:それからまさに000.000にしたいので、test = 0;のあとにb = 0;として
実際に処理に使用するfloat変数をbにすればいいということでしょうか?
すいません。それだと二度手間なので、変換処理の前に0にする処理を持ってくるといいでしょう。

コード:

DWORD test = 0;
float b = 0;

//testはミリ秒で1加算される変数
if(test >= 999999){
    test = 0;
}
b = test / 1000.0f;

Re: float型のオーバーフローについて

Posted: 2016年1月09日(土) 11:21
by cattail
みけCATさん、
何度も何度もすみませんでした!
書いて頂いたコードでやってみます!

普段から簡潔なコードを心がけていないので、「あ、そうか」の連発で。
自分もそういうふうにコードを書いていきたいと思います。
ただ、ちょっと頭が付いていかないので限界もあるかもしれないです。

今作っているゲームにfloatの演算がたくさんあるので直すのが大変です!

皆様、ありがとうございました!