DTFT・DFT・FFTの違いを整理する:定義・関係・Python実装

離散時間フーリエ変換(DTFT)・離散フーリエ変換(DFT)・高速フーリエ変換(FFT)の関係を、定義・スペクトルの離散化・計算量の観点から整理し、PythonでDFT直接計算とnp.fft.fftの結果一致を検証します。

はじめに

「DTFT」「DFT」「FFT」――フーリエ解析を学ぶと類似した略語が次々に登場し、それぞれの違いと関係を曖昧なまま使ってしまうことが少なくありません。実際には3つは明確に区別されており、

  • DTFT(Discrete-Time Fourier Transform):離散時間信号に対する連続な周波数表現
  • DFT(Discrete Fourier Transform):DTFTを周波数軸でも離散化したもの
  • FFT(Fast Fourier Transform):DFTを高速に計算するためのアルゴリズム

という関係にあります。本記事では、連続時間信号から標本化を経て離散信号に至る流れを起点に、DTFT・DFT・FFTの定義と相互関係を整理し、最後にPythonでDFTの直接計算と np.fft.fft の結果を比較して理解を確認します。

FFTの仕組みとPython実装窓関数とPSD では実装中心に解説しましたが、本記事はその理論的な土台を整理する位置づけです。

連続信号から離散信号へ

連続時間フーリエ変換(CTFT)

連続時間信号 \(x_c(t)\) のフーリエ変換は次のように定義されます。

\[X_c(j\Omega) = \int_{-\infty}^{\infty} x_c(t)\, e^{-j\Omega t}\, dt \tag{1}\]

ここで \(\Omega\) は連続角周波数(rad/s)です。\(X_c(j\Omega)\) は連続関数であり、信号のあらゆる周波数成分を表します。

標本化による離散化

サンプリング定理 で示したように、サンプリング周期 \(T_s\) (サンプリング周波数 \(f_s = 1/T_s\) )で \(x_c(t)\) を標本化すると離散時間信号 \(x[n] = x_c(nT_s)\) が得られます。離散信号上の角周波数 \(\omega\) と連続信号上の角周波数 \(\Omega\) には次の関係があります。

\[\omega = \Omega T_s \tag{2}\]

\(\omega\) は単位「rad/sample」を持ち、\(2\pi\) で周期的です。これがDTFT/DFTを定義する自然な周波数軸になります。

DTFT:離散時間フーリエ変換

DTFTの定義

離散時間信号 \(x[n]\) (\(n \in \mathbb{Z}\) )に対する**離散時間フーリエ変換(DTFT)**は次のように定義されます。

\[X(e^{j\omega}) = \sum_{n=-\infty}^{\infty} x[n]\, e^{-j\omega n} \tag{3}\]

ここで \(\omega \in \mathbb{R}\) は連続な角周波数(rad/sample)で、\(X(e^{j\omega})\) は \(\omega\) について 2π周期 の連続関数です。

逆DTFT

逆変換は次の積分で与えられます。

\[x[n] = \frac{1}{2\pi}\int_{-\pi}^{\pi} X(e^{j\omega})\, e^{j\omega n}\, d\omega \tag{4}\]

積分区間が \([-\pi, \pi]\) なのは、DTFTの周期性から1周期分で十分なためです。

DTFTの主な性質

  • 周期性:\(X(e^{j(\omega + 2\pi)}) = X(e^{j\omega})\)
  • 線形性:\(\mathcal{F}\{a x_1[n] + b x_2[n]\} = a X_1(e^{j\omega}) + b X_2(e^{j\omega})\)
  • 時間シフト:\(\mathcal{F}\{x[n - k]\} = e^{-j\omega k} X(e^{j\omega})\)
  • 畳み込み定理:\(\mathcal{F}\{x_1[n] * x_2[n]\} = X_1(e^{j\omega})\, X_2(e^{j\omega})\)
  • パーセバルの等式:\(\sum_{n} |x[n]|^2 = \dfrac{1}{2\pi}\int_{-\pi}^{\pi} |X(e^{j\omega})|^2 d\omega\)

Z変換との関係

Z変換の記事 で示したように、Z変換 \(X(z) = \sum_n x[n] z^{-n}\) を単位円 \(z = e^{j\omega}\) 上で評価したものがDTFTです。すなわちDTFTはZ変換の特殊ケースで、ROCに単位円を含む場合のみ存在します。

