home / junkbox / cordic CORDICアルゴリズム

 COordinate Rotation DIgital Computer の略だそうです。 事実上加減算とシフトだけで三角関数を求められるアルゴリズムで、ずいぶん昔からあるらしいですが、最近まで知りませんでした。 三角関数以外にも双曲線関数をはじめとしていろいろなものが求められます。 双曲線関数CORDICについて述べられているサイトが少なかったので、まとめてみました(覚え書きとも言う)。

参考: cordic methods(英語)

home / junkbox / cordic 三角関数

 三角関数については他の方が詳しく説明されているので、ここでは流れだけを説明します。 代数的には加法定理の応用で、cosβをくくり出して、第2項のtanが2の負べき乗になるようにテーブルを変更し、くくり出したcosβを事前に計算できるよう、条件分岐を「足す・足さない」ではなく、「正・負」に変更したものです。 最後の条件分岐の変更は、除算で言う「復元法(加え戻し法)」と「非復元法(引き放し法)」の違いに相当します。

 x = cosθ, y = sinθを求めるとして、θを α0 + S0β0 + S1β1 + ... のように分解します。 たとえば、 α0 = 0 , β0 = 51.2 , β1 = 25.6 , β2 = 12.8 , β3 = 6.4 , β4 = 3.2 , β5 = 1.6 , β6 = 0.8 , β7 = 0.4 , β8 = 0.2 , β9 = 0.1 , として、θを10倍して2進数にし、各桁を Sk に当てはめれば、0.1度単位でθを表すことができます。 そして、 x0 = cosα0 , y0 = sinα0 として、次の漸化式を必要な精度まで計算することで、cosθとsinθが求まります。

αk+1 = αk + Skβk

xk+1 = cosαk+1

= cos (αk + Skβk )

= cosαk cos Skβk - sinαk sin Skβk

= xk cos Skβk - yk sin Skβk

yk+1 = yk cos Skβk + xk sin Skβk (同様に)

 この漸化式で cos Skβk をくくりだします。

xk+1 = cos Skβk ( xk - yk sin Skβk / cos Skβk )

= cos Skβk ( xk - yk tan Skβk )

yk+1 = cos Skβk ( yk + xk sin Skβk / cos Skβk )

= cos Skβk ( yk + xk tan Skβk )

 ここで、βk による分解を、βk を 2m-k にするのではなく、tanβk = 2m-k になるように分解すれば、つまり、βk = tan-1 2m-k としてしまえば、tanβk に関する乗算がなくなり、単なるシフトになります。

xk+1 = cos Skβk ( xk - yk Sk 2m-k )

yk+1 = cos Skβk ( yk + xk Sk 2m-k )

ただし、こうするとθの2進分解ができなくなるので、漸化式を計算する都度αkk をθと比べて Sk を決定しなければなりません。 このとき、もし、Sk を「足すか足さないか」、つまり、1 か 0 ではなく、「足すか引くか」、つまり、+1 か -1 にすれば、くくり出した cos Skβk は cos (±βk ) = cosβk になり、Sk が影響しなくなります。 したがって、M = cosβ0・cosβ1・cosβ2・・・cosβn をあらかじめ計算しておいて最後に乗じれば正しい答が得られます。

xk+1 = xk - yk Sk 2m-k

yk+1 = yk + xk Sk 2m-k

cosθ = M xn

sinθ = M yn

ただし M = Πcosβk

これがCORDICの基本的な形で、最後に M を乗じる以外、乗算がなくなります。 x と y の初期値が定数の場合、定数にあらかじめ M を乗じておけば最後の乗算もなくなります。 βk = tan-1 2m-k をラジアンで求めておけば、全体の計算はラジアンで行われます。 度で求めておけば度で行われます。 2π/ 360 をかけて変換を行う必要はありません。

 初期値として α0 = 45度 とすることが多いようですが、α0 = 0 としても動作します。 前者の場合は ( sqrt ( 2 ) / 2 , sqrt ( 2 ) / 2 ) が初期値ですが、( 1 , 1 ) を初期値として計算しても構いません。 この場合、最後に乗じる定数が変わります。 逆に、( M sqrt ( 2 ) / 2 , M sqrt ( 2 ) / 2 ) を出発点にすれば最後の乗算を省けます。 後者の場合は ( 1 , 0 ) が出発点ですが、これも ( M , 0 ) を出発点にすれば最後の乗算を省けます。

 級数展開と比べると乗算の回数は圧倒的に減りますが、必要な精度を得るための演算回数はそんなに減りません。 したがって、乗算器がないシステムでは圧倒的に有利ですが、乗算も加算も1クロック、といったシステムではかえって遅くなることもあります。 条件分岐も必要なので、条件分岐のコストが高いシステムも不利です。 また、実行時に計算の打ち切り位置を変えられる級数展開と違って、分解能はテーブルによって決まってしまいますから、0近くの有効桁が減ってしまいます。 このため、浮動小数には向きません。 乗算器のない8ビットCPUで、固定小数で値を求めればいい場合などに向きます。

 ハードウェアで実現する場合は加算器をパイプライン接続すれば、乗算器ひとつと同程度のハードウェア規模で1クロックにつきsin・cosがひとつずつ求まりますから、面積・速度とも圧倒的に有利です。

●tanθ

 CORDICでは cosθと sinθが同時に求まるので、これを使って tanθ= sinθ/ cosθで求めます。 除算が必要になってしまいますが、最後に比を求めることになるので、Mを乗じる必要はありません。

●tan-1 t

 CORDICを逆に使います。 今まではθで Sk を決めていましたが、スタート地点を x0 = 1 , y0 = t として、yk+1 が 0 に近づくように Sk を決めます。 つまり yk > 0 なら sk = -1 、 そうでなければ sk = +1 です。 このとき、α0 = 0 として積算していくと、-αn が tan-1 t になっています。 必要なのは角度だけなので、M を乗じる必要はありません。

●tan-1 ( Y / X ) と ベクトルの大きさ

 tan-1 t と同じですが、初期値を x0 = X , y0 = Y とします。 tan-1 だけが欲しい場合は角度だけあればいいので M の出番はありません。 こちらは X = 0 でも tan-1 が求まります。 CORDICは幾何学的にはベクトルを ±βk 回転させて、大きさを 1 / cosβk 倍していることになるので、最終的に求まったベクトルの大きさに M をかけると sqrt ( X2 + Y2 ) が求まります。 yn が 0 になるように積算したのですから、最終的なベクトルの大きさは xn になるはずです。 つまり、M xn が元のベクトル ( X , Y ) の大きさ(絶対値)になります。

●sin-1 s ・ cos-1 c

 冒頭の参考サイトでは x0 = M 、y0 = 0 として、θの変わりに y と t を比較することで計算しています。 これだとなんとなく動くような動かないような気がするので、c = sqrt ( 1 - s2 ) を求めて、tan-1 s/c を求めた方がいい気がします。 c は双曲線CORDICで求まります。 c から s を求めれば cos-1 c が求められます。

home / junkbox / cordic 双曲線関数

 双曲線関数でも加法定理が成り立つので、同じようにCORDICを使うことができます。

xk+1 = xk + yk Sk 2m-k

yk+1 = yk + xk Sk 2m-k

cosh t = M xn

sinh t = M yn

ただし、

Sk = ±1

M = Πcosh βk

βk = tanh-1 2m-k

 表面上、三角関数と違うのは x の項が減算ではなく加算になることくらいですが、問題がふたつあります。 一つ目は分解に関する問題で、三角関数のときに βk = tan-1 2m-k と置きなおしましたが、これが許されるのは、

βk+1 > βk / 2

tan-1 ( t / 2 ) > ( tan-1 t ) / 2

( tan β ) / 2 > tan ( β / 2 )

ただし β> 0 , t > 0

の関係があるからです。 元々は βk+1 = βk / 2 になっていたのを変えたわけですが、k が1増えたときに、βが元の半分以上ないと分解結果に隙間が開いてしまいます。 ところが、双曲線関数では

( tanh β ) / 2 < tanh ( β / 2 )

と、関係が逆になっています。 これは tan と tanh の曲線の曲がり方が逆だからです。 tanh t は、原点と曲線上の点 ( cosh t , sinh t ) を結ぶ直線の傾きです。 双曲線関数が作る双曲線の漸近線が y = ±x ですから、tanh t は漸近線の傾き、つまり ±1 の範囲内にあります。 したがって、y = tanh x の漸近線は y = ±1 です。 これはX軸に平行な直線です。 一方で三角関数 y = tan x の漸近線は原点付近では x =±π/ 2 です。 こちらはY軸に平行な直線です。 つまり、tan の傾きはだんだん急になるのに対し、tanh の傾きはだんだん緩やかになります。

 三角関数の場合、たとえば β0 = tan-1 1 , β1 = tan-1 2-1 , β2 = tan-1 2-2 , ・・・ として Sk = ±1 のすべての組み合わせについて ΣSkβk がどの位置にくるかを示したのが下の図です。 横軸の単位は度です。

