FFT
算法原理
首先,为了能够进行FFT,需要了解DFT。 因为两者之间有本质上相同的东西。 在此之前,先列举离散傅立叶变换对(DFT )。
但是FFT之所以被称为快速傅立叶变换,是利用了以下性质()
先把这三个公式放在这里,接下来用。
这些特性使得可以将长DFT计算分解为几个短序列的DFT计算的组合,从而减少计算量。 为了便于理解,我们使用了按时间提取的快速傅立叶变换(DIT-FFT )方法。
首先,将一个序列x(n )分成两部分。
在以下情况下:
设n为2的整数次方,即N=2^M。
按奇偶校验对x(n )进行分组:
DFT也分为两组进行预算。
此时,在我们上面列举的三个性质中,约定性很有用。
让我们回顾一下:
该公式如下所示。
x1(k )和x2 ) k )都是长度为N/2的序列x1 ) k )和x2 ) k )的N/2点的离散傅立叶变换。
其中:
这样,一个n点的DFT被分解为两个N/2的DFT。 但是,x1(k )和x1(k )只有N/2分。 也就是说,公式(1-1)只是DFT的前半部分。 DFT的后半部分被要求能够利用其对称性求出后半部分。
式(1-1)和式(1-2)可以用蝶形信号流程图符号表示。 图:
以N=8为例,可以用下图表示。
那么,通过这样分解,每1个N/2点DFT,只需要(N^2)/4次的复数乘法,就可以明显地节约运算量。
那么,接下来类推,是继续按奇偶校验分解已经出现的x1(k )和x1(k ),还是与上边相同的方法? 在百度上也能找到的这张照片出山了。
对于这张图像,我想强调的是二次蝶形运算的时候。 之后不应该变成那样吗? 为什么会变成这样? 这个问题之前我烦恼了一会儿,但别忘了。 前者的展开是N/2,因为此时n是奇偶校验分离的,是从根据的可约束性得出的,在此不能混淆。
没有必要提到计算效率。 m级计算总共需要:
FFT
c语言实现
FFT算法c语言的实现,网络方法层出不穷,除了本人懒惰(不擅长看别人的程序)外,还有自给自足的衣食住原则,所以我自己也写了个人容易理解的程序。 而且,为了帮助读者理解,我特意尽量减少了库函数的使用。 一些基本函数是自己写的。 但是,作为FFT算法已经足够了。 目前,该程序只能处理2^N的数据。 理论上,如果2^N不够,可以进行在后面的数列中添加0的操作。 当然,因为简约语句也不能实现,但主要功能如下开源。
左右滑动查看代码
/*
功能:对input内的数据进行快速傅立叶变换并输出
*/
#包含
#包含
#define FFT_LENGTH 8
double input [ FFT _ length ]={ 1,1,1,1,1,1,1,1 };
定义结构完成1 {//多个结构
双现实; //实部
双图像; //虚部
(;
将input的实数结果保存在复数中
结构完成1 result _ dat [8];
/*
虚数乘法
*/
结构完成1 con _ complex (结构完成1 a,结构完成1 b ) {
struct complex1 temp;
temp.real=(a.real*b.real)-(a.image*b.image);
temp.image=(a.image*b.real)+(a.real*b.image);
return temp;
}
/*
简单的a的b次方
*/
int mypow(int a,int b){
int i,sum=a;
if(b==0)return 1;
for(i=1;i
sum*=a;
}
return sum;
}
/*
简单的求以2为底的正整数
*/
int log2(int n){
unsigned i=1;
int sum=1;
for(i;;i++){
sum*=2;
if(sum>=n)break;
}
return i;
}
/*
简单的交换数据的函数
*/
void swap(struct complex1 *a,struct complex1 *b){
struct complex1 temp;
temp=*a;
*a=*b;
*b=temp;
}
/*
dat为输入数据的数组
N为抽样次数 也代表周期 必须是2^N次方
*/
void fft(struct complex1 dat[],unsigned char N){
/*最终 dat_buf计算出 当前蝶形运算奇数项与W 乘积
dat_org存放上一个偶数项的值
*/
struct complex1 dat_buf,dat_org;
/* L为几级蝶形运算 也代表了2进制的位数
n为当前级蝶形的需要次数 n最初为N/2 每级蝶形运算后都要/2
i j为倒位时要用到的自增符号 同时 i也用到了L碟级数 j是计算当前碟级的计算次数
re_i i_copy均是倒位时用到的变量
k为当前碟级 cos(2*pi/N*k)的 k 也是e^(-j2*pi/N)*k 的 k
*/
unsigned char L,i,j,re_i=0,i_copy=0,k=0,fft_flag=1;
//经过观察,发现每级蝶形运算需要N/2次运算,共运算N/2*log2N 次
unsigned char fft_counter=0;
//在此要进行补2 N必须是2^n 在此略
//蝶形级数 (L级)
L=log2(N);
//计算每级蝶形计算的次数(这里只是一个初始值) 之后每次要/2
//n=N/2;
//对dat的顺序进行倒位
for(i=1;i
i_copy=i;
re_i=0;
for(j=L-1;j>0;j--){
//判断i的副本最低位的数字 并且移动到最高位 次高位 ..
//re_i为交换的数 每次它的数字是不能移动的 并且循环之后要清0
re_i|=((i_copy&0x01)<
i_copy>>=1;
}
swap(&dat,&dat[re_i]);
}
//进行fft计算
for(i=0;i
fft_flag=1;
fft_counter=0;
for(j=0;j
if(fft_counter==mypow(2,i)){ //控制隔几次,运算几次
fft_flag=0;
}else if(fft_counter==0){ //休止结束,继续运算
fft_flag=1;
}
//当不判断这个语句的时候 fft_flag保持 这样就可以持续运算了
if(fft_flag){
dat_buf.real=cos((2*PI*k)/(N/mypow(2,L-i-1)));
dat_buf.image=-sin((2*PI*k)/(N/mypow(2,L-i-1)));
dat_buf=con_complex(dat[j+mypow(2,i)],dat_buf);
//计算 当前蝶形运算奇数项与W 乘积
dat_org.real=dat[j].real;
dat_org.image=dat[j].image; //暂存
dat[j].real=dat_org.real+dat_buf.real;
dat[j].image=dat_org.image+dat_buf.image;
//实部加实部 虚部加虚部
dat[j+mypow(2,i)].real=dat_org.real-dat_buf.real;
dat[j+mypow(2,i)].image=dat_org.image-dat_buf.image;
//实部减实部 虚部减虚部
k++;
fft_counter++;
}else{
fft_counter--; //运算几次,就休止几次
k=0;
}
}
}
}
void main{
int i;
//先将输入信号转换成复数
for(i=0;i
result_dat.image=0;
//输入信号是二维的,暂时不存在复数
result_dat.real=input;
//result_dat.real=10;
//输入信号都为实数
}
fft(result_dat,FFT_LENGTH);
for(i=0;i
input=sqrt(result_dat.real*result_dat.real+result_dat.image*result_dat.image);
//取模
printf("%lfn",input);
}
while(1);
}
这个程序中input这个数组是输入信号,在这里只模拟抽样了8次,输出的数据也是input,如果想看其它序列的话,可以改变FFT_LENGTH 的值以及 input里的内容,程序输出的是实部和虚部的模,如果单纯想看实部或者虚部的幅度的话,请自行修改程序