zoukankan      html  css  js  c++  java
  • 利用CORDIC算法计算三角函数

    这里主要先介绍如何利用CORDIC算法计算固定角度(phi)(cos(phi))(sin(phi))值。

    一般利用MATLAB计算三角函数时,用(cos)举例,只需要输入相应的(cos(phi))便自动计算出来了。但是如果是硬件处理或者没有那么方便的函数时,该如何计算(cos(phi))的值呢?

    有一种最傻瓜的方式是用rom存储(0^o)(90^o)所有的余弦值,然后用查表的方法计算,但随着精度要求的提升,需要存储的值会越来越多,这是不合适的。那么有没有一种用较少资源且能较快计算出高精度三角函数值的方法呢?有!这就是CORDIC算法可以做的事,算法不难,有一点三角函数和移位运算的基础就能看懂了,核心思想是数形结合计算三角函数,计算过程把乘法运算变成移位运算。

    为什么说是数形结合呢,让我们看下如何通过旋转求(cosphi)(sinphi)。如下图可以看出,如果让初始坐标((x_1, y_1))(x)轴一个特定的位置,最后使其旋转(phi^o)到坐标((x_n,y_n))处,且满足(sqrt{x_n^2 + y_n^2} = 1),那么(cosphi)不就等于(x_n)(sinphi)不就等于(y_n)了吗,这是后话。

    下面首先引入一个在圆上的坐标旋转公式(不一定单位圆):

    [egin{cases} x_2 = x_1cos heta - y_1sin heta \ y_2 = x_1sin heta + y_1cos heta ag{1}\ end{cases} ]

    注:((1))的推导

    [egin{cases} x_1 = Acosalpha\ y_1 = Asinalpha ag{1.1} end{cases} ]

    其中(A)为旋转圆半径,(alpha)((x_1, y_1))的角度值,图中红线与x轴夹角。则坐标((x_2, y_2))可以写为:

    [egin{cases} x_2 = Acos(alpha + heta) = A(cosalpha cos heta - sinalpha sin heta) = x_1cos heta - y_1sin heta \ y_2 = Asin(alpha + heta) = A(sinalpha cos heta + cosalpha sin heta) = x_1sin heta + y_1cos heta ag{1.2} end{cases} ]

    推导完毕。

    我们继续看式((1))((1))也可以写成:

    [egin{cases} x_2 = cos heta(x_1 - y_1tan heta) \ y_2 = cos heta(y_1 + x_1tan heta) ag{2} \ end{cases} ]

    由于对((x_2, y_2))相当于同乘了一个常数(cos heta),不影响旋转角度,只影响旋转后坐标到原点的距离。我们先不看它,得到:

    [egin{cases} x_2 = x_1 - y_1tan heta \ y_2 = y_1 + x_1tan heta ag{3} \ end{cases} ]


    一:乘法变移位

    之前说的我们要把乘法运算变成移位运算,所以我们找到(tan heta)(2^{-i})之间的对应关系,注意由于是变成移位操作,所以对应旋转的角度也是几个固定的值,但是通过旋转这几个固定的角度,旋转(i)次,最终也一定能转到我们需要的角度(phi)上((-99.7^olephi le 99.7^o))。

    于是把((3))再改写为:

    [egin{cases} x(i+1) = x(i) - d(i)y(i)2^{-i} \ y(i+1) = y(i) + d(i)x(i)2^{-i} ag{4} end{cases} ]

    这样,旋转( heta^o)就变成了移位、相加的操作。注意(d(i) = ±1)表示旋转的逆、顺时针。

    比如要旋转(phi = 66^o),可以先转(+45^o)(45^o < 66^o),再转(+26.565^o)(45^o+26.565^o ge 66^o),再转(-14.036^o cdots),最终会逼近(66^o)。而整个运算仅仅进行了(2^{-0}、2^{-1}、2^{-2} cdots)移位操作和加法操作。


    二:cos累乘项

    现在考虑把(cos heta)加回去,回到((2)),且考虑旋转方向(d_i)和旋转角度( heta_i),得到:

    [egin{cases} x_2 = cos heta_1(x_1 - d_1y_1tan heta_1) \ y_2 = cos heta_1(y_1 + d_1x_1tan heta_1) ag{5.1} \ end{cases} ]

    进行下一次迭代(旋转),得到:

    [egin{align} x_3 &= cos heta_2(x_2 - d_2y_2tan heta_2) otag\ &= cos heta_2(cos heta_1(x_1 - d_1y_1tan heta_1)- d_2cos heta_1(y_1 + d_1x_1tan heta_1)tan heta_2) otag\ &= cos heta_1cos heta_2(x_1 - d_1y_1tan heta_1 - d_2y_1tan heta_2 - d_2d_1x_1tan heta_1tan heta_2) otag end{align} otag\ ]

    [egin{align} y_3 &= cos heta_2(y_2 + d_2x_2tan heta_2) otag\ &= cos heta_2(cos heta_1(y_1 + d_1x_1tan heta_1) + d_2cos heta_1(x_1 - d_1y_1tan heta_1)tan heta_2) ag{5.2}\ &= cos heta_1cos heta_2(y_1 + d_1x_1tan heta_1 + d_2x_1tan heta_2 - d_2d_1y_1tan heta_1tan heta_2) otag end{align} ]

    可以看到每次旋转都可以提取出(cos heta_i)(tan heta_i)已经用移位替代了。接下来只用计算(prod_{i=1}^{N}cos heta_i)就行了,且(prod_{i=1}^{N}cos heta_i)只跟迭代次数有关,大概收敛到0.607。确定了迭代次数后,可以预先把(prod_{i=1}^{N}cos heta_i)算出来。


    三:累计旋转角度与旋转方向

    现在考虑最后一个问题,如何确定每次迭代的旋转方向(d_i)呢?其实定义一个累计旋转角度(z_i)

    [z_{i + 1} = z_i -d_i heta_i = z_i -d_i2^{-i} ag{6} ]

    (z_1)等于目标角度值,然后每次迭代作个判断就好,如果(z_i > 0),说明当前旋转还没转到目标角度,(d_{i+1} = 1);如果(z_i < 0),说明当前旋转超过了目标角度,(d_{i+1} = -1)

    当我们最终转到了目标角度(phi)时,比如(phi = 66^o),可以此时(z_i)已经很小趋近于零了。

    另外,在作比较判断时,单次旋转角度( heta_i)则还是需要通过查一次(arctan(2^{-i}))表得到,但这个表比起文章开头说的傻瓜式查表要小太多了。


    四:计算cos和sin值

    由开始所说的数形结合可以推出,假设只旋转一次把((x_1, y_1))转到((x_n, y_n)),有:

    [egin{cases} x_n = x_1cosphi - y_1sinphi \ y_n = x_1sinphi + y_1cosphi ag{7}\ end{cases} ]

    按第一节所说的,把一次就旋转到位用很多次旋转替代,每次旋转后坐标到原点的距离比上一次都大了(frac{1}{cos heta_i})倍,所以如果我们把((x_1, y_1))设为((prod_{i=1}^n cos heta_i, 0)),那么最后((x_n, y_n))到原点的距离就为1,(cosphi)就等于(x_n)(sinphi)就等于(y_n)

    这样就只用通过((4)(6))的移位、相加、一次查表,再迭代十多次,就能计算(cosphi)(sinphi)值啦!

    其实用CORDIC算法还能计算arctan、sinh、cosh等值,以后学习了再来补充进阶版。


    五:完整MATLAB代码

    clc, clear, close all;
    
    %% 初始计算cos累乘值
    N = 16; % 设置迭代次数16次
    Nprod(1) = 1;
    for i = 1 : N
        Nprod(i + 1) = Nprod(i) * cos(atan(2^(-(i - 1))));
    end
    
    %% Cordic算法计算cos、sin值
    x(1) = Nprod(N); % 横坐标初始值赋为cos累乘值
    y(1) = 0; % 纵坐标初始值赋为0
    z(1) = 66 / 180 * pi; %目标旋转角度值,66°,注意转化成弧度值
    d(1) = 1; %旋转方向,初始肯定为1
    
    
    for i = 1 : N
        x(i + 1) = x(i) - d(i) * y(i) * 2^(-(i - 1)); %移位运算,公式(4)
        y(i + 1) = y(i) + d(i) * x(i) * 2^(-(i - 1)); %移位运算,公式(4)
        z(i + 1) = z(i) - d(i) * atan(2^(-(i - 1))); %计算累计旋转角度,查一次表,公式(6)
        if z(i + 1) >= 0 % 判断下一次的旋转方向
            d(i + 1) = 1;
        else
            d(i + 1) = -1;
        end
    end
    
    COS = x(N) % 输出cos66的值
    SIN = y(N) % 输出sin66的值
    

    六:参考文章

    [1]:https://mp.weixin.qq.com/s/c4oro0bOhdDUmBt0yyLpTA

    [2]:https://blog.csdn.net/qq_39210023/article/details/77456031

  • 相关阅读:
    【Oracle】ORA-00257:archiver error. Connect internal only, until freed 错误的处理方法
    【SQL】区分新来顾客和再访顾客
    【Xcelsius】在PPT中嵌入水晶易表Xcelsius2008仪表盘
    【SAP BO】【WEBI】【转】Webi实现动态选择度量
    【转】Xcelsius2008 水晶易表问题 部分汇总
    如何进行有效的需求调研
    实现数据仓库的七个步骤、七个禁忌、七种思路
    UTC日期转时间戳
    WIN10下命令行禁用编辑模式
    让socket端口不被子进程继承
  • 原文地址:https://www.cnblogs.com/gjblog/p/14466847.html
Copyright © 2011-2022 走看看