DTFTの「困りごと」

DTFTは数学的に美しい定義ですが、計算機で扱う上では次の問題があります。

  1. 無限和:信号が無限長 \(n \in \mathbb{Z}\) の場合は厳密計算が困難
  2. 連続周波数軸:\(\omega \in \mathbb{R}\) が連続なので、計算機で表現するには離散化が必要

この2点を解決するのがDFTです。

DFT:離散フーリエ変換

DFTの定義

長さ \(N\) の有限長信号 \(x[n]\) (\(n = 0, 1, \ldots, N-1\) )に対する**離散フーリエ変換(DFT)**は次のように定義されます。

\[X[k] = \sum_{n=0}^{N-1} x[n]\, e^{-j 2\pi kn/N}, \quad k = 0, 1, \ldots, N-1 \tag{5}\]

入力 \(N\) サンプル → 出力 \(N\) サンプルの有限長 → 有限長の写像である点が、DTFTとの最大の違いです。

逆DFT(IDFT)

\[x[n] = \frac{1}{N}\sum_{k=0}^{N-1} X[k]\, e^{j 2\pi kn/N}, \quad n = 0, 1, \ldots, N-1 \tag{6}\]

DTFTとDFTの関係

DTFTを周波数軸 \(\omega\) について \(N\) 等分(\(\omega_k = 2\pi k / N\) 、\(k = 0, 1, \ldots, N-1\) )でサンプリングしたものがDFTです。

\[X[k] = X(e^{j\omega})\big|_{\omega = 2\pi k/N} = \sum_{n=0}^{N-1} x[n]\, e^{-j 2\pi kn/N} \tag{7}\]

つまりDFTはDTFTを周波数軸でも離散化したものであり、計算機で扱える有限のデータ構造になります。\(X[k]\) は連続なDTFTスペクトル \(X(e^{j\omega})\) の「サンプル」であり、ビン間の値はサンプリングされていない点に注意が必要です。

周波数分解能

サンプリング周波数 \(f_s\) 、長さ \(N\) のDFTにおいて、ビン \(k\) に対応する物理周波数は

\[f_k = \frac{k}{N} f_s \tag{8}\]

であり、隣接ビン間の周波数分解能は \(\Delta f = f_s / N\) です。観測時間 \(T = N/f_s\) が長いほど分解能は細かくなります。

スペクトル漏れ

式 \((5)\) は \(x[n]\) を周期 \(N\) で周期的に拡張したものに対するDTFTサンプリングと等価です。実信号がこの仮想的な周期と一致しないと、有限長による打ち切りでスペクトル漏れが生じます。これを軽減するのが 窓関数 です。

FFT:高速フーリエ変換

FFTは「アルゴリズム」である

ここで強調したいのは、FFTは新しい変換ではなくDFTを高速に計算するアルゴリズムの総称であるという点です。出力 \(X[k]\) はDFTと完全に同一で、計算手順が異なるだけです。

DFTを式 \((5)\) どおりに素朴に計算すると、各 \(k\) について \(N\) 回の複素乗算と加算が必要なので、全体で \(O(N^2)\) 回の演算になります。\(N = 10^6\) の場合 \(10^{12}\) 回の演算となり、リアルタイム処理は不可能です。

Cooley-Tukey法のアイデア

1965年にCooleyとTukeyが発表したCooley-Tukey FFTは、\(N\) が合成数(特に2の冪乗)のとき、長さ \(N\) のDFTを長さ \(N/2\) の2つのDFTに分割することで計算量を削減します。

\(x[n]\) を偶数番目 \(x_e[m] = x[2m]\) と奇数番目 \(x_o[m] = x[2m+1]\) に分けると、

\[X[k] = \underbrace{\sum_{m=0}^{N/2-1} x_e[m]\, e^{-j 2\pi km/(N/2)}}_{E[k]} + W_N^k \underbrace{\sum_{m=0}^{N/2-1} x_o[m]\, e^{-j 2\pi km/(N/2)}}_{O[k]} \tag{9}\]

ここで \(W_N = e^{-j 2\pi / N}\) は回転因子です。回転因子の対称性 \(W_N^{k + N/2} = -W_N^k\) から、

