2---理解正余弦、复数求模、反正切和乘除运算的CORDIC算法实现

CORDIC(Coordinate Rotation Digital Computer)算法是J.Volder在1956在航空控制系统设计中构思的,但其实相似的算法在更早的1624年就已经被Henry Briggs公布了。https://en.wikipedia.org/wiki/CORDIC
CORDIC的基本思想: 通过坐标旋转不断的迭代,去逼近一个设定的值,其核心是每次迭代旋转的角度是上一次的一半(类似于2分法),这样计算可通过加法和移位实现,适合数字电路实现。
CORDIC能实现的计算: 乘、除、平方根、正弦、余弦、反正切、复数乘法、坐标转换、指数运算等
CORDIC算法原理参考链接:
Xilinx CORDIC算法
三角函数计算,Cordic 算法入门

1. 圆周旋转,计算正余弦、复数求模、反正切

先看下边第一个图,圆周旋转其实就是对圆坐标系上的点进行旋转,从图中可以看到3个参数,坐标参数(x,y)和角度值 θ \theta θ,如果圆的半径 R = 1 R = 1 R=1,则有

x = cos ⁡ θ x = \cos\theta x=cosθ
y = sin ⁡ θ y = \sin\theta y=sinθ
R = x 2 + y 2 = cos ⁡ 2 θ + sin ⁡ 2 θ = 1 R = \sqrt{x^2 + y^2} = \sqrt{\cos^2\theta + \sin^2\theta} = 1 R=x2+y2 =cos2θ+sin2θ =1

那么如果我们知道角度为 θ \theta θ时的坐标参数 ( x , y ) (x, y) (x,y),我们就能知道 θ \theta θ的正余弦函数值

cos ⁡ θ = x \cos\theta = x cosθ=x
sin ⁡ θ = y \sin\theta =y sinθ=y

那么怎么获得这个坐标参数呢,请看下边第二个图,我们想要计算的角度值为 θ \theta θ,其对应的坐标值为 ( x 3 , y 3 ) (x_3, y_3) (x3,y3),我们通过旋转来不断的逼近这个坐标点 V 3 V_3 V3,每次旋转的角度都是固定的,并且越来越小。如果旋转超过了 V 3 V_3 V3,如 V 1 V_1 V1 V 2 V_2 V2的旋转,则下次旋转的方向与之前相反。就这样不断的在 V 3 V_3 V3附近摆动,因为旋转的角度不断减小,所以会越来越接近 V 3 V_3 V3。这个在目标值附近不断摆动并接近目标值的过程就是迭代。

在这里插入图片描述

迭代需要注意的细节有

  • 半径R的伸缩
  • 每次迭代要旋转的固定角度

这里会涉及到几个公式如下,公式详细可参考Xilinx CORDIC算法

x i + 1 = x i − d i ( y i 2 − i ) x_{i+1} = x_i - d_i(y_i2^{-i}) xi+1=xidi(yi2i)
y i + 1 = y i + d i ( x i 2 − i ) y_{i+1} = y_i + d_i(x_i2^{-i}) yi+1=yi+di(xi2i)
z i + 1 = z i − d i θ i z_{i+1} = z_i - d_i\theta_{i} zi+1=zidiθi

i i i是迭代次数, d i d_i di是判断算子,用来确定旋转的方向, z z z是角度累加器。

怎么计算正余弦

计算正余弦就是要计算坐标值,此时我们已经知道了角度值 z z z,计算正余弦的过程如下

  • 设定初始的坐标为(1, 0)
  • 旋转 z z z个角度,即使 z z z趋于0
  • 最终得到的 x x x y y y的值就是我们要的正余弦函数值

可通过下面这段MATLAB代码来理解

for i = 0:1:itime					% itime为迭代次数
   if z0 > 0                        % z0生成判断算子,确定旋转方向
       x = x0 - y0 / 2^(i);
       y = y0 + x0 / 2^(i);
       z = z0 - atan(1/2^i);
   else
       x = x0 + y0 / 2^(i);
       y = y0 - x0 / 2^(i);
       z = z0 + atan(1/2^i);
   end
   x0 = x;
   y0 = y;
   z0 = z;
end
怎么计算复数求模和反正切

先看我们要求的是什么

∣ a + b i ∣ = ( a ) 2 + ( b ) 2 = ( ( a ) 2 + ( b ) 2 ) 2 + ( 0 ) 2 |a + bi| = \sqrt{(a)^2 + (b)^2} = \sqrt{(\sqrt{(a)^2 + (b)^2})^2 + (0)^2} a+bi=(a)2+(b)2 =((a)2+(b)2 )2+(0)2
t a n − 1 ( a ) = t a n − 1 ( a 1 ) = t a n − 1 ( y x ) = θ tan^{-1}(a) = tan^{-1}(\frac{a}{1}) = tan^{-1}(\frac{y}{x}) = \theta tan1(a)=tan1(1a)=tan1(xy)=θ
其中 y = a y = a y=a x = 1 x = 1 x=1

