Standard Math Library (math.h)
Mathematical functions for C — trigonometry, exponents, roots, rounding, and more. Remember to link with -lm at compile time.
What is math.h and why does -lm exist?
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.
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
#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 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:
| Function | Input Type | Output Type | Header | Notes |
|---|---|---|---|---|
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 |
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.
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
| Function | Signature | Description | Example |
|---|---|---|---|
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
| Function | Signature | Description | Example |
|---|---|---|---|
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)
| Function | Signature | Description | Example |
|---|---|---|---|
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; }
Complete programs you can compile and run
#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 */
distance((0,0) to (1,1)) = 1.414214
distance((2.5,-1) to (6.5,2)) = 5.0000
#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 */
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)
#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 */
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
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 */
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.
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
The key distinction: for negative numbers, floor and trunc diverge. For positive numbers they agree (both truncate downward).
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;
}
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.
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;
}
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
Test your understanding
abs() and fabs()?LO1floor(-3.7) return?LO1atan2(y, x) solve that atan(y/x) cannot?LO1-lm when compiling a program that calls sqrt(), you will get a _______ error at link time.LO1