\[X[k] = E[k] + W_N^k\, O[k], \quad X[k + N/2] = E[k] - W_N^k\, O[k] \tag{10}\]

が得られます。これがバタフライ演算です。長さ \(N\) のDFTを再帰的に2分割すると、計算量の漸化式は \(T(N) = 2T(N/2) + O(N)\) となり、解は

\[T(N) = O(N \log N) \tag{11}\]

になります。アルゴリズムの詳細な導出は FFTの記事 を参照してください。

計算量の比較

サイズ \(N\)DFT \(O(N^2)\)FFT \(O(N \log_2 N)\)高速化倍率
\(2^{10}\)\(\approx 10^6\)\(\approx 10^4\)\(\sim 100\) 倍
\(2^{16}\)\(\approx 4 \times 10^9\)\(\approx 10^6\)\(\sim 4000\) 倍
\(2^{20}\)\(\approx 10^{12}\)\(\approx 2 \times 10^7\)\(\sim 50000\) 倍

\(N\) が大きくなるほどFFTの優位性が顕著になります。

radix-2以外のFFT

実用的なFFTライブラリは2の冪乗以外のサイズも扱えるよう、複数のアルゴリズムを組み合わせています。

  • radix-2 / radix-4 / split-radix:2の冪乗向けの基本アルゴリズム
  • mixed-radix:合成数 \(N = N_1 N_2\) を用いる一般化
  • Bluestein / chirp-Z:任意の \(N\) に対するチャープ畳み込み変換
  • Rader:素数長 \(N\) 向けの巡回畳み込みベース変換

NumPyの np.fft.fft 内部(FFTPACK / pocketfft)はこれらを使い分けるため、\(N\) が2の冪乗でなくても効率よく動作します。

DTFT・DFT・FFTの関係まとめ

変換入力出力周波数軸性格
DTFT離散・無限長連続関数 \(X(e^{j\omega})\)連続・\(2\pi\) 周期数学的定義
DFT離散・有限長 \(N\)離散 \(N\) サンプル \(X[k]\)\(\omega_k = 2\pi k/N\) で離散化計算可能な変換
FFT離散・有限長 \(N\)DFTと同一DFTと同一DFT計算の高速アルゴリズム

要するに、

  • DTFT は理論上の定義
  • DFT はDTFTを周波数軸で離散化した有限版
  • FFT はDFTを \(O(N \log N)\) で計算するアルゴリズム

という階層関係です。FFTの結果はDFTそのものであり、両者を概念的に区別することが重要です。

PythonでDFT直接計算とFFTを比較

DFTの定義どおりの実装

行列形式で書くと、DFTは \(X = W x\) (\(W_{kn} = e^{-j 2\pi kn / N}\) のDFT行列)と表現できます。NumPyのブロードキャストで素直に実装できます。

import numpy as np

def dft_direct(x: np.ndarray) -> np.ndarray:
    """定義式どおりに O(N^2) で DFT を計算する。"""
    x = np.asarray(x, dtype=complex)
    N = x.shape[0]
    n = np.arange(N)
    k = n.reshape(-1, 1)               # 列ベクトル
    W = np.exp(-2j * np.pi * k * n / N)  # DFT 行列 (N x N)
    return W @ x

# --- np.fft.fft と結果を比較 ---
rng = np.random.default_rng(0)
x = rng.standard_normal(256) + 1j * rng.standard_normal(256)

X_dft = dft_direct(x)
X_fft = np.fft.fft(x)

abs_err = np.max(np.abs(X_dft - X_fft))
print(f"最大絶対誤差: {abs_err:.3e}")
# 出力例: 最大絶対誤差: 4.27e-13

両者の差は浮動小数点演算の丸め誤差レベル(\(10^{-13}\) 程度)で、FFTがDFTを正確に計算していることが確認できます。

計算時間の比較

長さ \(N\) を変えて両者の実行時間を比較すると、\(O(N^2)\) と \(O(N \log N)\) の差が体感できます。

import time
import numpy as np
import matplotlib.pyplot as plt

def dft_direct(x):
    x = np.asarray(x, dtype=complex)
    N = x.shape[0]
    n = np.arange(N)
    k = n.reshape(-1, 1)
    W = np.exp(-2j * np.pi * k * n / N)
    return W @ x

