CORDICアルゴリズムによるTan関数の近似

# はじめに
CORDICは、関数電卓などに用いられる簡易的な関数近似アルゴリズムである。そこで今回は、CORDICを用いてPythonで三角関数のTan関数の値を近似することを目的とし、誤差の評価を行う。

# アルゴリズム

CORDICのアルゴリズムは以下の大変分かりやすい記事を参考にした。

https://teamcoil.sp.u-tokai.ac.jp/calculators/column/100224/index.html


例として、$tan 60^\circ$の近似値を算出することを考える。

以下のアルゴリズムについて図を用いて説明する。

(1)$tan 45^\circ$は上辺と底辺の比率が1対1の直角三角形の傾きである。

(2)上辺と底辺の比率が1対2の直角三角形を(1)の三角形の斜辺に底辺が来るような倍率で付け加える。

(3)目的角（図の赤線）が$60^\circ$と比較して大きいかどうかを調べ、大きければ、1対$2^2$の比率の直角三角形を(2)と同様のアルゴリズムで付け足し、小さければ負の角度方向に直角三角形を付け足す。

(4)(1)から(3)の操作を可能なだけ繰り返すことで、理論上は$tan 60^\circ$に漸近する近似値を求めることができる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1686019/89654d66-2c4c-2d9f-8cc6-311dcc02c3de.png)

ただしこれには、(2)に相当するアルゴリズムで、$Arctan(\frac{1}{2^k})$の値を予め求めておく必要性がある。($k$は試行回数である。)


ここで、Arctanのマクローリン展開は単純な式によって以下のように与えられる。(tan関数のマクローリン展開は凄く複雑になる)

https://manabitimes.jp/math/975

https://manabitimes.jp/math/1381



```math
Arctan x=\Sigma^{\infty}_{n=1}\frac{(-1)^{n-1}}{2n-1}x^{2n-1}
```

これをもとに、以下のようなプログラムを作成した。

# プログラム
## 基礎となるプログラム
```　python cordic_basic.py
import numpy as np
import matplotlib.pyplot as plt
import math
import japanize_matplotlib
#m次までのアークタンジェントの計算（マクローリン展開）
m=17
n=50
err = 1.0*10**(-2)
def Arctan(x):
  arctan=0
  for i in range(1,m):
      x_i=((-1)**(i-1)/(2*i-1))*x**(2*i-1)
      arctan=arctan+x_i
  return arctan
#print(Arctan(1)*180/math.pi)
theta_degree=30
theta = (math.pi/180)*theta_degree

x_k=1
y_k=1

theta_c =(math.pi/180)*45
k=1
#CORDICアルゴリズムメイン
while abs(theta_c-theta) > err:
  z_k= x_k+1j*y_k
  if theta_c < theta:
      theta_c =theta_c+Arctan(1/(2**(k)))
      delta_z_k=(z_k)*1j*(2**(-(k)))
  else:
      theta_c =theta_c-Arctan(1/(2**(k)))
      delta_z_k=-(z_k)*1j*(2**(-(k)))
      # print(delta_z_k)
  z_k=z_k+delta_z_k
  x_k=z_k.real
  y_k=z_k.imag
      #print(y_k/x_k)
  k=k+1
  #print(y_k/x_k)
  if k>n:
    break
# 推定結果の表示
print(y_k/x_k)
```

