By Long Luo
任何一款科学计算器上都可以计算三角函数,三角函数应用于生活工作中的方方面面,但计算机是如何计算三角函数值的呢?
三角函数本质上是直角三角形的3条边的比例关系,计算并没有很直观,那么计算机是如何计算三角函数值的呢?
在微积分中我们学习过 泰勒公式 ,我们知道可以使用泰勒展开式来对某个值求得其近似值,例如:
sin∠18∘=45−1≈0.3090169943
利用泰勒公式,取前 4 项:
sinx≈x−3!x3+5!x5−7!x7
代入可得:
sin∠18∘=sin10π=10π−61(10π)3+1201(10π)5−50401(10π)7≈0.30903399538
可见取前 4 项时精度已经在 10−5 之下,对于很多场合精度已经足够高了。
在没有了解 CORDIC(Coordinate Rotation Digital Computer) Algorithm 算法之前,我一直以为计算器是利用泰勒公式去求解,最近才了解到原来在计算机中,CORDIC 算法远比泰勒公式高效。
下面我们就来了解下泰勒公式的不足之处和 CORDIC 算法是怎么做的。
泰勒公式的缺点
上一节我们使用泰勒公式去计算三角函数值时,需要做很多次乘法运算,而计算器中乘法运算是很昂贵的,其缺点如下所示:
- 展开过小则会导致展开精度不够,展开过大则会导致计算量过大;
- 幂运算需要使用乘法器,存在很多重复计算;
- 需要很多变量来存储中间值。
在之前的文章 矩阵乘法的 Strassen 算法 ,就是通过减少乘法,多做加法,从而大大降低了运算量,那么我们可以用相同的思想来优化运算吗?
当然可以,让我们请出今天的主角:CORDIC 算法。
解析 CORDIC 算法
我们知道单位圆上一点 P ,其坐标为:(cosθ,sinθ) ,如下图所示:

如果我们接收一个旋转向量 Min 逆时针旋转 θ ,将点 P(xin,yin) 旋转到 P′(xR,yR) , 如下图所示:

很容易得到如下公式:
xR=xincos(θ)−yinsin(θ)
yR=xinsin(θ)+yincos(θ)
实际上由 复数运算 ,我们知道复数乘法就是幅角相加,模长相乘。我们可以将上式写成下列矩阵运算形式:
[xRyR]=[cos(θ)sin(θ)−sin(θ)cos(θ)][xinyin]
但上式运算时,只是对向量 vin=[xinyin] 进行了线性变换,乘以一个旋转向量 Min ,得到了旋转后的结果:向量 vR=[xRyR] 。
但是上式仍然需要 4 次乘法和 2 次加减法操作, 复杂度没有任何降低,那怎么办呢?
当当…当!
通过上述分析,我们已经知道可以使用有限次旋转操作来避免复杂的乘法操作,我们修改矩阵运算公式,提取 cos(θ) ,则公式可以修改为:
[xRyR]=cos(θ)[1tan(θ)−tan(θ)1][xinyin]
如果我们选择合适的角度值 θi,使得
tan(θi)=2−i,i=0,1,…,n
这样和 tan(θi) 乘法操作就变成了移位操作,我们知道计算机中移位操作是非常快的,就可以大大加快计算速度了。
但这里仍然有 3 个问题需要解决:
- 对于任意角度 ,可以通过满足条件的角度累加来得到在数学上相同的结果吗?
- 每次旋转得到的结果仍然需要乘以 cos(θ) ,这部分的计算成本如何?如何计算?
- 因为每次旋转角度 θ=arctan(2−i) ,朝着目标角度进行旋转时,可能会出现没有超过目标角度的情况,也会存在超过目标角度的情况,这种情况如何解决呢?

