逐次更新ループの書き方
反復計算などで、前回の値を利用して、新しい値を逐次的に計算するプログラムについて。
例として、平方根の計算を考えると、
プログラムでは、途中の値を残す必要はないので、順次上書きしていくことにする。で、若かりしころは以下のように書いていた。
double x = 1; while(1) { double x_new = (x + a/x)/2.0; if(is_converge(x_new, x)) break; // 前回の値と比べて、収束を判定 x = x_new; // 更新 } // output x as result
これのよくないなと思うところは、最初の計算が終わった後の主役は x_new
であって、最後の瞬間まで x
は過去の値である。より複雑なプログラムになると、 x_new
の値がさらに改変された x_new2
が出てくるハメになる。さらに、前回の値である x
は、最後の方まで変更せずに維持しなければならないが、これはコードでは保証できない。
なので、考えた結果、今は必ず以下のルールで書くことにしている。
x は常に最新の値を持っている
したがって、ループローカル変数には、前回の値などの古い値を const で持つようにする。
double x = 1; do { double const x_prev = x; x = (x_prev + a/x_prev)/2.0; if(is_converge(x, x_prev)) break; } while(1); /* infinite loop*/ // output x as result
せっかくの while ループなのに、無限ループにして、途中で収束条件を判定して break するのは結構ダサい。ほんとは、これを素直に、
double x = 1; do { double const x_prev = x; x = (x_prev + a/x_prev)/2.0; } while(!is_converge(x, x_prev)); // [error] // output x as result
と書ければいいが、条件式にはループローカルの変数が書けない。かければいいのに。 do-while だけでもいいから、かけるようにしてくれればいいのに。(しつこい)
しょうがないので、ループ条件でも生きてる変数を用意することになるが、それでも最小限の影響範囲でいてほしいので、さらにブロックで囲むとよいかと思う。
double x = 1; { bool isConv; do { double const x_prev = x; x = (x_prev + a/x_prev)/2.0; isConv = is_converge(x, x_prev); } while(!isConv); } // output x as result
ちなみに、大体の場合において、最大の反復回数を指定して、収束しない場合を諦めるという予防線をはるので、 while ループは、反復回数のカウンタによる for ループになることが多い。