数値計算の教科書などにfor文のloop変数に浮動小数点型のdoubleやfloatを使ったらダメという記述がみられます。整数型を使いなさいという記述があります。
以下は数値計算に関するサイトではありませんが、
https://www.jpcert.or.jp/java-rules/num09-j.html
NUM09-J. 浮動小数点数型変数をループカウンタとして使用しない
上記に
浮動小数点数型変数をループカウンタとして使用してはならない。精度に限界のある IEEE 754 浮動小数点数型は、次に示すように、すべての値を表現できるわけではない。
とあります。確かに正しい記述です。
実際以下のコードでは正しくループ回数がカウントされません。一回余計に実行されます。(OS:CentOS7.9, CPU: AMD64, gcc-4.8.5で確認)
#include <stdio.h>
int
main ()
{
double i;
long int count = 0;
for (i = 0.0; i < 1000000.0; i += 0.1)
{
count++;
}
printf ("ループ回数 = %ld\n", count);
return 0;
}
$ gcc double_loop.c
$ ./a.out
ループ回数 = 10000001
というように回数が正しくなくなります。これは10進数0.1が2進数では循環小数となるからです。
10進数: 0.1
2進数: 0 . 000110011..(循環小数)
循環小数になるため、丸め誤差により+=0.1の足し算が正しくおこなわれないため、ループ回数が正しく実行されません。
この問題は循環小数になるからダメなだけです。この点注意していただきたい。この点を忘れている人が時々います。
10進数の整数は2進数で有限桁数で正確に表現できます。double型の仮数部の有効桁数内なら正確に2進数で表現できます。
なのでこんな感じに小数0.1を足す処理を整数1を足す処理に置き換えればfor文で正しくループが実行されます。
#include <stdio.h>
int
main ()
{
double i;
long int count = 0;
for (i = 0.0; i < 1000000.0 * 10; i += 1.0)
{
count++;
}
printf ("ループ回数 = %ld\n", count);
return 0;
}
$ gcc double_loop2.c
$ ./a.out
ループ回数 = 10000000
ですので、
#整数演算ならfor文のloop変数にdoubleを使っても丸め誤差は起きません
C言語の浮動小数点の実装は処理系依存なのですが、IEEE754に準じた処理系の場合は仮数部は52bitでケチ表現を使っているため、
0から2^53-1までの整数は正確に計算できます。
なので、loop変数に対して整数演算しか行わない場合はなんら問題ありません。但し除算を除く。
#除算は例外
除算を行う場合は、計算結果が小数になった場合、整数演算なら切り捨てになりますが、浮動小数点演算の場合は切り捨てにならず小数になりますので、扱いが異なります。
なのでfor文で除算を使う場合は除きます。加減乗算については結果が同じです。
繰り返しますが、仮数部の有効桁数を超えない場合のみです。
#なぜ浮動小数点型のみやり玉に挙げられるのか?
整数の場合であってもint型で加算や乗算で2^31-1を超えた場合、正しい演算が行われません。何故浮動小数点のみやり玉にあげられるのは腑に落ちません。
#とは言え
doubleをloop変数にするのはお勧めはしませんが(^_^;
#最後に
偉い方からお叱りを受けそうな記事ですが、声を大にして言いたい。
十分理解して書いてるコードなのに、よく見もせずにfor文でdoubleを使ってるコードは間違いであると断言しないで頂きたい。
#追記(12/25)
コメントを頂いてますが、double_loop.cのコードのループ回数が1回多くなってしまうのは丸め誤差の影響に加えて、情報落ちの影響が大きいようです。
学問が浅い人間が思い付きで書くとよくありません。ご指摘ありがとうございました。
参考文献を挙げておきます。私がdobuleを使うとループ回数が正しくない事が起きるという話を学習したは次の書籍になります。少々古い本ですが、今でも役に立つと思います。
現在でも刊行されてるんですね。ちょっと驚き。また有名な書籍ですので、理系の大学図書館とか置いてあることが多いと思います。