## tan曲線の描写
``` python cordic_tan.py
import numpy as np
import matplotlib.pyplot as plt
import math
import japanize_matplotlib
import matplotlib.animation as animation

m=17
n=50
err = 1.0*10**(-2)
def Arctan(x):
  arctan=0
  for i in range(1,m):
      x_i=((-1)**(i-1)/(2*i-1))*x**(2*i-1)
      arctan=arctan+x_i
  return arctan

#print(Arctan(1)*180/math.pi)

num=100

theta_degree_ary=np.linspace(0,85,num)
tan_ary=[]
for p in range(num):
  theta_degree=theta_degree_ary[p]
  theta = (math.pi/180)*theta_degree
  x_k=1
  y_k=1
  theta_c =(math.pi/180)*45
  k=1
  while abs(theta_c-theta) > err:
    z_k= x_k+1j*y_k
    if theta_c < theta:
        theta_c =theta_c+Arctan(1/(2**(k)))
        delta_z_k=(z_k)*1j*(2**(-(k)))
    else:
        theta_c =theta_c-Arctan(1/(2**(k)))
        delta_z_k=-(z_k)*1j*(2**(-(k)))
        # print(delta_z_k)
    z_k=z_k+delta_z_k
    x_k=z_k.real
    y_k=z_k.imag
        #print(y_k/x_k)
    k=k+1
    #print(y_k/x_k)
    if k>n:
      break
  #print(y_k/x_k)
  tan_ary.append(y_k/x_k)
    #print(k)
plt.title('Cordicアルゴリズムによるtan関数の計算')
plt.plot(theta_degree_ary,tan_ary,color='blue',label='tan_near(θ)')
plt.plot(theta_degree_ary,np.tan(theta_degree_ary*math.pi/180),color='red',label='tan(θ)')
plt.xlabel('θ[degree]')
plt.ylabel('tan_near(θ)')
plt.legend()
plt.savefig("Cordicアルゴリズムによるtan関数の計算.png")
plt.show()
```

これを実行すると以下の様な近似曲線を得ることができる。

![Cordicアルゴリズムによるtan関数の計算.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1686019/ee5aa8ab-a56b-fdd0-9502-f84e93bd394f.png)

このようにかなり正確であるといえる。ただし、御存じの通り$90^\circ$付近でtan関数は発散してしまうので角度が大きくなるにつれ近似精度が悪化する傾向がある。
## 誤差と試行回数の関係
次に、$30^\circ$においてCORDICを回していった際の計算精度について評価するために以下の様なプログラムを作成した。

``` python cordic_err.py
import numpy as np
import matplotlib.pyplot as plt
import math
import japanize_matplotlib
import matplotlib.animation as animation
m=17
n=50
err = 1.0*10**(-2)
def Arctan(x):
  arctan=0
  for i in range(1,m):
      x_i=((-1)**(i-1)/(2*i-1))*x**(2*i-1)
      arctan=arctan+x_i
  return arctan
#print(Arctan(1)*180/math.pi)
theta_degree=30
theta = (math.pi/180)*theta_degree
x_k=1
y_k=1
theta_c =(math.pi/180)*45
k=1
tan_theta_near_ary=[]
err_ary=[]
k_ary=[1]
while abs(theta_c-theta) > err:
  z_k= x_k+1j*y_k
  if theta_c < theta:
      theta_c =theta_c+Arctan(1/(2**(k)))
      delta_z_k=(z_k)*1j*(2**(-(k)))
  else:
      theta_c =theta_c-Arctan(1/(2**(k)))
      delta_z_k=-(z_k)*1j*(2**(-(k)))
      # print(delta_z_k)
  z_k=z_k+delta_z_k
  x_k=z_k.real
  y_k=z_k.imag
      #print(y_k/x_k)
  k=k+1
  #print(y_k/x_k)
  if k>n:
    break
  tan_theta_near=y_k/x_k
  tan_theta_near_ary.append(tan_theta_near)
  tan_theta=np.tan(theta)
  err_ary.append(abs(tan_theta_near-tan_theta)/tan_theta)
  k_ary.append(k)

tan_theta_near=y_k/x_k
tan_theta_near_ary.append(tan_theta_near)
tan_theta=np.tan(theta)
err_ary.append(abs(tan_theta_near-tan_theta)/tan_theta)
plt.title('Cordicアルゴリズムによるtan関数の計算と試行回数の関係')
plt.xlabel('試行回数')
plt.ylabel('誤差')
plt.plot(k_ary,err_ary)
plt.savefig('Cordicアルゴリズムによるtan関数の計算と試行回数の関係.png')
plt.show()
```
これを実行すると以下のようなグラフが出力される。

![Cordicアルゴリズムによるtan関数の計算と試行回数の関係.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1686019/7290c247-b21f-61ad-32c6-4037e376a633.png)

なるほど、確かに試行回数が増えるほど誤差は小さくなっているようである。

# まとめ
今回は、関数電卓に使用されているアルゴリズムである、CORDICを用いてtan関数の近似曲線を描写した。また、誤差と試行回数の関係を調査することで、計算時間の許す限りではあるが、試行回数を上昇させていけば、近似精度は上昇するということが分かった。