c2java 第9篇 FFT

21 篇文章 1 订阅

傅里叶分析是什么?

设多项式A(x) = a_0 + a_1*x^1 + a_2*x^2 + ... + a_{n-1}*x^{n-1},
系数向量记为a = (a_0, a_1, ..., a_{n-1}),
多项式求值向量记为y=(y_0, y_1, ..., y_{n-1}),
y_{j} = A(w_n^j),
w_n = e^{2*\pi*i/n},
称y = FT(a)为离散傅里叶变换
反过来, 跟据y求系数a也很容易,称a=IFT(y)为逆傅里叶变换。
fft-test.c/fft_ref就是按定义来求FT的,复杂度为O(N^2)

基本原理
====
设n是2的幂。把A(x)拆分为
B(x) = a_0 + a_2 * x + ... + a_{n-2} * x ^{n/2-1}
C(x) = a_1 + a_3 * x + ... + a_{n-1} * x ^{n/2-1}
则A(x) = B(x^2) + x*C(x^2); (I)
直观看B(x), B(x)的次数为n/2, 问题规模减半,那计算量f(n)是否减半呢?
我们需要计算n点:(w_n^0)^2, (w_n^1)^2, ..., (w_n^n)^2,
由折半引理:(w_n^{k+n/2})^2 = (w_n^k)^2, 第k个点等于第n/2+k个点。
得到f(n) = 2*f(n/2) + O(n).

如果按递归写法,需要给子问题开辟系数存储所需的空间,某一时刻
最大为:n + n/2 + n/4 + ... + 2 + 1 = 2*n.
实际内存要求比这个值大,因为内存管理还有额外开销。
一定需要额外空间么,可不可以原地操作呢?

迭代版本
====
递归并不一定能转化为迭代的,有时需要我们重新定义问题和输入数据。
递归中,计算顺序是假设子问题已经算好的,因此不需要特别关;而迭代中却是首要解决的问题。
观察下面这个由递归调用形成的树。
从底到上,第二层左边第一个节点(b0, b1):
b0 = a0 + a4; b1 = a0 - a4;
因为在同一层中,没有其他地方修改或引用原来的a0, a4, 因此
我们可以直接把存储空间b0,b1 放到a0,a4。
因为我们首先是按a0,a4,a2,a6,a1,a5,a3,a7顺序计算的,因此开始时需要
把原始数据A摆成这样的顺序。
下面是把这个过程一般化:
FFT(A, bits)
arrange(A);
n = 2^bits;
for(s = 1; s <= bits; ++s){//s表示在哪一层
 m = 2^s;//当前问题的规模
 for(k = 0; k < n; k += m){//k表示从左边开始数第几个节点
  A[k,...,k+m-1] = 应用等式(I)合并
   A[k,...,k+m/2-1] 和 A[k+m/2, ..., k+m-1]
 }
}

ffmpeg 中的FFT
====
fft_template.c/fft_calc_c 的实部和虚部是放在同一个数组的两端,
比且对于n=4,n=8直接内循环展开。

SSE 3DNow向量指令优化:
x86/fft.asm
 fft_calc

应用
====
1. 多项式乘法,即卷积, 为什么卷积重要?
2. ffmpeg:wma*.c 等,DCT似乎在每个codec都有。
3. 为什么要有MDCT ?

 

代码:

/*FFT.java -- 快速傅里页变换*/
import java.util.*;

class Complex
{
	double re, im;
	public Complex(double a, double b)
	{
		re = a; im = b;
	}
	public String toString()
	{return re + "+" + im+"i";}

