我是一名从事电信项目的计算机程序员
在我们的项目中,我必须将一系列复数转换为傅立叶变换。因此我需要FFT
标准的高效C89
代码。
我使用以下代码,它运作良好:
short FFT(short int dir,long m,double *x,double *y)
{
long n,i,i1,j,k,i2,l,l1,l2;
double c1,c2,tx,ty,t1,t2,u1,u2,z;
/* Calculate the number of points */
n = 1;
for (i=0;i<m;i++)
n *= 2;
/* Do the bit reversal */
i2 = n >> 1;
j = 0;
for (i=0;i<n-1;i++) {
if (i < j) {
tx = x[i];
ty = y[i];
x[i] = x[j];
y[i] = y[j];
x[j] = tx;
y[j] = ty;
}
k = i2;
while (k <= j) {
j -= k;
k >>= 1;
}
j += k;
}
/* Compute the FFT */
c1 = -1.0;
c2 = 0.0;
l2 = 1;
for (l=0;l<m;l++) {
l1 = l2;
l2 <<= 1;
u1 = 1.0;
u2 = 0.0;
for (j=0;j<l1;j++) {
for (i=j;i<n;i+=l2) {
i1 = i + l1;
t1 = u1 * x[i1] - u2 * y[i1];
t2 = u1 * y[i1] + u2 * x[i1];
x[i1] = x[i] - t1;
y[i1] = y[i] - t2;
x[i] += t1;
y[i] += t2;
}
z = u1 * c1 - u2 * c2;
u2 = u1 * c2 + u2 * c1;
u1 = z;
}
c2 = sqrt((1.0 - c1) / 2.0);
if (dir == 1)
c2 = -c2;
c1 = sqrt((1.0 + c1) / 2.0);
}
/* Scaling for forward transform */
if (dir == 1) {
for (i=0;i<n;i++) {
x[i] /= n;
y[i] /= n;
}
}
return(true);
}
但是这段代码只支持大小为2^m
的数组。例如CLRS
图书代码。
我们应该转换的阵列不在这个范围内,加零将是昂贵的,所以我正在寻找另一种解决方案,帮助我输入任何尺寸。
像IT++
和matlab
那样的东西。但是正如我们希望它在纯C中使用它们是不可能的。另外,IT++
代码在我检查时被阻止
答案 0 :(得分:2)
如果您正在开发任何大众市场计算平台(英特尔与Windows或OS X,iOS等),那么供应商或制造商就会提供高性能的FFT实现。
否则,您应该评估FFTW。
为2次幂以外的尺寸编写高性能FFT是一项复杂的任务。
如果您打算使用自己的实现,那么,仅关于两种尺寸的功能:
您显示的实现在FFT期间计算sqrt
。大多数高性能FFT实现会提前计算常量并将它们存储在表格中。
缩放包含x[i] /= n
和y[i] /= n
中的除法运算。编译器可能将这些实现为除法指令。分区通常是对通用处理器的慢速指令。最好一次计算scale = 1. / n
并乘以scale
而不是除以n
。
更好的是完全省略比例。变换通常在没有比例的情况下有用,或者可以从单个变换中省略比例,并且仅作为聚合比例应用一次。 (例如,不是进行两个比例操作,一个在正向FFT中,一个在反向FFT中,而是将比例运算保留在FFT例程之外,并且在正向FFT和反向FFT之后只执行一次。)
如果频域数据按位颠倒顺序可以接受,则可以省略位反转排列。
如果保持位反转排列,则可以对其进行优化。执行此操作的技术取决于平台。某些平台具有反转整数位的指令(例如,ARM具有rbit
)。如果您的平台没有,您可能希望将位反转索引保留在表格中,或者比当前代码更快地研究计算它们的方法。
如果你保持位反转排列和缩放,你应该考虑同时进行它们。置换使用大量内存运动,缩放使用处理器的算术单元。大多数现代处理器可以同时执行这两种处理,因此您可以从重叠操作中获得一些好处。
您当前的代码使用的是基数为2的蝴蝶。 Radix-4通常更好,因为它可以通过以下方式获得一些优势:只需改变使用哪一个数据并将一些数据添加到减法中就可以完成乘法,反之亦然。
如果您的阵列长度接近处理器上的第一级内存缓存的大小,则部分FFT实现将破坏缓存并显着减慢速度,除非您设计适当的代码来处理此问题(通常通过将数组的部分复制到暂时缓冲。)
如果您的目标处理器具有SIMD功能,您绝对应该使用FFT中的功能;它们极大地提高了FFT性能。
以上内容应该告诉您,编写高效的FFT是一项复杂的任务。除非您想花费大量精力,否则最好使用FFTW或其他现有实现。
答案 1 :(得分:0)
在您的实现中,我对这段代码感到担忧:
z = u1 * c1 - u2 * c2;
u2 = u1 * c2 + u2 * c1;
u1 = z;
当l1
很大时,(u1,u2)会受到大量累积的舍入错误的影响。您可能会得到不准确的转换。
我赞同FFTW的建议,但我认为它具有高度的平台特性。 (大多数FFT库都是。)[编辑:不,它实际上是直接的C89。这正是你所说的你想要的。]
Wikipedia FFT页面列出了一系列适用于奇怪大小的输入数组的算法。我不知道它们是如何工作的,但我相信一般的想法是你使用Rader's algorithm或Bluestein's algorithm进行素数大小的输入,Cooley-Tukey将复合大小的变换减少到一堆大规模变换。
答案 2 :(得分:0)
对于FFTW的替代方案,请检查我的mix-radix FFT,它也处理非2 ^ N FFT长度。 C源可从http://www.corix.dk/Mix-FFT/mix-fft.html获得。