Code Fix

上級

0.1 + 0.2 == 0.3 が偽になる理由

doubleやfloatは2進数で値を表現するため、0.1や0.2のような小数を正確には表現できません。==でぴったり比較すると、ほぼ確実に一致しません。

なぜエラーが出ないのか

出力: not equal
(エラーなし)
Cは何も報告しません。コンパイルも実行も正常に完了するためです
出力: not equal
実際の挙動 — 期待した結果と食い違っている箇所
見つけ方
エラーが出ないので、出力を目で確かめるか、コードを目で追うしかありません。この種の誤りが最も発見が遅れます

このエラーが出る典型パターン

パターン1

 1  #include <stdio.h>
 2  #include <math.h>
 3  
 4  int main(void) {
 5      double a = 0.1 + 0.2;
 6      if (a == 0.3            ) {
                      ^
 7          printf("equal\n");
 8      } else {
 9          printf("not equal\n");
10      }
11      return 0;
12  }
出力: not equal期待: equal

0.1と0.2は2進数で正確に表現できないため、0.1 + 0.2はぴったり0.3にはなりません。==での比較はほぼ確実に失敗します。差の絶対値が十分小さいかどうか(許容誤差)で判定します。

直し方: a == 0.3 を fabs(a - 0.3) < 1e-9 にします。

この問題を解いてみる →

広告
広告スロット(未設定)

パターン2

 1  #include <stdio.h>
 2  #include <math.h>
 3  
 4  int main(void) {
 5      double b = 1.1 - 1.0;
 6      if (b == 0.1            ) {
                      ^
 7          printf("equal\n");
 8      } else {
 9          printf("not equal\n");
10      }
11      return 0;
12  }
出力: not equal期待: equal

引き算の結果も同じ問題を抱えます。1.1 - 1.0は数学的には0.1ですが、浮動小数点の内部表現では誤差を含みます。

直し方: b == 0.1 を fabs(b - 0.1) < 1e-9 にします。

この問題を解いてみる →

パターン3

 1  #include <stdio.h>
 2  #include <math.h>
 3  
 4  int main(void) {
 5      double c = 0.3 - 0.2;
 6      if (c == 0.1            ) {
                      ^
 7          printf("equal\n");
 8      } else {
 9          printf("not equal\n");
10      }
11      return 0;
12  }
出力: not equal期待: equal

型をfloatからdoubleに変えても解決しません。2進数で0.1を正確に表現できないという根本的な制約は変わらないためです。

直し方: c == 0.1 を fabs(c - 0.1) < 1e-9 にします。

この問題を解いてみる →

パターン4

 1  #include <stdio.h>
 2  #include <math.h>
 3  
 4  int main(void) {
 5      double d = 0.7 - 0.6;
 6      if (d == 0.1            ) {
                      ^
 7          printf("equal\n");
 8      } else {
 9          printf("not equal\n");
10      }
11      return 0;
12  }
出力: not equal期待: equal

引き算の結果でも同じ誤差が生じます。==での比較は避け、許容誤差で判定します。

直し方: d == 0.1 を fabs(d - 0.1) < 1e-9 にします。

この問題を解いてみる →

パターン5

 1  #include <stdio.h>
 2  #include <math.h>
 3  
 4  int main(void) {
 5      double e = 0.1 * 3;
 6      if (e == 0.3            ) {
                      ^
 7          printf("equal\n");
 8      } else {
 9          printf("not equal\n");
10      }
11      return 0;
12  }
出力: not equal期待: equal

掛け算でも同じ誤差が生じます。0.1 * 3は0.3ぴったりにはなりません。

直し方: e == 0.3 を fabs(e - 0.3) < 1e-9 にします。

この問題を解いてみる →

よくある誤解

型をfloatからdoubleに変えても解決しません。精度が上がるだけで、2進数で0.1を正確に表現できないという根本的な制約は変わらないためです。小数の比較は、差の絶対値が十分小さいかどうか(許容誤差)で判定します。

実務での勘所

許容誤差を自分で適当な小さい数値として決め打ちするより、<float.h>に定義されているDBL_EPSILON(double型で表現できる隣り合う値の最小の差)を基準にする方が移植性が高くなります。float型を使う場合はFLT_EPSILONという別の定数を使う必要があります。比較する数値の大きさによっては、EPSILONそのものではなく、比較対象の値でスケールさせた相対誤差を使う必要がある点は他の言語とも共通です。

演習をはじめる

関連するエラー

広告
広告スロット(未設定)