C math.h Library

The C standard library’s <math.h> header gives your programs access to a full set of mathematical functions — square roots, powers, trigonometry, logarithms, and rounding — all operating on floating-point values. Without it, you would have to hand-write approximations for operations the C library already implements efficiently and accurately using the CPU’s floating-point hardware. Nearly every C program that does numeric work, from graphics and games to physics simulations and financial calculations, relies on math.h.

Overview / How math.h Works

math.h is a header file: it only declares function prototypes. The actual machine code that implements sqrt, sin, pow, and the rest lives in a separate library called libm on Linux and most Unix-like systems (on Windows, the math functions are usually bundled directly into the C runtime). This means that on gcc or clang you must explicitly link the math library with the -lm flag:

gcc program.c -o program -lm

If you forget -lm, the code will usually still compile, but the linker will fail with an error like undefined reference to 'sqrt', because the compiler found the declaration in the header but could not find the compiled function body.

Almost every function in math.h operates on double (double-precision, 64-bit IEEE 754 floating point, roughly 15–17 significant decimal digits). Most functions also have float and long double variants distinguished by an f or l suffix — for example sqrtf(float) and sqrtl(long double). Using the correctly suffixed function for your argument’s type avoids unnecessary conversions and keeps precision consistent.

Functions in math.h generally fall into a few families:

  • Power and root: pow, sqrt, cbrt
  • Exponential and logarithmic: exp, log, log10, log2
  • Trigonometric: sin, cos, tan, asin, acos, atan, atan2
  • Hyperbolic: sinh, cosh, tanh
  • Rounding and remainder: floor, ceil, round, trunc, fmod
  • Classification: isnan, isinf, isfinite

Some math functions have a restricted domain. For example, sqrt is only defined for non-negative numbers, and log is only defined for positive numbers. When you pass an invalid argument, the function does not stop your program — instead it returns a special IEEE 754 value called NaN (Not a Number) and sets the global variable errno (declared in <errno.h>) to EDOM (domain error). Similarly, if a result is too large to represent as a double, the function returns HUGE_VAL (or the built-in INFINITY constant) and sets errno to ERANGE (range error). Checking errno is the standard way to detect these failures, since the function’s return value alone can’t distinguish “a huge but valid number” from “an overflow.”

Syntax

The general form of a math.h call is simple — pass one or more numeric arguments and receive a double result:

#include <math.h>

double result = function_name(argument1, argument2 /* , ... */);
Function Signature Description
sqrt double sqrt(double x) Square root of x (domain: x >= 0)
pow double pow(double base, double exp) Raises base to the power exp
fabs double fabs(double x) Absolute value of a floating-point number
ceil double ceil(double x) Rounds up to the nearest integer value
floor double floor(double x) Rounds down to the nearest integer value
round double round(double x) Rounds to the nearest integer, halves away from zero
fmod double fmod(double x, double y) Floating-point remainder of x / y
sin/cos/tan double sin(double x) Trigonometric functions; argument is in radians
log double log(double x) Natural logarithm (base e), domain: x > 0

Examples

Example 1: Basic power, root, and rounding functions

#include <stdio.h>
#include <math.h>

int main(void) {
    double x = 16.0;
    double y = 2.0;

    printf("sqrt(%.1f) = %.2f\n", x, sqrt(x));
    printf("pow(%.1f, %.1f) = %.2f\n", x, y, pow(x, y));
    printf("fabs(-9.5) = %.2f\n", fabs(-9.5));
    printf("ceil(4.3) = %.2f\n", ceil(4.3));
    printf("floor(4.8) = %.2f\n", floor(4.8));

    return 0;
}

Output:

sqrt(16.0) = 4.00
pow(16.0, 2.0) = 256.00
fabs(-9.5) = 9.50
ceil(4.3) = 5.00
floor(4.8) = 4.00

This example shows the most commonly used math functions. sqrt(16.0) returns 4.00 because 4 × 4 = 16. pow(16.0, 2.0) raises 16 to the power of 2, giving 256. fabs strips the sign from a negative number, and ceil/floor round toward positive and negative infinity respectively — notice they round to the nearest integer in that direction, not simply truncate.

Example 2: Trigonometric functions and degree-to-radian conversion

All trigonometric functions in math.h expect their argument in radians, not degrees. A common mistake is passing a raw degree value straight into sin or cos. Convert degrees to radians first using the formula radians = degrees × (PI / 180):

#include <stdio.h>
#include <math.h>

#define PI 3.14159265358979323846

int main(void) {
    double angle_deg = 60.0;
    double angle_rad = angle_deg * (PI / 180.0);

    printf("sin(60 deg) = %.4f\n", sin(angle_rad));
    printf("cos(60 deg) = %.4f\n", cos(angle_rad));
    printf("tan(60 deg) = %.4f\n", tan(angle_rad));

    return 0;
}

Output:

sin(60 deg) = 0.8660
cos(60 deg) = 0.5000
tan(60 deg) = 1.7321