计算正余弦函数的过程可以看成是第二图中 V 0 V_0 V0 V 3 V_3 V3的旋转,而计算复数求模和反正切就可以看成是** V 3 V_3 V3 V 0 V_0 V0的旋转**,即初始我们是知道 ( x 3 , y 3 ) (x_3, y_3) (x3,y3)的,然后我们要求 ( x 0 , 0 ) (x_0, 0) (x0,0)的值,因为这是圆周旋转,所以这里有一个隐含条件

( x 3 ) 2 + ( y 3 ) 2 = ( x 0 ) 2 + ( 0 ) 2 = ∣ x 0 ∣ \sqrt{(x_3)^2 + (y_3)^2} = \sqrt{(x_0)^2 + (0)^2} = |x_0| (x3)2+(y3)2 =(x0)2+(0)2 =x0

那么复数求模 a + b i a + bi a+bi的过程如下

  • 设定初始值 x 0 = a , y 0 = b , z 0 = 0 x_0 = a, y_0 = b, z_0 = 0 x0=a,y0=b,z0=0。如果只求模, z z z其实无所谓,因为求解过程不需要用到 z z z
  • 使 y y y趋于0
  • 迭代完成后的结果 x x x就是复数的模

求反正切的过程与复数求模基本一致,因为都是 V 3 V_3 V3 V 0 V_0 V0的旋转,在这个迭代过程中, z z z一直在积累角度,迭代完成后** z z z的值就是旋转的角度 θ \theta θ**。即有

θ = t a n − 1 ( b a ) \theta = tan^{-1}(\frac{b}{a}) θ=tan1(ab)

如果我们将初始值 y y y设为 x = a = 1 x = a = 1 x=a=1,那么 b b b的反正切函数值为

t a n − 1 ( b ) = z = θ tan^{-1}(b) = z = \theta tan1(b)=z=θ

计算复数求模和反正切函数值的MATLAB代码

for i = 0:1:itime					% itime为迭代次数,迭代的越多,越精确
   if y0 < 0                        % y0生成判断算子,确定旋转方向
       x = x0 - y0 / 2^(i);
       y = y0 + x0 / 2^(i);
       z = z0 - atan(1/2^i);
   else
       x = x0 + y0 / 2^(i);
       y = y0 - x0 / 2^(i);
       z = z0 + atan(1/2^i);
   end
   x0 = x;
   y0 = y;
   z0 = z;
end

2. 线性旋转,乘除运算的CORDIC实现

Xilinx CORDIC算法在介绍线性旋转的时候有说到一个线性坐标系,有的讲CORDIC的书里也有提到线性坐标系旋转,但是也有的书里是没有提到的,而且在百度和谷歌里都没搜到“线性坐标系”这个名词,所以也不知道是不是对的。不过从下面这个图来理解线性旋转好像确实有点不好理解,所以就先不管了。

  • 线性旋转的迭代过程可表示为

x i + 1 = x i x_{i+1} = x_i xi+1=xi
y i + 1 = y i + d i ( x i 2 − i ) y_{i+1} = y_i + d_i(x_i2^{-i}) yi+1=yi+di(xi2i)
z i + 1 = z i − d i ( 2 − i ) z_{i+1} = z_i - d_i(2^{-i}) zi+1=zidi(2i)

  • 选择 d i = s i g n ( z i ) d_i = sign(z_i) di=sign(zi)使得 z i → 0 z_i → 0 zi0 n n n次迭代后得到

x n = x 0 x_n = x_0 xn=x0
y n = y 0 + x 0 z 0 y_n = y_0 + x_0z_0 yn=y0+x0z0
z n = 0 z_n = 0 zn=0

如果初始 y 0 y_0 y0设为0,那么迭代完成后就可以得到 x x x z z z乘积

  • 选择 d i = − s i g n ( x i y i ) d_i = -sign(x_iy_i) di=sign(xiyi)使得 y i → 0 y_i → 0 yi0 n n n次迭代后得到

x n = x 0 x_n = x_0 xn=x0
y n = 0 y_n =0 yn=0
z n = z 0 + y 0 x 0 z_n = z_0 + \frac{y_0}{x_0} zn=z0+x0y0

如果初始 z 0 z_0 z0设为0,那么迭代完成后就可以得到 y y y x x x

其实上面的过程隐含了一个等式

y = x z y = xz y=xz

我们都知道乘法可以通过二进制的移位相加来实现,如6乘以5

5 = 2 2 + 2 0 5 = 2^2 + 2^0 5=22+20
则 6乘5 = 6左移2位 + 6
6*5 = 0b(11000) + 0b(00110) = 0b(11110) = 30

所以求乘积的过程就是 x 0 x_0 x0不断移位相加的过程,这个过程会近似乘完所有 z 0 z_0 z0的分解项(分解成如 5 = 2 2 + 2 0 5 = 2^2 + 2^0 5=22+20的形式),每次乘积后累加,最后得到 y n y^n yn
而求商的过程则与求乘积的过程恰好相反

