離散 Hartley 変換は,通常の DFT と同様に Cooley-Tukey 型の FFT の 考えが適用できます.離散 Hartley 変換 :
N-1
A = Σ a cas(2πjk/N) , cas(x) = cos(x) + sin(x)
k j=0 j
に対する周波数間引きの基数 2 の分解は次のようになります.
N/2-1
A = Σ r cas(2πjk/(N/2))
2k j=0 j
N/2-1
A = Σ s cas(2πjk/(N/2))
2k+1 j=0 j
r = a + a
j j N/2+j
s = (a − a )cos(2πj/N) + (a − a )sin(2πj/N)
j j N/2+j N/2-j N-j
通常の DFT の分解と比較すると,奇数の項の分解が少し複雑になり, a_N/2-j と a_N-j という余計な項が入ってきます.したがって,データの 上書きをする場合は,r_j と r_N/2-j と s_j と s_N/2-j の四つの項を 同時に演算する必要があります.このとき,s_j と s_N/2-j の計算で 複素数乗算に相当する演算が含まれ,通常の複素 FFT のバタフライ演算と 同じ構造になってしまいます.この変更を行った高速 Hartley 変換 (FHT) の ルーチンは次のようになります.
| リスト2.4.2-1. 周波数間引き基数 2 の FHT |
|---|
/*
離散 Hartley 変換 :
F[k]=Σ_j=0^n-1 a[j]*(cos(2*pi*j*k/n)+sin(2*pi*j*k/n)),0<=k<n
を計算する. n はデータ数で 2 の整数乗.
*/
#include <math.h>
void fht(int n, double a[])
{
int m, mh, mq, i, j, jr, ji, k, kr, ki;
double theta, wr, wi, xr, xi;
theta = 8 * atan(1.0) / n;
for (m = n; (mh = m >> 1) >= 1; m = mh) {
mq = mh >> 1;
for (i = 0; i <= mq; i++) {
/* ---- (cos + sin) == 1 ---- */
if (i == 0 || i == mq) {
for (jr = i; jr < n; jr += m) {
kr = jr + mh;
xr = a[jr] - a[kr];
a[jr] += a[kr];
a[kr] = xr;
}
continue;
}
/* ---- (cos + sin) != 1 ---- */
wr = cos(theta * i);
wi = sin(theta * i);
for (j = 0; j < n; j += m) {
jr = i + j;
ji = mh - i + j;
kr = jr + mh;
ki = ji + mh;
xr = a[jr] - a[kr];
xi = a[ji] - a[ki];
a[jr] += a[kr];
a[ji] += a[ki];
a[kr] = wr * xr + wi * xi;
a[ki] = wi * xr - wr * xi;
}
}
theta *= 2;
}
/* ---- unscrambler ---- */
i = 0;
for (j = 1; j < n - 1; j++) {
for (k = n >> 1; k > (i ^= k); k >>= 1);
if (j < i) {
xr = a[j];
a[j] = a[i];
a[i] = xr;
}
}
}
|
このルーチンは 2 バタフライルーチンで,cas(x) = 1 のときの例外処理を 行っています.このルーチンのフローグラフは次のようになります.
![]() |
| 図2.4.2-1. 周波数間引き基数 2 の FHT |
|---|
この FHT のルーチンから通常の DFT が簡単に計算できます.FHT から 通常の実数の DFT に変換するルーチンを示しておきます.
| リスト2.4.2-2. FHT を間接的に用いる実数 FFT |
|---|
/*
実数データの DFT :
F[k]=Σ_j=0^n-1 a[j]*exp(-2*pi*i*j*k/n),0<=k<n を計算する.
出力 a[0...n/2], a[n/2+1...n-1] はそれぞれ F[0...n/2] の実部,
F[n/2+1...n-1] の虚部に相当する.
*/
void rfft(int n, double a[])
{
void fht(int n, double a[]);
int nh, j;
double x;
fht(n, a);
nh = n / 2;
for (j = 1; j < nh; j++) {
x = 0.5 * (a[j] - a[n - j]);
a[j] -= x;
a[n - j] = x;
}
}
/*
実数データの DFT の逆変換:
F[k] = (a[0]+a[n/2]*(-1)^k)/2 + Σ_j=1^n/2-1 a[j]*cos(2*pi*j*k/n)
+ Σ_j=1^n/2-1 a[n-j]*sin(2*pi*j*k/n)
を計算する.
注意: スケーリングは 2/n しなければいけない.
*/
void irfft(int n, double a[])
{
void fht(int n, double a[]);
int nh, j;
double x;
nh = n / 2;
a[0] *= 0.5;
a[nh] *= 0.5;
for (j = 1; j < nh; j++) {
x = 0.5 * (a[j] - a[n - j]);
a[j] -= x;
a[n - j] = x;
}
fht(n, a);
}
|
また,複素 DFT も同様に FHT から計算できます.
次に,FHT の基数について考えます.通常の FFT と同様に,FHT も, 混合基数や大きい基数, さらに Split-Radix にすることができて,通常のFFT と同じ割合で演算量を削減できます. 次に,Split-Radix による FHT のルーチンを示します.
| リスト2.4.2-3. Split-Radix FHT |
|---|
/*
離散 Hartley 変換 :
F[k]=Σ_j=0^n-1 a[j]*(cos(2*pi*j*k/n)+sin(2*pi*j*k/n)),0<=k<n
を計算する. n はデータ数で 2 の整数乗.
*/
#include <math.h>
void fht(int n, double a[])
{
int m, mq, mqh, i, j, k;
int j0r, j0i, j1r, j1i, j2r, j2i, j3r, j3i;
double theta, w1r, w1i, w3r, w3i, x0r, x0i, x1r, x1i, x3r, x3i;
/* ---- L shaped butterflies ---- */
theta = 8 * atan(1.0) / n;
for (m = n; m > 4; m >>= 1) {
mq = m >> 2;
mqh = mq >> 1;
/* ---- (cos + sin) == sqrt(2) ---- */
w1r = 2 * cos(theta * mqh);
for (k = m; k <= n; k <<= 2) {
for (j0r = k - m; j0r < n; j0r += 2 * k) {
j1r = j0r + mqh;
j0i = j0r + mq;
j1i = j1r + mq;
j2r = j0i + mq;
j3r = j1i + mq;
j2i = j2r + mq;
j3i = j3r + mq;
x1r = a[j0r] - a[j2r];
x1i = a[j0i] - a[j2i];
a[j0r] += a[j2r];
a[j0i] += a[j2i];
a[j2r] = x1r + x1i;
a[j2i] = x1r - x1i;
x3r = w1r * (a[j1r] - a[j3r]);
x3i = w1r * (a[j1i] - a[j3i]);
a[j1r] += a[j3r];
a[j1i] += a[j3i];
a[j3r] = x3r;
a[j3i] = x3i;
}
}
for (i = 1; i < mqh; i++) {
/* ---- (cos + sin) != sqrt(2) ---- */
w1r = cos(theta * i);
w1i = sin(theta * i);
w3r = cos(theta * 3 * i);
w3i = sin(theta * 3 * i);
for (k = m; k <= n; k <<= 2) {
for (j = k - m; j < n; j += 2 * k) {
j0r = i + j;
j1r = mq - i + j;
j0i = j1r + mq;
j1i = j0r + mq;
j2r = j1i + mq;
j3r = j0i + mq;
j2i = j3r + mq;
j3i = j2r + mq;
x1r = a[j0r] - a[j2r];
x1i = a[j0i] - a[j2i];
a[j0r] += a[j2r];
a[j0i] += a[j2i];
x3r = a[j1r] - a[j3r];
x3i = a[j1i] - a[j3i];
a[j1r] += a[j3r];
a[j1i] += a[j3i];
x0r = x1r + x3r;
x0i = x1i - x3i;
a[j2r] = w1r * x0r + w1i * x0i;
a[j3r] = w1i * x0r - w1r * x0i;
x0r = x1r - x3r;
x0i = x1i + x3i;
a[j3i] = w3r * x0r + w3i * x0i;
a[j2i] = w3i * x0r - w3r * x0i;
}
}
}
theta *= 2;
}
/* ---- radix 2 butterflies (m == 4) ---- */
for (k = 4; k <= n; k <<= 2) {
for (j = k - 4; j < n; j += 2 * k) {
x0r = a[j] - a[j + 2];
x0i = a[j + 1] - a[j + 3];
a[j] += a[j + 2];
a[j + 1] += a[j + 3];
a[j + 2] = x0r;
a[j + 3] = x0i;
}
}
/* ---- radix 2 butterflies (last stage) ---- */
if (n > 1) {
for (j = 0; j < n; j += 2) {
x0r = a[j] - a[j + 1];
a[j] += a[j + 1];
a[j + 1] = x0r;
}
}
/* ---- unscrambler ---- */
i = 0;
for (j = 1; j < n - 1; j++) {
for (k = n >> 1; k > (i ^= k); k >>= 1);
if (j < i) {
x0r = a[j];
a[j] = a[i];
a[i] = x0r;
}
}
}
|
このルーチンは,log(N) 段ある演算で,Split-Radix が意味を持たなくなった 時点で,基数 2 に切り替えています.したがって,通常の FFT に比べて, 切り替えを一段だけ早くしてあります.また,このルーチンは 2 バタフライの Split-Radix FFT で凝ったことは何もしていないのですが,不要な乗算は すべて取り除かれています.
次に,FHT の演算量と通常の FFT の演算量とを比較してみます.上の Split-Radix FHT のルーチンの乗算回数 μ と加算回数 α は次のように なります.
2 19 1 log N
μ = --- N log N - ---- N + 3 + --- (-1)
3 9 9
4 8 1 log N
α = --- N log N - --- N + 1 - --- (-1)
3 9 9
また,複素数乗算に相当する演算を 3 回の実数乗算と 3 回の実数加算で 計算すれば
1 3
μ = --- N log N - --- N + 2
2 2
3 3
α = --- N log N - --- N + 2
2 2
となります.これと通常の複素 Split-Radix FFT の演算量とを比較すると 演算量はちょうど半分になります.しかし,複素 DFT が二回の FHT で 計算できることを考慮すると,演算量はまったく同じということになります. さらに,FHT を DFT に変換するときの加減算も考慮すると,かえって不利に なります.しかし,FHT は複素 FFT に比べて少ないバタフライ数で済むため, 実際の計算では高速になるかもしれません.FHT は複素 FFT に比べて高速だと よく言われるようですが,FHT と FFT の比較を実際にインプリメントする ことで調べた文献[参考文献] によるとその差はほとんどないようです.