平方根を速く求める

[戻る]

仕事上, 時折マイコンのような低性能なコンピュータのプログラムを組んでます. この手のコンピュータは基本すべての演算を整数型で行わなくてはいけません. 整数型の方が圧倒的に速いわけですから…

そのためアルゴリズムはメモリ操作と条件分岐, 和算, 差算, 積算, そしてたまに除算を使って作り上げます. しかしたまにどうしても平方根を求めたい時があります. 一番簡単な方法はテーブル作って参照させることですが, 求めたい範囲が 32bit 整数の正の範囲 (0〜2147483647) になると現実的ではありません. 正攻法は例えば次のようなニュートン法を使うことでしょう.

int Sqrt(int a){ // 本題でないので a<=1 の例外処理は略
 int x = a >> 1; // ここで適当に初期値を設定
 int y;
 do{
  y = x;
  x = (x + a/x) >> 1;
 } while (x !=y);
 return x;
}
しかしこの方法は何度もループを回さなくてはならず, その都度マイコンの苦手な除算があります. ステップ数の予測もつきません.

要は一行目の初期値です. これを適当に近い値にすればいいのです. というわけで以下のようなコードをあみだしました.

int Sqrt(int a){
 if (a<=0)
  return 0;
 if (a==1)
  return 1;
 int x;

 if (a <= 38408)
  x = (a >> 7)+11;
 else if (a<=1411319)
  x = (a >> 10)+210;
 else if (a<=70459124)
  x = (a >> 13)+1414;
 else if (a<=794112116)
  x = (a >> 15)+7863;
 else
 x = (a>>17)+26038;

 x = (x +a/x) >> 1;
 x = (x +a/x) >> 1;
 return x;
}
条件文が多いですが漸化式を二回しか使っていません.

条件分岐は少々分かりにくいですが, 32bit 整数の正の範囲 (0〜2147483647) すべてで真値との誤差が 1 以下に なるよう調整した結果です. 当然分岐を増やせば 0.5 以下になるのでしょうが, そこまでの精度は普通要りません (1 以下でもオーバースペックです).


2014.04