Here we define our own PI constant for portability (some compilers only expose the non-standard M_PI macro when certain feature-test macros are defined). The results match the well-known exact values: sin(60°) = √3/2 ≈ 0.8660, cos(60°) = 0.5, and tan(60°) = √3 ≈ 1.7321.

Example 3: Error handling with errno, isinf, round, and fmod

#include <stdio.h>
#include <math.h>
#include <errno.h>

int main(void) {
    errno = 0;
    double result = sqrt(-4.0);

    if (errno == EDOM) {
        printf("Domain error: cannot take sqrt of a negative number\n");
    } else {
        printf("sqrt(-4.0) = %.2f\n", result);
    }

    double big = pow(10.0, 400.0);
    if (isinf(big)) {
        printf("pow(10, 400) overflowed to infinity\n");
    }

    printf("round(2.5) = %.1f\n", round(2.5));
    printf("fmod(10.0, 3.0) = %.2f\n", fmod(10.0, 3.0));

    return 0;
}

Output:

Domain error: cannot take sqrt of a negative number
pow(10, 400) overflowed to infinity
round(2.5) = 3.0
fmod(10.0, 3.0) = 1.00

This example demonstrates defensive error handling. We reset errno to 0 before the call (it is not automatically cleared), then check whether sqrt set it to EDOM. We also use isinf to detect that 10400 is far beyond what a double can represent, so it becomes infinity. Finally, round(2.5) shows that C rounds halves away from zero (giving 3.0, not 2.0), and fmod computes a true floating-point remainder, unlike the % operator which only works on integers.

Under the Hood

When you call sqrt(x), several things happen in sequence:

  • The compiler sees the prototype double sqrt(double) from <math.h> and generates a call instruction (or, with optimizations enabled, may emit a direct hardware square-root instruction like sqrtsd on x86-64, bypassing the function call entirely).
  • If a real function call is generated, the linker resolves the symbol sqrt against libm, which contains a highly optimized, tested implementation (often written in assembly for critical functions).
  • The CPU’s floating-point unit performs the computation in IEEE 754 double precision, which stores numbers using 1 sign bit, 11 exponent bits, and 52 mantissa bits.
  • If the input is invalid for the function’s mathematical domain, the implementation sets errno and returns a bit pattern representing NaN, which then propagates through any further arithmetic (any operation involving NaN produces NaN).

This is why a single invalid math operation deep in a calculation can silently corrupt an entire result chain unless you check for it.

Common Mistakes

1. Forgetting to link the math library

gcc program.c -o program

This fails at link time with undefined reference to 'sqrt' on many Linux toolchains, because the declaration is found but the implementation is not. Fix it by linking -lm:

gcc program.c -o program -lm

2. Passing raw degrees to trigonometric functions

/* Wrong: sin expects radians, not degrees */
double result = sin(90.0);  /* Not sin of 90 degrees! */

Since sin expects radians, sin(90.0) computes the sine of 90 radians (a huge angle after wrapping), not 90 degrees. Convert first:

double result = sin(90.0 * (PI / 180.0));

3. Comparing floating-point results with ==

/* Wrong: floating point results are rarely exactly equal */
if (sqrt(2.0) * sqrt(2.0) == 2.0) {
    printf("Equal\n");
}

Due to rounding error in binary floating-point representation, this comparison often fails even though the math is “correct.” Use a small tolerance instead:

if (fabs(sqrt(2.0) * sqrt(2.0) - 2.0) < 1e-9) {
    printf("Equal\n");
}

Best Practices

  • Always link with -lm when compiling with gcc or clang on Linux.
  • Reset errno to 0 before a math call if you plan to check it afterward, since it is never cleared automatically.
  • Convert degrees to radians before calling any trigonometric function.
  • Never compare floating-point results with ==; use a tolerance-based comparison with fabs.
  • Use isnan and isinf to validate results after operations that might overflow or hit an undefined domain.
  • Match function precision to your data: use sqrtf for float arguments and sqrtl for long double to avoid unnecessary implicit conversions.
  • Prefer fmod over % when you need a remainder of floating-point values, since % only works on integer types.

Practice Exercises

  • Write a program that reads a circle’s radius and prints its area (πr²) and circumference (2πr) using a defined PI constant and pow or plain multiplication.
  • Write a program that checks whether an integer is a perfect square by computing sqrt of it, rounding the result, and verifying that the rounded value squared equals the original number.
  • Write a program that solves a quadratic equation ax² + bx + c = 0 using the quadratic formula and sqrt. Handle the case where the discriminant (b² - 4ac) is negative by printing a message instead of calling sqrt on a negative number.

Summary

  • math.h declares numeric functions; their implementations live in libm, so link with -lm on gcc/clang.
  • Most functions operate on double and have f/l suffixed variants for float and long double.
  • Trigonometric functions expect radians, not degrees — convert with degrees * (PI / 180).
  • Invalid inputs set errno to EDOM (domain error) or ERANGE (range error) and return NaN or infinity instead of stopping the program.
  • Never compare floating-point results with ==; use a small tolerance with fabs instead.
  • Useful diagnostic functions include isnan, isinf, and isfinite for validating results after risky calculations.