自然対数を速く求める

[戻る]

前回, 整数型しか扱えないマイコンで平方根を求める方法を述べましたが, 別の時, 自然対数を求めたいことがありました. その時得た Tips です.

幸い精度はあまり必要なかったので, テーブル参照を使ったのですが得たい範囲を均等に分割した単純なテーブルだと 使い物になりません. そこで若干工夫することにしました (実際工夫してあるアルゴリズムをネットで 見つけ再実装しただけですが…).

このアルゴリズムは, 整数型 (2 進数) の nbit 目は 2^n を表していることを 利用してます. まず 0 でなく 1 となっている一番大きい桁を探します.

それを mbit 目, m-1, m-2, m-3... bit 目をそれぞれ a,b,c... とすると その数:
と書けます. すなわちこの数の log は
と表現できます. 第一項の log2 は定数ですし, 第二項の log の中身は 1〜2 の値です. 適当な bit (例えば 5bit) で分解すればテーブルを作ることは可能です. 精度を高めるにはテーブルを細かくしてもいいですが, 幸い logX の微分は 1/X なので, その分を足せばいいでしょう.

といった感じで組んだコードが以下の通りです. 整数型で返すため 2048 倍された値を返すようにしています.

int Log(int a){
 int Tbl[32] = { // 8192 log (1+N/32) のテーブル
  0, 252, 497, 734, 965, 1189, 1408, 1621,
  1828, 2030, 2227, 2420, 2609, 2793, 2973, 3149,
  3322, 3490, 3656, 3818, 3977, 4133, 4286, 4437,
  4584, 4729, 4872, 5012, 5150, 5285, 5418, 5549
 };

 if (a<=1)
  return 0;

 int m=0;
 int k=1;
 int x=a;
 int y;

 while (x>=k){ // 最上位 bit を求める
  m++;
  k<<=1;
 }
 m--;

 k>>=1;
 x ^= k;

 if (m>5){
  x >>= m-5;
  y = (x + 0x20) << (m-5);
 } else if (m<5){
  x <<= 5-m;
  y = (x+0x20) >> (5-m);
 } else {
  y = x + 0x20;
 }

 // これで a を (1 + x/32) 2^m で表現したときの x と m と
 // y = (1 + x/32) 2^m が求まる

 m *= 5679;// 8192 log(2)
 m += Tbl[x]; // + 8192 log(1+x/32)
 m <<=2; // 8192 →2048

 // 補間処理
 y = a-y;
 if (y)
  m += (2048*y)/a;

 return m;
}


2014.04