スキマなく並び、0付近では一部が重複していることが分かります。

 双曲線関数で β0 = tanh-1 2-1 , β1 = tanh-1 2-2 , β2 = tanh-1 2-3 , ・・・ として、同じことをすると、以下のようになります。 横軸は双曲角。

線は128本あるはずですが、一部にスキマができ、中央のスキマが一番広くなることが分かります。 この問題を解決する必要があるのですが、冒頭のサイトでは3回に1回は分解を2回行うことで解決しています。

 もうひとつは定義域に関する問題で、Skβk による分解には上限があり、これは双曲線CORDICの入力の上限になります。 上の図では±1ぐらいから上は表現できないことが分かります。 三角関数でも上限はあるのですが、周期性があるのであまり困らないのです。 双曲線関数の場合は(実数については)周期がないので、XY平面上で漸近線に近い値を扱う際には注意が必要です。

●cosh t ・ sinh t
 ( M , 0 ) を起点とした双曲線CORDICで求まりますが、t が1以上になる場合は倍角の公式 cosh ( 2 t ) = 2 cosh2 t - 1 、 sinh ( 2 t ) = 2 sint h cost h などを使って求めることになるのかな。
●tanh t
 双曲線CORDICでは cosh t と sinh t が同時に求まるので、tanh t = sinh t / cosh t で求めます。 M を乗じる必要はありません。
●tanh-1 t
 x = 1 , y = t として双曲線CORDICを逆に使います。 必要なのは双曲角なので、Mは不要です。 t が 0.76 を超えると答が1を超えるようになるので、工夫が必要です。 log と tanh-1 は表裏一体ですが、 log は正しく計算できる範囲を超えた場合に対数法則で細工ができるので、 一度 log に直して、対数法則を適用してから log を tan-1 を使って計算しなおすと、

tanh-1 t = 0.5 log { ( 1 + t ) / ( 1 - t ) }

= 0.5 { log ( 1 + t ) - log ( 1 - t ) }

= 0.5 [ log ( 1 + t ) - log { ( 1 - t ) 2n / 2n } ]

= 0.5 [ log ( 1 + t ) - log { ( 1 - t ) 2n } + log 2n ]

= 0.5 [ log ( 1 + t ) - log { ( 1 - t ) 2n } + n log 2 ]

= 0.5 log [ ( 1 + t ) / { ( 1 - t ) 2n } ] + 0.5 n log 2
= tanh-1 ( 1 + t ) / { ( 1 - t ) 2n } - 1 + 0.5 n log 2
( 1 + t ) / { ( 1 - t ) 2n } + 1
= tanh-1 { ( 1 + t ) - ( 1 - t ) 2n } / { ( 1 - t ) 2n } + 0.5 n log 2
{ ( 1 + t ) + ( 1 - t ) 2n } / { ( 1 - t ) 2n }
= tanh-1 ( 1 + t ) - ( 1 - t ) 2n + 0.5 n log 2
( 1 + t ) + ( 1 - t ) 2n

という関係があるので、( 1 - t ) 2n が1〜4くらいになるように n を決めて計算するといいのかな。 tanh-1の分子分母は加減算とシフトだけで計算できます。 tanh-1自体は次の tanh-1 Y / X で計算するので、除算はいりません。 0.5 n log 2 は log 2 が定数ですから、単なる乗算です。

 t が-0.76以下になったときは各自の課題とします。

●tanh-1 Y / X
 x = X , y = Y として双曲線CORDICを逆に使います。 Y / X が 1 に近いときは、
tanh-1 t = tanh-1 ( 1 + t ) - ( 1 - t ) 2n + 0.5 n log 2
( 1 + t ) + ( 1 - t ) 2n

から、

tanh-1 y/x = tanh-1 ( 1 + y/x ) - ( 1 - y/x ) 2n + 0.5 n log 2
( 1 + y/x ) + ( 1 - y/x ) 2n
= tanh-1 x { ( 1 + y/x ) - ( 1 - y/x ) 2n } + 0.5 n log 2
x { ( 1 + y/x ) + ( 1 - y/x ) 2n }
= tanh-1 x ( 1 + y/x ) - x ( 1 - y/x ) 2n + 0.5 n log 2
x ( 1 + y/x ) + x ( 1 - y/x ) 2n
= tanh-1 ( x + y ) - ( x - y ) 2n + 0.5 n log 2
( x + y ) + ( x - y ) 2n

