

C#实现FFT(递归法) - Mokera
source link: https://www.cnblogs.com/yang-ding/p/16466018.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

C#实现FFT(递归法)
1. C#实现复数类
我们在进行信号分析的时候,难免会使用到复数。但是遗憾的是,C#没有自带的复数类,以下提供了一种复数类的构建方法。
复数相比于实数,可以理解为一个二维数,构建复数类,我们需要实现以下这些内容:
- 复数实部与虚部的属性
- 复数与复数的加减乘除运算
- 复数与实数的加减乘除运算
- 复数取相位角
- 欧拉公式(即eix+yeix+y)
C#实现的代码如下:
public class Complex
{
double real;
double imag;
public Complex(double x, double y) //构造函数
{
this.real = x;
this.imag = y;
}
//通过属性实现对复数实部与虚部的单独查看和设置
public double Real
{
set { this.real = value; }
get { return this.real; }
}
public double Imag
{
set { this.imag = value; }
get { return this.imag; }
}
//重载加法
public static Complex operator +(Complex c1, Complex c2)
{
return new Complex(c1.real + c2.real, c1.imag + c2.imag);
}
public static Complex operator +(double c1, Complex c2)
{
return new Complex(c1 + c2.real, c2.imag);
}
public static Complex operator +(Complex c1, double c2)
{
return new Complex(c1.Real + c2, c1.imag);
}
//重载减法
public static Complex operator -(Complex c1, Complex c2)
{
return new Complex(c1.real - c2.real, c1.imag - c2.imag);
}
public static Complex operator -(double c1, Complex c2)
{
return new Complex(c1 - c2.real, -c2.imag);
}
public static Complex operator -(Complex c1, double c2)
{
return new Complex(c1.real - c2, c1.imag);
}
//重载乘法
public static Complex operator *(Complex c1, Complex c2)
{
double cr = c1.real * c2.real - c1.imag * c2.imag;
double ci = c1.imag * c2.real + c2.imag * c1.real;
return new Complex(Math.Round(cr, 4), Math.Round(ci, 4));
}
public static Complex operator *(double c1, Complex c2)
{
double cr = c1 * c2.real;
double ci = c1 * c2.imag;
return new Complex(Math.Round(cr, 4), Math.Round(ci, 4));
}
public static Complex operator *(Complex c1, double c2)
{
double cr = c1.Real * c2;
double ci = c1.Imag * c2;
return new Complex(Math.Round(cr, 4), Math.Round(ci, 4));
}
//重载除法
public static Complex operator /(Complex c1, Complex c2)
{
if (c2.real == 0 && c2.imag == 0)
{
return new Complex(double.NaN, double.NaN);
}
else
{
double cr = (c1.imag * c2.imag + c2.real * c1.real) / (c2.imag * c2.imag + c2.real * c2.real);
double ci = (c1.imag * c2.real - c2.imag * c1.real) / (c2.imag * c2.imag + c2.real * c2.real);
return new Complex(Math.Round(cr, 4), Math.Round(ci, 4)); //保留四位小数后输出
}
}
public static Complex operator /(double c1, Complex c2)
{
if (c2.real == 0 && c2.imag == 0)
{
return new Complex(double.NaN, double.NaN);
}
else
{
double cr = c1 * c2.Real / (c2.imag * c2.imag + c2.real * c2.real);
double ci = -c1 * c2.imag / (c2.imag * c2.imag + c2.real * c2.real);
return new Complex(Math.Round(cr, 4), Math.Round(ci, 4)); //保留四位小数后输出
}
}
public static Complex operator /(Complex c1, double c2)
{
if (c2 == 0)
{
return new Complex(double.NaN, double.NaN);
}
else
{
double cr = c1.Real / c2;
double ci = c1.imag / c2;
return new Complex(Math.Round(cr, 4), Math.Round(ci, 4)); //保留四位小数后输出
}
}
//创建一个取模的方法
public static double Abs(Complex c)
{
return Math.Sqrt(c.imag * c.imag + c.real * c.real);
}
//创建一个取相位角的方法
public static double Angle(Complex c)
{
return Math.Round(Math.Atan2(c.real, c.imag), 6);//保留6位小数输出
}
//重载字符串转换方法,便于显示复数
public override string ToString()
{
if (imag >= 0)
return string.Format("{0}+i{1}", real, imag);
else
return string.Format("{0}-i{1}", real, -imag);
}
//欧拉公式
public static Complex Exp(Complex c)
{
double amplitude = Math.Exp(c.real);
double cr = amplitude * Math.Cos(c.imag);
double ci = amplitude * Math.Sin(c.imag);
return new Complex(Math.Round(cr, 4), Math.Round(ci, 4));//保留四位小数输出
}
}
2. 递归法实现FFT
以下的递归法是基于奇偶分解实现的。
奇偶分解的原理推导如下:
x(2r)x(2r)和x(2r+1)x(2r+1)都是长度为N/2−1N/2−1的数据序列,不妨令
则原来的DFT就变成了:
于是,将原来的N点傅里叶变换变成了两个N/2点傅里叶变换的线性组合。
但是,N/2点傅里叶变换只能确定N/2个频域数据,另外N/2个数据怎么确定呢?
因为X1(k)X1(k)和X2(k)X2(k)周期都是N/2N/2,所以有
从而得到:
综上,我们就可以得到递归法实现FFT的流程:
-
对于每组数据,按奇偶分解成两组数据
-
两组数据分别进行傅里叶变换,得到X1(k)X1(k)和X2(k)X2(k)
-
总体数据的X(k)X(k)由下式确定:
X(k)==X1(k)+WkNX2(k)X(k+N/2)=X1(k)−WknX2(k)0≤k≤N/2−1X(k)==X1(k)+WNkX2(k)X(k+N/2)=X1(k)−WnkX2(k)0≤k≤N/2−1 -
对上述过程进行递归
具体代码实现如下:
public Complex[] FFTre(Complex[] c)
{
int n = c.Length;
Complex[] cout = new Complex[n];
if (n == 1)
{
cout[0] = c[0];
return cout;
}
else
{
double n_2_f = n / 2;
int n_2 = (int)Math.Floor(n_2_f);
Complex[] c1 = new Complex[n / 2];
Complex[] c2 = new Complex[n / 2];
for (int i = 0; i < n_2; i++)
{
c1[i] = c[2 * i];
c2[i] = c[2 * i + 1];
}
Complex[] c1out = FFTre(c1);
Complex[] c2out = FFTre(c2);
Complex[] c3 = new Complex[n / 2];
for (int i = 0; i < n / 2; i++)
{
c3[i] = new Complex(0, -2 * Math.PI * i / n);
}
for (int i = 0; i < n / 2; i++)
{
c2out[i] = c2out[i] * Complex.Exp(c3[i]);
}
for (int i = 0; i < n / 2; i++)
{
cout[i] = c1out[i] + c2out[i];
cout[i + n / 2] = c1out[i] - c2out[i];
}
return cout;
}
}
3. 补充:窗函数
顺便提供几个常用的窗函数:
- Rectangle
- Bartlett
- Hamming
- Hanning
- Blackman
public class WDSLib
{
//以下窗函数均为periodic
public double[] Rectangle(int len)
{
double[] win = new double[len];
for (int i = 0; i < len; i++)
{
win[i] = 1;
}
return win;
}
public double[] Bartlett(int len)
{
double length = (double)len - 1;
double[] win = new double[len];
for (int i = 0; i < len; i++)
{
if (i < len / 2) { win[i] = 2 * i / length; }
else { win[i] = 2 - 2 * i / length; }
}
return win;
}
public double[] Hamming(int len)
{
double[] win = new double[len];
for (int i = 0; i < len; i++)
{
win[i] = 0.54 - 0.46 * Math.Cos(Math.PI * 2 * i / len);
}
return win;
}
public double[] Hanning(int len)
{
double[] win = new double[len];
for (int i = 0; i < len; i++)
{
win[i] = 0.5 * (1 - Math.Cos(2 * Math.PI * i / len));
}
return win;
}
public double[] Blackman(int len)
{
double[] win = new double[len];
for (int i = 0; i < len; i++)
{
win[i] = 0.42 - 0.5 * Math.Cos(Math.PI * 2 * (double)i / len) + 0.08 * Math.Cos(Math.PI * 4 * (double)i / len);
}
return win;
}
}
Recommend
-
12
一 第一版一版的递归实现 n!,比如 5!= 5 * 4 * 3 * 2 *1 let count = 9000; const fact = (n) => { if (n == 1) { return 1; } else { return n * fact(n - 1); } }; let a = fact(coun...
-
10
在这篇文章里,我将介绍栈的基本操作和它的一些应用。 我们将看到栈在括号匹配检测,表达式求值,函数调用上的应用。 递归是一种特殊的函数调用,由于递归在程序设计中十分重要且不容易理解,所以我将阐述我对递归的理解。 最后我们将...
-
10
Friday Q&A 2012-11-02Loading [MathJax]/jax/output/HTML-CSS/jax.js mikeash.com: just this guy, you know? Friday Q&A 2012-11-02: Building the FFT by
-
8
golang二叉树翻转(递归与非递归实现)
-
6
在上一篇博文中,我们谈到了一种利用分治法优化整数乘法的算法,它的时间复杂度约为O(n1.59),而本文将介绍另一种基于快速傅立叶变换(Fast Fourier Transform, FFT)的整数乘法,它的时间复杂度更低,为O(nlogn)。这个方法将整数乘法和多项式乘法联系起来,而...
-
6
C++实现裸音频数据的FFT变换_shixin_0125的技术博客_51CTO博客#include "waveconvertor.h" // // class CWaveConvertor /* Pjotr '87. */ // As best as I can determine this is in the public domain. N...
-
6
lis05's blog FFT vs Karatsuba: need...
-
7
-is-this-fft-'s blog [Tutori...
-
6
点值表示法 我们正常表示一个多项式的方式,形如 A(x)=a0+a1x+a2x2+...+anxnA(x)=a0+a1x
-
8
Tutorial on FFT/NTT — The tough made simple. ( Part 1 ) Tutorial on FFT/NTT — The tough made simple. ( Part 1 ) ...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK