TOC prev next

Part 6 - implementing DFT in C

Now you know enough to be able to implement DFT and IDFT. The language of choice will be C.

This implementation is made with readability in mind and to be easy to understand. Speed is of no concern here.

All code written here is licensed under Creative Commons Zero license, whose full text can be found at https://creativecommons.org/publicdomain/zero/1.0/. It basically means that you can do with the code whatever you like without asking for any permissions.

Complex numbers

Let's start by implementing complex numbers. Altough they are part of C99 standard, their support between compilers differs and is not always available.

First structure to store complex numbers.

typedef struct
{
    float r; // real part
    float i; // imaginary part
} cnum;

Now time for operations on complex numbers: addition and multiplication

// complex numbers addition a+b
cnum cnum_add(cnum a, cnum b)
{
    cnum out;

    out.r = a.r + b.r;
    out.i = a.i + b.i;
    return out;
}

// complex numbers multiplication a*b
cnum cnum_mul(cnum a, cnum b)
{
    cnum out;

    out.r = (a.r * b.r) - (a.i * b.i);
    out.i = (a.r * b.i) + (a.i * b.r);
    return out;
}

Forward DFT

Now that we have complex numbers we can implement DFT. Let's start with forward DFT. For reminder

$$x = (x_0, x_1, x_2, ..., x_{N-1})$$

$$DFT(x) = y = (y_0, y_1, y_2, ..., y_{N-1})$$

$$y_k = \sum_{n=0}^{N-1}x_n*e^{-j*2\pi*k/N*n} = \sum_{n=0}^{N-1}x_n* (cos(2\pi*k/N*n) - isin(2\pi*k/N*n))$$

So DFT function takes a list x of length N and returns a list y of length N. Both lists contain complex numbers.

#include <math.h>

// C99 standard does not define constant PI
#define PI 3.1415926218032837

void dft(cnum *x, /*out*/cnum *y, int N)
{
    // make sure y is zeroed before use
    for(int n=0; n<N; ++n)
    {
        y[n].r = 0.0;
        y[n].i = 0.0;
    }

    float Nf = (float) N;

    // for complex sinusoid of k-th frequency
    for(int k=0; k<N; ++k)
    {
        // project signal onto that complex sinusoid
        for(int n=0; n<N; ++n)
        {
            cnum csin;
            csin.r = cosf(2*PI*k/Nf*n);
            csin.i = -sinf(2*PI*k/Nf*n); // remember about minus
            cnum addend = cnum_mul(x[n], csin);

            y[k] = cnum_add(y[k], addend);
        }
    }
}

There are few elements that require some explanation or additional attention.

We include math.h to have sinf() and cosf() functions.

Because C99 standard does not define constant PI (and M_PI is a POSIX or GNU extension) we shall define it ouselves.

Next, before doing anything, values of y are zeroed as we will be using them to store intermediary values of the sum.

Next we explicitly cast N value to type float. This is because we are computing value of cos(2*PI*k/N*n) and sin(2*PI*k/N*n). If N were integer (and compiler did a very strange thing) there could happen integer division, which would ruin the algorithm. To make sure it never happens just force N to be used as a float there and problem solved. Better safe than sorry.

Last thing that you shall remember is to put minus sign before sinf() function. If your code gives results with reversed signs in imaginary numbers (eg. 2+2j instead of 2-2j), then this is where you have made a mistake.

Inverse DFT

Reminding the formula

$$x_n = \frac{1}{N} \sum_{k=0}^{N-1}y_k * e^{j*2\pi*k/N*n}$$

we see that inverse DFT is almost the same as forward DFT, so their implementations will also be alike.

void idft(cnum *y, /*out*/ cnum *x, int N)
{
    // make sure x is zeroed before use
    for(int n=0; n<N; ++n)
    {
        x[n].r = 0.0;
        x[n].i = 0.0;
    }

    float Nf = (float) N;

    // for a given point
    for(int n=0; n<N; ++n)
    {
        // in every complex sinusoid
        for(int k=0; k<N; ++k)
        {
            cnum csin;
            csin.r = cosf(2*PI*k/Nf*n);
            csin.i = sinf(2*PI*k/Nf*n); // no minus this time
            cnum addend = cnum_mul(y[k], csin);

            x[n] = cnum_add(x[n], addend);
        }
    }

    // divide points by N
    for(int n=0; n<N; ++n)
    {
        x[n].r /= Nf;
        x[n].i /= Nf;
    }
}

The obvious difference is that this time y is input, x is output. And so x is zeroed before anything happens.

Next internal and external loops are swapped.
In forward DFT projecting signal x onto k-th csin means that k is set and loop is walking over subsequent points of that csin (by incrementing values of n).
In inverse DFT getting value of a sample n means that n is set and loop is walking over csins with subsequent frequencies (by incrementing values of k).

In IDFT there is no minus sign in exponential and so none before sinf() function.

Values of IDFT shall be divided by length of a signal to give original signal in time domain. This is dealt with a loop over all the samples at the end of the function.

Using the code

Time to write main function and make sure that everything works as it should. If your code happens not to work, check places mentioned above.

#include <stdio.h>

int main(void)
{
    cnum x[4], y[4], restored_x[4];

    // create some input signal
    x[0].r = 6.0;
    x[0].i = 0.0;
    x[1].r = 3.0;
    x[1].i = 0.0;
    x[2].r = 5.0;
    x[2].i = 0.0;
    x[3].r = 1.0;
    x[3].i = 0.0;

    // compute forward dft
    dft(x, y, 4);

    // display result of forward dft
    printf("%f%+fi\n", y[0].r, y[0].i);
    printf("%f%+fi\n", y[1].r, y[1].i);
    printf("%f%+fi\n", y[2].r, y[2].i);
    printf("%f%+fi\n", y[3].r, y[3].i);

    // compute inverse dft
    idft(y, restored_x, 4);

    // separate result of dft and idft with empty line
    printf("\n");

    // display result of inverse dft
    printf("%f%+fi\n", restored_x[0].r, restored_x[0].i);
    printf("%f%+fi\n", restored_x[1].r, restored_x[1].i);
    printf("%f%+fi\n", restored_x[2].r, restored_x[2].i);
    printf("%f%+fi\n", restored_x[3].r, restored_x[3].i);

    return 0;
}

Remember that computations done with floating point numbers are often approximate, so don't expect that x[0] == restored_x[0]. But you shall expect that difference between them to be about $10^{-8}$ in case of float (which is a level of a round off error). To see this you may add following code at the end of main.

    // show the difference between values of original and reconstructed signal
    printf("\n");
    printf("%d %d\n", x[2].r == restored_x[2].r, x[2].i == restored_x[2].i);
    printf("%e, %e\n", x[2].r - restored_x[2].r, x[2].i - restored_x[2].i);

Full code

To get a complete code download a source file.

You may also get Makefile, although it does not contain much.

Compilation and running

Using cc (gcc) as a compiler you may compile the code using

cc -Wall -Wextra -std=c99 -o dft dft.c -lm

-Wall -Wextra -std=c99 are to make sure we are not doing stupid things and are following c99 standard.

Don't forget to put option -lm to link program with mathematics library. Otherwise you will get error about unknown symbols sinf, cosf.

Program does not take any parameters, so running it is just simple ./dft

[noniwoo src]$ ./dft
15.000000+0.000000i
1.000000-2.000001i
7.000000+0.000001i
1.000000+2.000000i

6.000000+0.000000i
3.000000-0.000000i
5.000000-0.000000i
1.000000-0.000000i

1 0
0.000000e+00, 8.940697e-08
next part