对于第一个问题,答案是否定的。可以从数学上证明只有 ∠45∘ 的倍数角才可以得到完全一致的结果。但是在工程应用中,我们只需要满足一定精度即可,可以增加迭代次数无限逼近原始角度,如下所示提高 n 值以无限逼近原始角度。
θd=i=0∑nθi,∀tan(θi)=2−i
对于第二个问题,我们先来个例子,以 57.535∘ 为例来看看求解过程:
57.535∘=45∘+26.565∘−14.03∘
那么第一次旋转:
[x0y0]=cos(45∘)[11−11][xinyin]
第二次旋转:
[x1y1]=cos(26.565∘)[12−1−2−11][x0y0]
第三次旋转:
[x2y2]=cos(−14.03∘)[1−2−22−21][x1y1]
综合可得:
[x2y2]=cos(45∘)cos(26.565∘)cos(−14.03∘)[11−11][12−1−2−11][1−2−22−21][xinyin]
因为 tan(θi)=2−i ,由三角公式可以计算出:
cos(θi)=1+tan2(θi)1=1+2−2i1
令 Ki=cos(θi) ,则当进行 n 次迭代之后:
K(n)=i=0∏n−1Ki=i=0∏n−11+2−2i1
当 θi 越来越小时, cosθ 也越来越逼近 1 ,当迭代次数 n→∞ , K(n) 极限存在,求解可得:
K=n→∞limK(n)≈0.6072529350088812561694
由 K 我们实际上可以得到最终的向量 vR 的模长极限为:
A=K1=n→∞limi=0∏n−11+2−2i≈1.64676025812107
实际上当迭代次数为 6 时,可以计算出缩放比例 K ,就已经精确到 0.6072 了,如下所示:
K≈cos(45∘)cos(26.565∘)×⋯×cos(0.895∘)=0.6072
实际上,任意角度只要迭代次数超过 6 ,我们可以直接使用 K=0.6072 这个值。
对于第三个问题,稍微有点复杂,我们在下一节继续讲解!
角度累加
上一节遗留的问题是迭代旋转角度时,旋转角度不一定会落在目标角度内,我们需要引入一个角度误差,用来衡量旋转角度和目标角之间距离,如下所示:
θerror=θd−i=0∑nθi
当 θerror>0 时,我们应该逆时针旋转,而 θerror<0 ,则顺时针旋转。根据精度需要,当 ∣θerror∣≤ϵ 即可退出迭代。
同时我们修改之前的公式,引入 σi∈{+1,−1} ,于是可以得到最终公式:
x[i+1]=x[i]−σi2−iy[i]
y[i+1]=y[i]+σi2−ix[i]
z[i+1]=z[i]−σitan−1(2−i)
举个例子
上面讲了这么多,来个实例吧,练习巩固下知识,看看自己是否真的懂了?
计算 sin70∘ 和 cos70∘ 的值。
从 xin=1,yin=0 开始,迭代 6 次结果如下:
第 i 次迭代 |
σi |
x[i] |
y[i] |
z[i] |
- |
- |
1 |
0 |
70∘ |
0 |
1 |
1 |
1 |
25∘ |
1 |
1 |
0.5 |
1.5 |
−1.5651∘ |
2 |
−1 |
0.875 |
1.375 |
12.4711∘ |
3 |
1 |
0.7031 |
1.4844 |
5.3461∘ |
4 |
1 |
0.6103 |
1.5283 |
1.7698∘ |
5 |
1 |
0.5625 |
1.5474 |
−0.0201∘ |
6 |
−1 |
0.5867 |
1.5386 |
0.8751∘ |
迭代到第 6 次时,角度误差已经小于 1∘ 了, 通过表格可知:
xR=0.6072×0.5867=0.3562
yR=0.6072×1.5386=0.9342
通过计算器可知,cos(70∘)=0.34202 ,sin(70∘)=0.93969 ,误差已经在 1001 之下了,实际应用中我们会迭代 16 次,误差会非常小。
在线 CORDIC 算法Demo
通过上面分析,我们已经知道了 CORDIC 算法的原理,下面就开始编程吧!
用 JavaScript 写了一个在线互动版本,传送门 → :
- 可以调整不同迭代次数,和系统库函数 Math.cos ,Math.sin 进行比较:

- 可以查看每次迭代的结果,掌握 Cordic 算法迭代原理:

CORDIC 算法的优点
CORDIC 算法相比其他算法的优点,体现在以下几个方面:
- 简化运算:CORDIC 算法主要使用位移、加法和减法等简单的运算,避免了复杂的乘法操作,从而提高了计算速度;
- 并行计算:CORDIC 算法的迭代操作是相互独立的,可以进行并行计算,利用现代计算机的多核优势,进一步提升计算效率;
- 硬件优化:CORDIC 算法适用于硬件实现,可以通过专用硬件电路(如FPGA)进行加速,极大地提高计算速度;
- 低存储需求:CORDIC 算法只需存储一小组预先计算好的旋转角度,节省了存储空间;
- 迭代控制:通过控制迭代次数,可以平衡计算精度和计算速度,根据需求进行调整。
总结
CORDIC 算法是一种高效计算三角函数值的方法。相比传统的泰勒展开式,它具有简单高效、低存储需求和迭代控制等优势。在需要计算三角函数值的应用中,CORDIC 算法更快速、更节省资源。
参考文献