DOSEIの日記

技術メモ+日常ログ

逐次更新ループの書き方

反復計算などで、前回の値を利用して、新しい値を逐次的に計算するプログラムについて。
例として、平方根の計算を考えると、

 x_{n+1} = \frac{1}{2}\left( x_n + \frac{a}{x_n} \right)
を、 x_0=1 の初期値 (なんでもいいけど) から順に計算していけばいい。

プログラムでは、途中の値を残す必要はないので、順次上書きしていくことにする。で、若かりしころは以下のように書いていた。

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 ループになることが多い。