sizes = [2**p for p in range(6, 13)]  # 64, 128, ..., 4096
t_dft, t_fft = [], []

for N in sizes:
    x = np.random.randn(N) + 1j * np.random.randn(N)

    # DFT 直接計算(行列積)
    t0 = time.perf_counter()
    _ = dft_direct(x)
    t_dft.append(time.perf_counter() - t0)

    # np.fft.fft
    t0 = time.perf_counter()
    _ = np.fft.fft(x)
    t_fft.append(time.perf_counter() - t0)

# プロット
plt.figure(figsize=(8, 5))
plt.loglog(sizes, t_dft, 'o-', label='DFT direct  (O(N^2))')
plt.loglog(sizes, t_fft, 's-', label='np.fft.fft  (O(N log N))')
plt.xlabel('N (signal length)')
plt.ylabel('Elapsed time [s]')
plt.title('DFT vs FFT: computational cost')
plt.grid(True, which='both', alpha=0.3)
plt.legend()
plt.tight_layout()
plt.show()

両対数プロットでは、DFT直接計算の傾きが約2(\(O(N^2)\) )、FFTの傾きが約1(\(O(N \log N)\) なのでほぼ線形)になり、\(N\) が大きくなるほど差が広がるのが視覚的に確認できます。

DTFTの可視化(参考)

DFTビンの「間」の値を見たい場合は、信号をゼロパディングしてからFFTすると、DTFTを細かくサンプリングしたものが得られます。これは新しい情報を追加するわけではなく、連続スペクトル \(X(e^{j\omega})\) の補間表示です。

import numpy as np
import matplotlib.pyplot as plt

N = 32
n = np.arange(N)
x = np.cos(2 * np.pi * 0.1 * n)  # 正規化周波数 0.1 cycles/sample

# DFT(N点)
X_dft = np.fft.fft(x)
freqs_dft = np.fft.fftfreq(N)

# ゼロパディングでDTFTを近似(4096点に拡張)
N_pad = 4096
X_dtft = np.fft.fft(x, n=N_pad)
freqs_dtft = np.fft.fftfreq(N_pad)

# 正の周波数のみプロット
mask_dft = freqs_dft >= 0
mask_dtft = freqs_dtft >= 0

plt.figure(figsize=(9, 4))
plt.plot(freqs_dtft[mask_dtft], np.abs(X_dtft[mask_dtft]),
         'b-', alpha=0.6, label='DTFT (zero-padded approx.)')
plt.stem(freqs_dft[mask_dft], np.abs(X_dft[mask_dft]),
         linefmt='r-', markerfmt='ro', basefmt=' ',
         label='DFT bins')
plt.xlabel('Normalized frequency [cycles/sample]')
plt.ylabel('Magnitude')
plt.title('DFT bins as samples of the continuous DTFT')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

DFTビン(赤)が連続なDTFT曲線(青)の特定の点を取り出したものであることが直観的に分かります。

まとめ

  • DTFT は離散信号に対する連続周波数表現で、Z変換を単位円上に制限したもの
  • DFT はDTFTを周波数軸で \(N\) 等分にサンプリングした有限版で、計算機で扱える形
  • FFT はDFTを \(O(N \log N)\) で計算するアルゴリズム(出力はDFTと同一)
  • 計算量は \(N\) が大きいほどFFTが圧倒的に有利で、\(N = 2^{20}\) では数万倍の高速化
  • ゼロパディングは新情報を加えず、DTFTの密なサンプリング表示として機能する

3つの関係を整理しておくと、窓関数・PSD推定・フィルタ設計などの応用記事を読んだときに「いまどの軸の話をしているのか」が明確になります。

関連記事

参考文献

  • Cooley, J. W., & Tukey, J. W. (1965). “An algorithm for the machine calculation of complex Fourier series.” Mathematics of Computation, 19(90), 297-301.
  • Oppenheim, A. V., & Schafer, R. W. (2009). Discrete-Time Signal Processing (3rd ed.). Prentice Hall.
  • Proakis, J. G., & Manolakis, D. G. (2006). Digital Signal Processing (4th ed.). Prentice Hall.
  • NumPy FFT documentation