HEXA BLOG

プログラム

HEXA BLOGプログラム2009.12.16

浮動小数点誤差

こんにちは。平尾です。
朝布団から出るのが辛い時期になってきました雪
ついつい二度寝…なんてことをしたくなります。
そんな折、こんな商品があるのを発見!
http://www.sleeptracker.jp/
意外と高額です…。
サンタさんが運んで来てくれないでしょうかクリスマスプレゼント

気を取り直して、今日は浮動小数点誤差について話をします。
プログラムを始めた頃は誰もがハマってしまう道ではないかと思います。
C言語にはfloatという小数を扱える型(浮動小数点型)があります。
ところが、意外なことに正確な 0.1 を表現できないのですもうやだ〜(悲しい顔)
これだけ技術が進歩した世の中なのに、なぜなのでしょうか…。
コンピュータは2進数で数値を記憶します。
floatの場合、一般的には
  ・符号部 1 ビット
  ・指数部 8 ビット
  ・仮数部 23 ビット
といった形になります。(特殊なハードは除きます)
0.1を2進数で表現する場合、
001111011100110011001100110011001100110011001100…
と割り切れない値(循環小数)になってしまうため、どこかで区切らないといけませんend
32bit float の場合、33bit目が丸められて
00111101110011001100110011001101
という形になります。
このように最後のビットが丸められるため、正確な値を保持できません。
例えば次のような処理を行ったとします。

  1. float val = 0.0f;
  2. for( int i=0; i<10; i++ ) {
  3. val += 0.1f;
  4. }
  5. if( val != 1.0f ) {
  6. printf("val は 1.0です");
  7. } else {
  8. printf("val は 1.0ではありません");
  9. }

10回0.1を足しているので1になってくれないと困るのですが、「1.0ではありません」と表示されますがく〜(落胆した顔)
これは上記の丸め誤差があるため、正確に1.0にならないためです。
浮動小数点を=で比較するのはいけません。
ではどう比較すれば良いのでしょうか?
続きはWEBで…
あ、WEB上でしたわーい(嬉しい顔)
「一定の範囲で比較する」というのが答えです。

  1. if( ((1.0f - 0.1f) < val) && (val < (1.0f + 0.1f)) ) {
  2. printf("val は 1.0です");
  3. } else {
  4. printf("val は 1.0ではありません");
  5. }

でもさすがに0.1の誤差は大きすぎます。
そんな時に使用するのが FLT_EPSILON という定義です。
FLT_EPSILON は float.h で定義されているもので、
値としては 1.192092896e-07F (つまり0.0000001192092896) となっています。
定義内容としては「1.0+FLT_EPSILON !=1.0 となる最小値」となっています。
ちょっと乱暴ですが、分かりやすく言うと「すごく小さな値」ということになります。
浮動小数点の誤差を考慮する場合、この値を利用して判定するのが一般的ですひらめき

  1. if( ((1.0f - FLT_EPSILON) < val) && (val < (1.0f + FLT_EPSILON)) ) {
  2. printf("val は 1.0です");
  3. } else {
  4. printf("val は 1.0ではありません");
  5. }

使用用途などによってはFLT_EPSILONでは判定できない、なんてこともあるかと思います。
そういうときは
  ・単純に判定する範囲を広げる
  ・double型を使用して精度を上げる(double型の場合はDBL_EPSILONになります)
  ・固定小数点(整数型で小数点を表現する)
など、いろいろ解法はあると思います。
浮動小数点にはこういった誤差が存在しますので、単純な比較をしないよう、皆さんも十分ご注意下さい!ぴかぴか(新しい)

こういったプログラム話が大好きな学生の方、ご応募お待ちしております!わーい(嬉しい顔)

RECRUIT

大阪・東京共にスタッフを募集しています。
特にキャリア採用のプログラマー・アーティストに興味がある方は下のボタンをクリックしてください

RECRUIT SITE 

S