Testing Floating-Point Functions in C

Table of Contents
  1. Testing Floating-Point Functions in C
    1. Introduction
    2. Comparing Floating-Point Numbers
    3. Example: Testing a Square Function
      1. Unit Test for sqr()
      2. Running Multiple Tests
    4. Results
    5. Why Use a Tolerance?
    6. Quick Example Using assert()
      1. Key Points

Introduction

In the sum() example, we used == to compare integers. That works fine for integers and booleans, but not for floating-point numbers (float and double). Why? Because floating-point numbers are stored in binary and often cannot represent decimal values exactly.

For example, two variables that should both hold 1.0 might actually be:

1.000000000003232
1.000000000012565

Comparing these with == will fail, even though they are “close enough.” So we need a different approach.


Comparing Floating-Point Numbers

Instead of checking for exact equality, we check if the numbers are almost equal within a small tolerance:

#include <math.h>
#include <stdbool.h>

// Returns true if a and b differ by less than 1e-6
bool almost_equal(double a, double b) {
    return fabs(a - b) <= 1e-6;
}

This uses fabs() from <math.h> to get the absolute difference and compares it to a threshold (1e-6).


Example: Testing a Square Function

Suppose we have a function sqr() that returns the square of a number:

double sqr(double a) {
    return a * a;
}

Unit Test for sqr()

We write a test function that uses almost_equal():

#include <stdio.h>

bool test_sqr(double input, double expected) {
    printf("sqr(%.6f): ", input);
    double val = sqr(input);
    if (almost_equal(val, expected)) {
        printf("passed\n");
        return true;
    } else {
        printf("FAILED! %.6f (expected %.6f)\n", val, expected);
        return false;
    }
}

Running Multiple Tests

int run_sqr_tests(void) {
    printf("\nTesting sqr()...\n\n");
    int passed = 0;

    if (test_sqr(-1.123456, 1.262155)) passed++;
    if (test_sqr(3.0, 9.0)) passed++;
    if (test_sqr(7.678, 58.951684)) passed++;
    if (test_sqr(0.00001, 0.0000000001)) passed++;

    printf("\nsqr() passed %d tests.\n", passed);
    return passed;
}

Results

Running run_sqr_tests() produces:

Testing sqr()...

sqr(-1.123456): passed
sqr(3.000000): passed
sqr(7.678000): passed
sqr(0.000010): passed

sqr() passed 4 tests.

Why Use a Tolerance?

Floating-point numbers can vary by tiny amounts due to rounding. Using a tolerance like 1e-6 means we accept small differences, which is usually fine for most engineering calculations.


Quick Example Using assert()

For automated checks, we can use assert():

#include <assert.h>
#include <math.h>

double sqr(double a) { return a * a; }
bool almost_equal(double a, double b) { return fabs(a - b) <= 1e-6; }

int main(void) {
    assert(almost_equal(sqr(-1.123456), 1.262155));
    assert(almost_equal(sqr(3.0), 9.0));
    assert(almost_equal(sqr(7.678), 58.951684));
    assert(almost_equal(sqr(0.00001), 0.0000000001));
    return 0;
}

If any test fails, the program stops immediately.


Key Points

  • Avoid == for floating-point comparisons.
  • Use a small tolerance (e.g., 1e-6) for “almost equal.”
  • Test normal, negative, and very small values.
  • assert() is great for quick automated checks.