Fast Fourier Transform Example

I provide a simple example code to compute the Fast Fourier Transform (FFT) of a sequence of length 2n where n is an integer. The implementation only computes the forward transform, discretized in time. The routine does not precompute the twiddle factors (aka the roots of unity).

This is a discretization in time (DIT) implementation of the FFT. You can also discretize in frequency (DIF). One would have to slightly modify the internals of the fast_fourier_transform function, but the changes would be minor.

If the inverse transform is desired, you can compute it using the forward transform multiple different ways. I prefer to conjugate the series, next forward transform, and finally conjugate the resulting series. Then I can apply whatever normalization I desire. But if this is too complicated, you can always copy and paste the code below, change the function name to inverse_fast_fourier_transform (whatever you prefer) and finally swap the sign of the angle variable. Then that will be an unnormalized inverse FFT.


/* Fast Fourier Transform (FFT) */
#include <math.h>
#include <stddef.h>
#include <stdint.h>

typedef struct complex_double {
    double real;
    double imag;
} complex_double;

void complex_add_inplace(complex_double *a, complex_double b) {
    a->real += b.real;
    a->imag += b.imag;
}

complex_double complex_sub(complex_double a, complex_double b) {
    return (complex_double){a.real - b.real, a.imag - b.imag};
}

complex_double complex_mul(complex_double a, complex_double b) {
    return (complex_double){a.real * b.real - a.imag * b.imag, a.imag * b.real + a.real * b.imag};
}

void complex_swap(complex_double *a, complex_double *b) {
    uint64_t *ar = (uint64_t *)&a->real;
    uint64_t *ai = (uint64_t *)&a->imag;
    uint64_t *br = (uint64_t *)&b->real;
    uint64_t *bi = (uint64_t *)&b->imag;

    /* Because XOR operates bit-wise, I can independantly swap the real and
     * imaginary components of a and b separately. */
    *br ^= *ar;
    *ar ^= *br;
    *br ^= *ar;

    *bi ^= *ai;
    *ai ^= *bi;
    *bi ^= *ai;
}

/* Fast fourier transform, decimated-in-time (DIT) */
void fast_fourier_transform(complex_double *data, size_t n_samples, size_t stride) {
    /* Bit-wise reversal */
    size_t position, target = 0, half_samples = n_samples >> 1;

    for (position = 0; position < n_samples; position++) {
        if (target > position) {
            size_t ip = position * stride, it = target * stride;
            complex_swap(data + ip, data + it);
        }

        size_t mask = half_samples;

        while (target & mask) {
            target &= ~mask;
            mask >>= 1;
        }
        target |= mask;
    }

    /* Butterfly loops */
    for (size_t span = 1; span < n_samples; span <<= 1) {
        size_t group_step = span << 1;

        for (size_t wing_idx = 0; wing_idx < span; wing_idx++) {

            /* Twiddle factors are the roots of unity. They could be precomputed for speed, but that
             * requires memory management, so I do not want to deal with it now. */
            double angle = -M_PI * wing_idx / span;
            complex_double twiddle = {cos(angle), sin(angle)};

            for (size_t node = wing_idx; node < n_samples; node += group_step) {
                size_t upper_idx = node * stride, lower_idx = (node + span) * stride;

                /* This the the butterfly step. There is only one complex
                 * multiplication to compute two new values. */
                complex_double product = complex_mul(twiddle, data[lower_idx]);
                data[lower_idx] = complex_sub(data[upper_idx], product);
                complex_add_inplace(data + upper_idx, product);
            }
        }
    }
}