なので、( x - y ) 2n が x + y の 0.5〜2倍程度になるように n を決めればいいのかな。

●sinh-1 s , cosh-1 c
 cosh2 c - sinh2 s = 1 より cosh c = sqrt ( 1 + sinh2 s ) 、これは三角関数CORDICで求まります。 sinh s = sqrt ( cosh2 c - 1 ) 、これは双曲線CORDICで求まります。 ここから tanh-1 s / c で sinh-1 s , cosh-1 c が求まります、たぶん。
●ex
 cosh x + sinh x = ex から求めます。
●log x
 log x = 2 tanh-1 { ( x - 1 ) / (x + 1 ) } から求めます。 どうしてこうなるかは 双曲線関数 に書いてあります。 tanh-1 Y / X を使えば割り算する必要はありません。 上限は8くらい、下限は0.12くらい。 範囲外は log ( x 2n ) = log x + n log 2 を使って求めることになるのでしょう。
●sqrt b
 x2 - y2 = a2 を満たす x , y を選んで y が 0 になるように双曲線CORDICを逆に使うと、 双曲線と座標系 に書いたように、双曲線 x2 - y2 = a2 を y = 0 に向かって動くことになるので、最後にMを乗じると x = a , y = 0 になります。 a2 を b で置き換えると、a = sqrt ( b ) ですから、b の 平方根が求まったことになるのですが、問題はこのような x , y を簡単に決められるか、です。

 bと定数の加減算だけで求めることを考えると、x = b + M , y = b + N と置いて、

( b + M )2 - ( b + N )2 = b

b2 + M2 + 2 M b - b2 - N2 - 2 N b = b

M2 + 2 M b - N2 - 2 N b = b

2 b ( M - N ) + M2 - N2 = b

となるので、右辺と左辺を見比べて、

2 ( M - N ) = 1

M2 = N2

です。 これを解くと M = 1 / 4 , N = - 1 / 4 になります。 したがって、 x = b + 1 / 4 , y = b - 1 / 4 からCORDICを逆に使うと、M x が sqrt ( b ) になります。

bが2を超えると tanh-1 ( b - 1/4 ) / ( b + 1/4 ) が 1 を超えるようになるため、4n で割るなどして対応する必要があります。 0に近いほうは0.03くらいまで。

●sqrt ( x2 - y2 )
 ( x , y ) から双曲線CORDICを逆に使うと、M x が sqrt ( x2 - y2 ) になります。 たとえば sin2θ = 1 - cos2θ ですから、x = 1 , y = cosθ からはじめれば cosθ から sinθ を求めることができます。 同様に sinθ から cosθ を求めることもできますし、 cosh2 u - sinh2 u = 1 より sinh2 u = cosh2 u - 1 ですから、( x = cosh u , y = 1 ) から始めれば、sinh u が求まります。 いずれも結果は必ず正になりますので、符号については別に考える必要があります。

 結果が0に近くなる条件では tanh が1を超えるようになり、正しい結果が得られなくなります。 そこで、tanh-1 のときの変形

tanh-1 y/x = tanh-1 ( x + y ) - ( x - y ) 2n + 0.5 n log 2
( x + y ) + ( x - y ) 2n
から、( u = ( x + y ) + ( x - y ) 2n , v = ( x + y ) - ( x - y ) 2n ) を開始点として選ぶと、求まるのは w = sqrt ( u2 - v2 ) ですから、

w2 = u2 - v2

= { ( x + y ) + ( x - y ) 2n }2 - { ( x + y ) - ( x - y ) 2n }2

= ( x + y )2 + 2 ( x + y ) ( x - y ) 2n + { ( x - y ) 2n }2 - ( x + y )2 + 2 ( x + y ) ( x - y ) 2n - { ( x - y ) 2n }2

= 4 ( x + y ) ( x - y ) 2n

= 4・2n ( x2 - y2 )

となって、 a2 = x2 - y2 と比べると w2 = 4・2n a2 となっているので、 n を偶数 2m にしておけば w から簡単に a = w / 2m+1 と a が求まることが分かります。


Copyright (C) 2012 You SUZUKI

$Id: cordic.htm,v 1.4 2014/03/29 10:43:07 you Exp $