What is math.h and why does -lm exist?

Real-World Analogy

math.h is like a calculator built into C — instead of coding square root from scratch, you call sqrt(). But unlike Python's import math which just works, in C you must tell the linker where to find the calculator by adding -lm at compile time. The header file (math.h) is just a menu listing the available dishes; the actual kitchen (the implementations) lives in a separate library called libm. Without -lm, the linker looks for the kitchen and finds nothing.

In Python, import math; math.sqrt(25) just works — the standard library is loaded automatically. In C, the process is split into two steps: the header (#include <math.h>) gives the compiler the function signatures, and the library (-lm) gives the linker the actual compiled implementations.

Python
import math

# Just works — no linking step
result = math.sqrt(25)    # 5.0
angle  = math.sin(math.pi / 2)  # 1.0
print(math.floor(3.7))   # 3
print(math.ceil(3.2))    # 4
C
#include <math.h>
#include <stdio.h>

/* Compile: gcc prog.c -o prog -lm */
int main(void) {
    double r = sqrt(25.0);    /* 5.0 */
    double a = sin(M_PI / 2); /* 1.0 */
    printf("%.1f\n", floor(3.7)); /* 3.0 */
    printf("%.1f\n", ceil(3.2));  /* 4.0 */
}
The -lm Linker Flag

The header #include <math.h> only provides declarations — it tells the compiler the function signatures exist. The actual implementations of sqrt, sin, pow, etc. live in libm, a separate compiled library. Without -lm, the linker cannot find them and you get: undefined reference to 'sqrt'. Always put -lm at the end of the gcc command: gcc program.c -o program -lm

abs() vs fabs() — A Critical Distinction

This is one of the most common traps in C math programming:

FunctionInput TypeOutput TypeHeaderNotes
abs(x) int int <stdlib.h> For integers only — NOT in math.h!
fabs(x) double double <math.h> For doubles — the one you want for floats
fabsf(x) float float <math.h> Float variant
fabsl(x) long double long double <math.h> Long double variant
Common Mistake: Using abs() on a double

If you call abs(-3.7) with #include <stdlib.h>, the double is silently truncated to an int first, giving abs(-3) = 3 instead of 3.7. This is undefined behavior in older C standards and a silent bug in practice. Always use fabs() for floating-point absolute values.

Rounding Functions at a Glance

For positive numbers, floor and trunc behave identically. The difference shows with negatives: floor(-3.7) = -4.0 (toward -infinity), but trunc(-3.7) = -3.0 (toward zero). ceil(-3.7) = -3.0 (toward +infinity). round(-3.7) = -4.0 (nearest integer, halfway away from zero).

math.h function reference

All functions below require #include <math.h> and gcc ... -lm. All take and return double unless noted. Trig functions work in radians, not degrees.

Absolute Value & Rounding

FunctionSignatureDescriptionExample
fabs double fabs(double x) Absolute value of x fabs(-3.5) → 3.5
floor double floor(double x) Round down toward -∞ floor(3.9) → 3.0, floor(-3.1) → -4.0
ceil double ceil(double x) Round up toward +∞ ceil(3.1) → 4.0, ceil(-3.9) → -3.0
round double round(double x) Round to nearest, halfway away from 0 round(3.5) → 4.0, round(-3.5) → -4.0
trunc double trunc(double x) Truncate toward zero (drop fractional part) trunc(3.9) → 3.0, trunc(-3.9) → -3.0
fmod double fmod(double x, double y) Floating-point remainder of x/y fmod(5.3, 2.0) → 1.3

Powers, Roots & Exponentials

FunctionSignatureDescriptionExample
pow double pow(double x, double y) x raised to power y pow(2.0, 10.0) → 1024.0
sqrt double sqrt(double x) Square root of x (x ≥ 0) sqrt(25.0) → 5.0
exp double exp(double x) e raised to power x (e ≈ 2.71828) exp(1.0) → 2.71828
log double log(double x) Natural logarithm (base e) of x log(M_E) → 1.0
log10 double log10(double x) Base-10 logarithm of x log10(100.0) → 2.0
log2 double log2(double x) Base-2 logarithm of x log2(8.0) → 3.0

Trigonometric Functions (arguments in radians)

FunctionSignatureDescriptionExample
sin double sin(double x) Sine of x (in radians) sin(M_PI/2) → 1.0
cos double cos(double x) Cosine of x (in radians) cos(0.0) → 1.0
tan double tan(double x) Tangent of x (in radians) tan(M_PI/4) → 1.0
asin double asin(double x) Arcsine, domain [-1,1], range [-π/2, π/2] asin(1.0) → π/2
acos double acos(double x) Arccosine, domain [-1,1], range [0, π] acos(1.0) → 0.0
atan double atan(double x) Arctangent, range (-π/2, π/2) — loses quadrant info atan(1.0) → π/4
atan2 double atan2(double y, double x) Arctangent of y/x using both signs — preserves quadrant, range (-π, π] atan2(1.0, -1.0) → 3π/4

Mathematical Constants

/* These constants are defined in math.h (POSIX extension) */
/* On some compilers, add -D_USE_MATH_DEFINES (MSVC) or use _GNU_SOURCE (GCC) */

M_PI    = 3.14159265358979323846  /* π (pi) */
M_E     = 2.71828182845904523536  /* e (Euler's number) */
M_SQRT2 = 1.41421356237309504880  /* √2 */
M_LN2   = 0.69314718055994530941  /* ln(2) */

/* Degree/radian conversions using M_PI */
double deg_to_rad(double deg) { return deg * M_PI / 180.0; }
double rad_to_deg(double rad) { return rad * 180.0 / M_PI; }
Rounding table summary: For x = 3.7: floor=3.0, ceil=4.0, round=4.0, trunc=3.0. For x = -3.7: floor=-4.0, ceil=-3.0, round=-4.0, trunc=-3.0. The negative case is where floor and trunc diverge.

Complete programs you can compile and run

Example 1 — Distance between two 2D points sqrt + pow
#include <math.h>
#include <stdio.h>

/*
 * distance2d: returns Euclidean distance between (x1,y1) and (x2,y2).
 *
 * Formula: sqrt( (x2-x1)^2 + (y2-y1)^2 )
 *
 * pow(a, 2.0) computes a^2.  Alternatively: (x2-x1)*(x2-x1) is faster
 * for squaring, but pow is clearer for general exponents.
 */
double distance2d(double x1, double y1, double x2, double y2) {
    double dx = x2 - x1;          /* horizontal difference */
    double dy = y2 - y1;          /* vertical difference   */
    return sqrt(pow(dx, 2.0) + pow(dy, 2.0));   /* Pythagoras */
}

int main(void) {
    /* Classic 3-4-5 right triangle */
    double d1 = distance2d(0.0, 0.0, 3.0, 4.0);
    printf("distance((0,0) to (3,4))  = %.4f\n", d1);   /* 5.0000 */

    /* Origin to (1,1) = sqrt(2) */
    double d2 = distance2d(0.0, 0.0, 1.0, 1.0);
    printf("distance((0,0) to (1,1))  = %.6f\n", d2);   /* 1.414214 */

    /* Arbitrary points */
    double d3 = distance2d(2.5, -1.0, 6.5, 2.0);
    printf("distance((2.5,-1) to (6.5,2)) = %.4f\n", d3);  /* 5.0000 */

    return 0;
}
/* Compile: gcc example1.c -o example1 -lm */
Output
distance((0,0) to (3,4)) = 5.0000
distance((0,0) to (1,1)) = 1.414214
distance((2.5,-1) to (6.5,2)) = 5.0000
Example 2 — Angle conversion and trigonometry with M_PI sin, cos, M_PI, atan2
#include <math.h>
#include <stdio.h>

/* Convert degrees to radians */
double deg_to_rad(double degrees) {
    return degrees * M_PI / 180.0;
}

/* Convert radians to degrees */
double rad_to_deg(double radians) {
    return radians * 180.0 / M_PI;
}

int main(void) {
    /* sin and cos expect radians */
    printf("sin(90 deg) = %.6f\n", sin(deg_to_rad(90.0)));   /* 1.000000 */
    printf("cos(0 deg)  = %.6f\n", cos(deg_to_rad(0.0)));    /* 1.000000 */
    printf("cos(180 deg)= %.6f\n", cos(deg_to_rad(180.0)));  /* -1.000000 */
    printf("sin(30 deg) = %.6f\n", sin(deg_to_rad(30.0)));   /* 0.500000 */

    /* atan2(y, x) gives full-circle angle — preserves quadrant */
    /* Point in quadrant 1: (1, 1) — angle should be 45 deg */
    double a1 = atan2(1.0, 1.0);
    printf("\natan2(1, 1)   = %.1f deg\n", rad_to_deg(a1));   /* 45.0 */

    /* Point in quadrant 2: (-1, 1) — angle should be 135 deg */
    double a2 = atan2(1.0, -1.0);
    printf("atan2(1, -1)  = %.1f deg\n", rad_to_deg(a2));    /* 135.0 */

    /* Point in quadrant 3: (-1, -1) — angle should be -135 deg */
    double a3 = atan2(-1.0, -1.0);
    printf("atan2(-1, -1) = %.1f deg\n", rad_to_deg(a3));    /* -135.0 */

    /* atan(y/x) loses quadrant info — compares Q1 and Q3 */
    double bad1 = atan(1.0 / 1.0);
    double bad2 = atan(-1.0 / -1.0);   /* same as atan(1.0)! */
    printf("\natan(1/1)   = %.1f deg  (Q1)\n", rad_to_deg(bad1));  /* 45.0 */
    printf("atan(-1/-1) = %.1f deg  (WRONG! should be -135)\n",
           rad_to_deg(bad2));           /* still 45.0 — atan can't tell */

    return 0;
}
/* Compile: gcc example2.c -o example2 -lm */
Output
sin(90 deg) = 1.000000
cos(0 deg) = 1.000000
cos(180 deg)= -1.000000
sin(30 deg) = 0.500000

atan2(1, 1) = 45.0 deg
atan2(1, -1) = 135.0 deg
atan2(-1, -1) = -135.0 deg

atan(1/1) = 45.0 deg (Q1)
atan(-1/-1) = 45.0 deg (WRONG! should be -135)
Example 3 — Rounding functions compared floor, ceil, round, trunc
#include <math.h>
#include <stdio.h>

void show_rounding(double x) {
    printf("x=%-6.1f  floor=%-6.1f  ceil=%-6.1f  round=%-6.1f  trunc=%-6.1f\n",
           x, floor(x), ceil(x), round(x), trunc(x));
}

int main(void) {
    show_rounding(3.7);
    show_rounding(3.5);
    show_rounding(3.2);
    show_rounding(3.0);
    show_rounding(-3.7);
    show_rounding(-3.5);
    show_rounding(-3.2);
    return 0;
}
/* Compile: gcc example3.c -o example3 -lm */
Output
x=3.7 floor=3.0 ceil=4.0 round=4.0 trunc=3.0
x=3.5 floor=3.0 ceil=4.0 round=4.0 trunc=3.0
x=3.2 floor=3.0 ceil=4.0 round=3.0 trunc=3.0
x=3.0 floor=3.0 ceil=3.0 round=3.0 trunc=3.0
x=-3.7 floor=-4.0 ceil=-3.0 round=-4.0 trunc=-3.0
x=-3.5 floor=-4.0 ceil=-3.0 round=-4.0 trunc=-3.0
x=-3.2 floor=-4.0 ceil=-3.0 round=-3.0 trunc=-3.0

Practice problems with solutions

P1 — Hypotenuse of a right triangle Tutorial Exercise

Implement a function hypotenuse(double a, double b) that returns the length of the hypotenuse of a right triangle given the two legs a and b. Use sqrt and pow. Verify with a 3-4-5 triangle, a 5-12-13 triangle, and a 1-1-sqrt(2) triangle. Compile with -lm.

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

/* Pythagorean theorem: c = sqrt(a^2 + b^2) */
double hypotenuse(double a, double b) {
    return sqrt(pow(a, 2.0) + pow(b, 2.0));
    /* Alternative: sqrt(a*a + b*b) — slightly faster for squaring */
}

int main(void) {
    printf("hyp(3, 4)   = %.4f\n", hypotenuse(3.0, 4.0));   /* 5.0000 */
    printf("hyp(5, 12)  = %.4f\n", hypotenuse(5.0, 12.0));  /* 13.0000 */
    printf("hyp(1, 1)   = %.6f\n", hypotenuse(1.0, 1.0));   /* 1.414214 = sqrt(2) */
    return 0;
}
/* Compile: gcc p1.c -o p1 -lm */
Key insight: pow(a, 2.0) computes a^2 as a floating-point operation. For the specific case of squaring, a * a is equivalent and avoids the overhead of the general power function. Both are correct; pow is more readable for the formula. The -lm flag is required because both sqrt and pow live in libm.
P2 — What does floor(-3.7) return, and why? Concept Check

Without running the code, predict what each of the following returns. Then explain in one sentence why floor and trunc give different results for negative numbers.

floor(-3.7)    /* ? */
trunc(-3.7)    /* ? */
ceil(-3.7)     /* ? */
round(-3.7)    /* ? */
floor(-3.7) = -4.0
trunc(-3.7) = -3.0
ceil(-3.7)  = -3.0
round(-3.7) = -4.0
floor always moves toward negative infinity, so -3.7 goes to -4.0 (the integer below it on the number line). trunc always moves toward zero, so -3.7 becomes -3.0 (drop the fractional part regardless of sign). ceil moves toward positive infinity, so -3.7 goes to -3.0 (the integer above it). round picks the nearest integer; -3.7 is closer to -4.0 than to -3.0, so it returns -4.0.

The key distinction: for negative numbers, floor and trunc diverge. For positive numbers they agree (both truncate downward).
P3 — Why does atan2 exist? When must you use it? Tutorial — Inverse Trig

Two points are at (1, 1) and (-1, -1) relative to the origin. Compute the angle from the origin using both atan(y/x) and atan2(y, x). Explain why the results differ and which one is correct for each point.

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

int main(void) {
    /* Point A: (1, 1) — Quadrant 1, should be 45 deg */
    printf("atan(1/1)    = %.1f deg\n", atan(1.0/1.0) * 180.0 / M_PI);    /* 45 */
    printf("atan2(1, 1)  = %.1f deg\n", atan2(1.0, 1.0) * 180.0 / M_PI); /* 45 */

    /* Point B: (-1, -1) — Quadrant 3, should be -135 deg (or 225 deg) */
    printf("atan(-1/-1)  = %.1f deg\n", atan(-1.0/-1.0) * 180.0 / M_PI);     /* 45 WRONG */
    printf("atan2(-1,-1) = %.1f deg\n", atan2(-1.0, -1.0) * 180.0 / M_PI);  /* -135 correct */
    return 0;
}
The problem with atan(y/x): When both y and x are negative, the division -1/-1 = 1.0 — which is the same as 1/1. atan only sees the ratio 1.0 and cannot distinguish which quadrant the original point came from. It always returns a value in (-π/2, π/2), covering only two quadrants.

atan2(y, x) takes y and x separately, looks at their individual signs, and returns an angle in the full range (-π, π], correctly placing the point in Quadrant 3. Always use atan2 when you need a full-circle angle from a 2D vector.
P4 — Spot the bug: wrong header for abs() Common Pitfall

A student writes the following. What is wrong? What output does it produce? How do you fix it?

#include <math.h>
#include <stdio.h>
int main(void) {
    double x = -3.7;
    double result = abs(x);   /* student expects 3.7 */
    printf("abs(-3.7) = %.2f\n", result);
    return 0;
}
/* Output of buggy code:
   abs(-3.7) = 3.00    <-- truncated to integer! */

/* Fix: use fabs() for floating-point absolute value */
#include <math.h>
#include <stdio.h>
int main(void) {
    double x = -3.7;
    double result = fabs(x);   /* correct: 3.7 */
    printf("fabs(-3.7) = %.2f\n", result);  /* 3.70 */
    return 0;
}
The bug: abs() is declared in <stdlib.h> and operates on int. When you pass a double to it, the double is implicitly converted to int first — so -3.7 becomes -3 before abs sees it, yielding 3, not 3.7. The fix is to use fabs() from <math.h>, which accepts and returns a double.

Key concepts to memorize

Card 1 of 8
Question — click to flip
Answer
Click card to flip • Use buttons to navigate

Test your understanding

Topic 33 Quiz — Standard Math Library Score: 0 / 5
1
What compiler flag is required to link the C math library when using functions from math.h?LO1
multiple choice
2
What is the difference between abs() and fabs()?LO1
multiple choice
3
What does floor(-3.7) return?LO1
multiple choice
4
What problem does atan2(y, x) solve that atan(y/x) cannot?LO1
multiple choice
5
Fill in the blank: If you forget to add -lm when compiling a program that calls sqrt(), you will get a _______ error at link time.LO1
fill in the blank
0/5
Quiz complete!