I made a function that directly calculates the discrete Fourier transform in dimension two, as well as functions that automatically initialize a 2D array to either something random, or specific basis elements (when viewing multidimensional arrays as tensors, but that's not important). Also an approximate equality checker. This would be used to verify a more sophisticated implementation of the Fourier transform.
I am mainly looking for feedback on C best practices, proper documentation and code safety, but also on test-design. Is this the proper way to write a test unit?
#include <stdio.h>
#include <stdlib.h>
#include <complex.h>
#include <math.h>
#include <time.h>
#include <stdbool.h>
#include <assert.h>
// For a 2D FFT of size N0 x N1, both powers of two.
#define N0 8
#define N1 4
// Initializes weights
void weightInit(double complex* weights, int length)
{
for (int j = 0; j < length; j++) {
weights[j] = cexp(-2.0 * M_PI * j / length * I);
}
}
// Calculates the N0 x N1 2D FFT directly. Input is overwritten by output.
void naiveFFT2D(double complex input[N0][N1])
{
// We temporarily write the output here. This is disgusting, but
// the only way to allocate a multidimensional array on the heap.
double complex (*output)[N1] = malloc( sizeof(double complex[N0][N1]) );
assert(output); // malloc returns a null pointer when the memory is not available
// and this is the only pointer that gets type casted to 0, so
// this terminates the program iff there is not sufficient memory.
// Initialize the weights
double complex* weightsN0 = malloc(N0 * sizeof(double complex));
double complex* weightsN1 = malloc(N1 * sizeof(double complex));
assert(weightsN0);
assert(weightsN1);
weightInit(weightsN0, N0);
weightInit(weightsN1, N1);
// Compute the FFT, (k0, k1) corresponds to output
for (int k0 = 0; k0 < N0; k0++) {
for (int k1 = 0; k1 < N1; k1++) {
output[k0][k1] = 0;
for (int j0 = 0; j0 < N0; j0++) {
for (int j1 = 0; j1 < N1; j1++) {
output[k0][k1] += input[j0][j1] * weightsN0[j0 * k0 % N0] * weightsN1[j1 * k1 % N2];
}
}
}
}
// Overwite input with output
for (int k0 = 0; k0 < N0; k0++) {
for (int k1 = 0; k1 < N1; k1++) {
input[k0][k1] = output[k0][k1];
}
}
free(weightsN0);
free(weightsN1);
free(output);
}
// Prints an N0 x N1 array.
void printArray(double complex arr[N0][N1])
{
for (int j0 = 0; j0 < N0; j0++) {
printf("\n");
for (int j1 = 0; j1 < N1; j1++) {
printf("%f + %fi | ", creal(arr[j0][j1]), cimag(arr[j0][j1]));
}
}
}
// Initializes input to array with a 1 on place (k0, k1) and 0's elsewhere.
void initBasisElement(int k0, int k1, double complex input[N0][N1])
{
for (int j0 = 0; j0 < N0; j0++) {
for (int j1 = 0; j1 < N1; j1++) {
input[j0][j1] = 0.0;
}
}
input[k0][k1] = 1.0;
}
// Initializes input to random array.
void initRandom(double complex input[N0][N1])
{
srand((unsigned int) clock());
for (int j0 = 0; j0 < N0; j0++) {
for (int j1 = 0; j1 < N1; j1++) {
// a + bi with 0 <= a, b < 1 random doubles
input[j0][j1] = (double) rand() / (double) RAND_MAX + ((double) rand() / (double) RAND_MAX) * I;
}
}
}
// Takes N0 x N1 arrays a and b, and returns true if and only if
// a and b differ less than error at every entry.
bool testEquality(double error, double complex a[N0][N1], double complex b[N0][N1])
{
bool equal = true;
for (int j0 = 0; j0 < N0; j0++) {
for (int j1 = 0; j1 < N1; j1++) {
if (cabs(a[j0][j1] - b[j0][j1]) > error) {
equal = false;
}
}
}
return equal;
}
int main()
{
double complex (*input)[N1] = malloc( sizeof(double complex[N0][N1]) );;
initBasisElement(2, 2, input);
double complex (*b)[N1] = malloc( sizeof(double complex[N0][N1]) );;
initRandom(b);
naiveFFT2D(input);
printArray(input);
if (testEquality(0.001, input, b)) {
printf("\n\nEquality!");
}
return 0;
}