浮動小数点数型変数をループカウンタとして使用してはならない。精度に限界のある IEEE 754 浮動小数点数型は、次に示すように、すべての値を表現できるわけではない。
- 単純な有理数すべてを厳密に表現できるわけではない。
- すべての10進数を厳密に表現できるわけではない。10進数では少ない桁数で表現できる場合でも、2進数では正確に表現できないことがある。
- 大きな値のすべての桁を表現できるとは限らない。値の大きな浮動小数点数をインクリメントしたとしても、利用可能な精度の範囲では値は変わらないかもしれない。
違反コード
以下の違反コード例では浮動小数点数型変数をループカウンタとして使用している。0.1という小数は、float型でもdouble型でも厳密に表現することはできない。
for (float x = 0.1f; x <= 1.0f; x += 0.1f) { System.out.println(x); }
0.1fはfloat型で表現できる近似値に丸められるため、繰り返し処理でxに加算される実際の値は0.1よりもわずかに大きい。それゆえ、ループは9回だけ実行され、期待とは異なる結果を出力する。
適合コード
以下の適合コードではint型整数をループカウンタとして使用しており、期待した浮動小数点数が得られる。
for (int count = 1; count <= 10; count += 1) { float x = count/10.0f; System.out.println(x); }
違反コード
以下の違反コードでは、浮動小数点数型変数をループカウンタとして用いてインクリメントしているが、与えられた精度では値が小さすぎて変化しない。
for (float x = 100000001.0f; x <= 100000010.0f; x += 1.0f) { /* ... */ }
このコードは無限にループする。
適合コード
以下の適合コードではint型変数をループカウンタとして使用し、その値を元に浮動小数点数を算出している。またdouble型を使い、求める値を表現するために十分な精度が得られることを保証している。また、FP-strict モードで実行しても可搬性のある結果が得られる。詳しくは「NUM06-J. どのプラットフォームでも一貫した浮動小数点数演算を行うために strictfp 修飾子を使う」を参照。
for (int count = 1; count <= 10; count += 1) { double x = 100000000.0 + count; /* ... */ }
リスク評価
ループカウンタに浮動小数点数を使用すると、プログラムの予期せぬ動作につながる恐れがある。
ルール | 深刻度 | 可能性 | 修正コスト | 優先度 | レベル |
---|---|---|---|---|---|
NUM09-J | 低 | 中 | 低 | P6 | L2 |
自動検出
浮動小数点数型ループカウンタの検出は容易である。
関連ガイドライン
The CERT C Secure Coding Standard | FLP30-C. Do not use floating point variables as loop counters |
The CERT C++ Secure Coding Standard | FLP30-CPP. Do not use floating point variables as loop counters |
ISO/IEC TR 24772:2010 | Floating-point Arithmetic [PLF] |
参考文献
[Bloch 2005] | Puzzle 34. Down for the Count |
[JLS 2005] | §.2.3, "Floating-Point Types, Formats, and Values" |
翻訳元
これは以下のページを翻訳したものです。
NUM09-J. Do not use floating-point variables as loop counters (revision 68)