	public void exp(int n)
	{//e^{i*2\pi/n}
		re = Math.cos(2*Math.PI/n);
		im = Math.sin(2*Math.PI/n);
	}
	public void multiply(Complex b)
	{//a *= b
		double bre = b.re, bim = b.im,
			   are = re, aim = im;
		re = are * bre - aim * bim;
		im = are * bim + aim * bre;
	}
	public void add(Complex b)
	{
		double bre = b.re, bim = b.im,
			   are = re, aim = im;
		re = are + bre;	
		im = aim + bim;
	}
	public void sub(Complex b)
	{
		double bre = b.re, bim = b.im,
			   are = re, aim = im;
		re = are - bre;	
		im = aim - bim;
	}
	public void assign(Complex b)
	{
		re = b.re;
		im = b.im;
	}	
}

public class FFT
{
	static void fft(Complex[] a, int bits)
	{
		int s, k, j, m, n = 1 << bits;
		Complex w0 = new Complex(.0, .0),
				w  = new Complex(.0, .0),
				u  = new Complex(.0, .0),
				t  = new Complex(.0, .0),
				one = new Complex(1., .0);

		for(s = 1; s <= bits; ++s){
			m = 1 << s;
			w0.exp(m);
			//System.out.printf("w0=%s ", w0);
			for(k = 0; k < n; k += m){
				w.assign(one);
				for(j = 0; j < m/2; ++j){
					u.assign(a[k+j]); //u = a[k+j];
					t.assign(a[k+m/2+j]); t.multiply(w); //t = w * a[k+m/2+j];
					a[k+j].assign(u); a[k+j].add(t); //a[k+j] = u + t;
					a[k+m/2+j].assign(u); a[k+m/2+j].sub(t); //a[k+m/2+j] = u - t;
					w.multiply(w0);
				}
			}
		}	
	}

	static int bitReverse(int n, int bits)
	{
		if(bits <= 1)return n&1;
		return ((n&1)<<(bits-1)) | bitReverse(n>>1, bits-1);
	}
	
	static void init(Complex[] a, Complex[] b, int bits)
	{
		int i, t, n = 1 << bits;	
		for(i = 0; i < n; ++i){
			t = bitReverse(i, bits);
			b[t].assign(a[i]); //a[rev[i]] = a[i];
			//System.out.printf("%d  ", t);
		}
	}

	static void dump(String prefix, Complex[] a)
	{
		int i;
		System.out.printf("%s", prefix);
		for(i = 0; i < a.length; ++i){
			System.out.printf("%.16f+%.16fi, ", a[i].re, a[i].im);
		}
		System.out.printf("%n");
	}

	static void fftRef(Complex[] b, Complex[] a, int bits)
	{
		int i, j, n = 1 << bits;
		Complex x = new Complex(.0, .0),
				sum = new Complex(.0, .0),
				w = new Complex(.0, .0),
				one = new Complex(1.0, .0);	
		x.assign(one);
		w.exp(n);
		for(j = 0; j < n; ++j){
		sum.assign(a[n-1]);
		//下面使用horner方法求多项式值,因为w^j可以直接求,也可以
		//直接从a0开始计算。
		for(i = n - 2; i >= 0; --i){
			sum.multiply(x); sum.add(a[i]); //sum = sum * x +  a[i];
		}
		b[j].assign(sum);
		x.multiply(w);
		//System.out.printf("x=%s ", x);
		}
	}

	static void checkDiff(Complex[] a, Complex[] b)
	{
		int i, n = a.length;
		double max = 0, err = 0., sum = 0.;
		for(i = 0; i < n; ++i){
			err = Math.abs(a[i].re - b[i].re);
			sum += err * err;
			if(err > max)max = err;
			err = Math.abs(a[i].im - b[i].im);
			sum += err * err;	
			if(err > max)max = err;
		}
		System.out.printf("err max=%g, avg=%g%n", max,
				Math.sqrt(sum/n));
	}	

	public static void main(String[] arg){
		int i, j, bits = 16; //3;
		int n = 1 << bits;
		System.out.printf("bits = %d%n", bits);
		/*for(i = 0; i < (1<
   
   
    
    ludi@ubun:~/java
    
    

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值