乘法迭代的MATLAB代码

for i = -indx:15			% 注意indx				
   if z0 > 0                       
       x = x0;
       y = y0 + x0 / 2^(i);
       z = z0 - (1/2^i);
   else
       x = x0;
       y = y0 - x0 / 2^(i);
       z = z0 + (1/2^i);
   end
   x0 = x;
   y0 = y;
   z0 = z;
end

注意 2 i n d x 2^{indx} 2indx必须大于 z 0 z_0 z0 1 2 \frac{1}{2} 21,否则迭代不会收敛,即 z z z不会趋于0

除法迭代的MATLAB代码

for i = -indx:15					
   if y0 < 0                       
       x = x0;
       y = y0 + x0 / 2^(i);
       z = z0 - (1/2^i);
   else
       x = x0;
       y = y0 - x0 / 2^(i);
       z = z0 + (1/2^i);
   end
   x0 = x;
   y0 = y;
   z0 = z;
fprintf('x0 = %d, y0 = %d, z0 = %d\n', x0, y0, z0)
end

注意 x 0 ∗ 2 i n d x x_0*2^{indx} x02indx必须大于 y 0 y_0 y0 1 2 \frac{1}{2} 21,否则迭代不会收敛,即 y y y不会趋于0

为什么不会收敛呢
因为”一尺之棰,日取其半,万世不竭”…………
拿乘法来说,假设 z 0 = 100 z_0 = 100 z0=100 i n d x = 5 indx = 5 indx=5,那迭代 n n n次之后有

z n = z 0 − ( 2 5 + 2 4 + . . . + 2 5 − n + 1 ) = 100 − 2 6 ( 1 − 2 − n ) = 36 + 2 6 − n z_n = z_0 - (2^5 + 2^4 + ... + 2^{5-n + 1}) = 100 - 2^6(1 - 2^{-n}) = 36 + 2^{6-n} zn=z0(25+24+...+25n+1)=10026(12n)=36+26n

即不管迭代多少次, z n z_n zn是永远大于36的,所以不能收敛。

  • 7
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是使用Python实现k-means算法,其中包括欧氏距离、曼哈顿距离和夹角余弦距离的实现: ```python import numpy as np import math # 欧氏距离 def euclidean_distance(x1, x2): return np.sqrt(np.sum((x1 - x2)**2)) # 曼哈顿距离 def manhattan_distance(x1, x2): return np.sum(np.abs(x1 - x2)) # 夹角余弦距离 def cosine_distance(x1, x2): dot_product = np.dot(x1, x2) norm_x1 = np.linalg.norm(x1) norm_x2 = np.linalg.norm(x2) return 1 - dot_product / (norm_x1 * norm_x2) class KMeans: def __init__(self, k=3, max_iters=100, distance="euclidean"): self.k = k self.max_iters = max_iters self.distance = distance def initialize_centroids(self, X): n_samples, n_features = X.shape centroids = np.zeros((self.k, n_features)) for i in range(self.k): centroid = X[np.random.choice(range(n_samples))] centroids[i] = centroid return centroids def closest_centroid(self, sample, centroids): distances = np.zeros(self.k) for i, centroid in enumerate(centroids): if self.distance == "euclidean": distances[i] = euclidean_distance(sample, centroid) elif self.distance == "manhattan": distances[i] = manhattan_distance(sample, centroid) else: distances[i] = cosine_distance(sample, centroid) closest_index = np.argmin(distances) return closest_index def create_clusters(self, X, centroids): clusters = [[] for _ in range(self.k)] for sample_i, sample in enumerate(X): centroid_i = self.closest_centroid(sample, centroids) clusters[centroid_i].append(sample_i) return clusters def calculate_centroids(self, X, clusters): n_features = X.shape[1] centroids = np.zeros((self.k, n_features)) for i, cluster in enumerate(clusters): centroid = np.mean(X[cluster], axis=0) centroids[i] = centroid return centroids def get_cluster_labels(self, clusters, X): y_pred = np.zeros(X.shape[0]) for cluster_i, cluster in enumerate(clusters): for sample_i in cluster: y_pred[sample_i] = cluster_i return y_pred def predict(self, X): centroids = self.initialize_centroids(X) for _ in range(self.max_iters): clusters = self.create_clusters(X, centroids) prev_centroids = centroids centroids = self.calculate_centroids(X, clusters) if np.all(centroids == prev_centroids): break return self.get_cluster_labels(clusters, X) ``` 使用示例: ```python from sklearn.datasets import make_blobs import matplotlib.pyplot as plt X, y = make_blobs(centers=3, n_samples=500, random_state=1) kmeans = KMeans(k=3, max_iters=100, distance="euclidean") y_pred = kmeans.predict(X) plt.scatter(X[:, 0], X[:, 1], c=y_pred) plt.title("K-Means Clustering") plt.show() ``` 其中,distance参数可以设置为"euclidean"、"manhattan"或者"cosine",表示使用欧氏距离、曼哈顿距离或夹角余弦距离。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值