Every C programmer meets math.h in their first week and keeps using it for the rest of their career — and yet the two most common math.h questions on the internet are not about mathematics at all. They are “why do I get undefined reference to sqrt?” and “why did my square root come out as zero?”
This is a complete, modern reference for the C math library: every function family with prototypes and descriptions, working examples for the ones you will actually use, the C99 additions most old tutorials skip, and the handful of gotchas behind those two famous questions. Every code example here compiles clean with gcc -std=c11 -Wall -Wextra -lm and runs exactly as shown — including the error messages, which were captured from real failed builds.
Table of Contents
- What Is math.h in C?
- Compiling: Don't Forget -lm
- math.h Function Reference
- Common math.h Errors and Gotchas
- Related Headers: tgmath.h, complex.h and Friends
- Key Takeaways
- Frequently Asked Questions
- Conclusion
What Is math.h in C?
math.h is the header of the C standard library’s mathematics functions. It declares functions for powers and roots (sqrt, pow, cbrt), trigonometry (sin, cos, tan, atan2), exponentials and logarithms (exp, log, log2), rounding (floor, ceil, round, trunc), and floating-point classification (isnan, isinf) — operating on double by default, with float and long double variants.
Two facts old tutorials get wrong:
- math.h is standard C, not a compiler extra. It has been part of the language standard since C89 and works identically in GCC, Clang, MSVC, and every conforming compiler — it was never something “provided by Turbo C.”
- It grew substantially in C99. Functions like
round,trunc,cbrt,log2,hypot,fma, and the wholeisnan/isinffamily arrived in C99, along withfloat(sinf) andlong double(sinl) variants of everything. Any reference that stops at the C89 function list is describing a 35-year-old snapshot.
Compiling: Don’t Forget -lm
Before the reference — the number one math.h problem in practice. On Linux, the math functions live in a separate library (libm), so you must link it explicitly with -lm:
gcc -std=c11 -Wall -Wextra program.c -o program -lm
Forget the -lm and you get the most-searched C linker error there is:
/usr/bin/ld: in function `main':
program.c:(.text+0x23): undefined reference to `sqrt'
collect2: error: ld returned 1 exit status
Note what this is not: it is not a missing header, and no amount of #include <math.h> fixes it. The header declares the functions; -lm supplies their compiled implementations at link time. (Windows toolchains like MSVC link the math library automatically, which is why the same code “just works” there.)
math.h Function Reference
All functions below take and return double unless noted. Since C99, each also has an f suffix variant for float (sqrtf) and an l suffix variant for long double (sqrtl). If the differences between these floating-point types are fuzzy, our guide to basic data types in C covers them.
Powers and roots
| Function | Prototype | Returns |
|---|---|---|
sqrt | double sqrt(double x) | Square root of x (x ≥ 0) |
cbrt | double cbrt(double x) | Cube root of x — works for negative x too (C99) |
pow | double pow(double x, double y) | x raised to the power y |
hypot | double hypot(double x, double y) | √(x²+y²) without intermediate overflow (C99) |
#include <stdio.h>
#include <math.h>
int main(void)
{
double area = 36.0;
printf("sqrt(36.0) = %f\n", sqrt(area)); /* square root */
printf("cbrt(27.0) = %f\n", cbrt(27.0)); /* cube root */
printf("pow(2.0, 10.0) = %f\n", pow(2.0, 10.0)); /* 2 to the 10 */
printf("hypot(3.0, 4.0) = %f\n", hypot(3.0, 4.0)); /* sqrt(3*3+4*4)*/
return 0;
}
Output:
sqrt(36.0) = 6.000000
cbrt(27.0) = 3.000000
pow(2.0, 10.0) = 1024.000000
hypot(3.0, 4.0) = 5.000000
Prefer hypot(x, y) over sqrt(x*x + y*y) for distances: it gives the same answer but cannot overflow on large inputs.
Trigonometric functions
| Function | Prototype | Returns |
|---|---|---|
sin, cos, tan | double sin(double x) | Sine/cosine/tangent of x, x in radians |
asin, acos, atan | double asin(double x) | Inverse functions, result in radians |
atan2 | double atan2(double y, double x) | Angle of point (x, y) — knows the quadrant, unlike atan(y/x) |
Every trig function works in radians, not degrees — forgetting this is the classic trig bug. Convert with radians = degrees × π / 180:
#include <stdio.h>
#include <math.h>
int main(void)
{
double pi = acos(-1.0); /* portable way to get pi at full precision */
printf("sin(pi/6) = %f\n", sin(pi / 6)); /* 30 degrees -> 0.5 */
printf("cos(pi/3) = %f\n", cos(pi / 3)); /* 60 degrees -> 0.5 */
printf("tan(pi/4) = %f\n", tan(pi / 4)); /* 45 degrees -> 1.0 */
/* atan2(y, x) knows the quadrant; atan(y/x) does not */
printf("atan2(1.0, -1.0) = %f radians\n", atan2(1.0, -1.0));
/* converting degrees to radians */
double degrees = 90.0;
printf("sin(%g degrees) = %f\n", degrees, sin(degrees * pi / 180.0));
return 0;
}
Output:
sin(pi/6) = 0.500000
cos(pi/3) = 0.500000
tan(pi/4) = 1.000000
atan2(1.0, -1.0) = 2.356194 radians
sin(90 degrees) = 1.000000
Why acos(-1.0) instead of M_PI? Because M_PI is not part of standard C — it comes from POSIX. Compile with strict -std=c11 and M_PI produces error: 'M_PI' undeclared (verified); on MSVC it needs #define _USE_MATH_DEFINES. acos(-1.0) works everywhere.
The hyperbolic family follows the same naming: sinh, cosh, tanh (C89) and their inverses asinh, acosh, atanh (C99).
Exponential and logarithmic functions
| Function | Prototype | Returns |
|---|---|---|
exp | double exp(double x) | e raised to the power x |
exp2 | double exp2(double x) | 2 raised to the power x (C99) |
log | double log(double x) | Natural logarithm ln(x), x > 0 |
log10 | double log10(double x) | Base-10 logarithm |
log2 | double log2(double x) | Base-2 logarithm (C99) |
frexp | double frexp(double x, int *exp) | Splits x into mantissa × 2^exponent |
ldexp | double ldexp(double x, int exp) | Computes x × 2^exponent |
#include <stdio.h>
#include <math.h>
int main(void)
{
printf("exp(1.0) = %f (e itself)\n", exp(1.0));
printf("log(100.0) = %f (natural log, base e)\n", log(100.0));
printf("log10(100.0) = %f (base 10)\n", log10(100.0));
printf("log2(8.0) = %f (base 2, added in C99)\n", log2(8.0));
/* any other base via the change-of-base formula */
double base = 5.0, x = 125.0;
printf("log base 5 of 125 = %f\n", log(x) / log(base));
return 0;
}
Output:
exp(1.0) = 2.718282 (e itself)
log(100.0) = 4.605170 (natural log, base e)
log10(100.0) = 2.000000 (base 10)
log2(8.0) = 3.000000 (base 2, added in C99)
log base 5 of 125 = 3.000000
Rounding and remainder functions
| Function | Prototype | Behavior |
|---|---|---|
floor | double floor(double x) | Rounds toward −infinity (always down) |
ceil | double ceil(double x) | Rounds toward +infinity (always up) |
round | double round(double x) | Nearest integer; halfway cases away from zero (C99) |
trunc | double trunc(double x) | Drops the fraction — rounds toward zero (C99) |
lround | long lround(double x) | Like round, but returns a long (C99) |
modf | double modf(double x, double *ipart) | Splits x into integer and fractional parts |
fmod | double fmod(double x, double y) | Floating-point remainder of x/y, sign of x |
fabs | double fabs(double x) | Absolute value |
The four rounding functions differ only on which direction they round — and the differences only show for negative numbers and halfway cases:
#include <stdio.h>
#include <math.h>
int main(void)
{
double values[] = { 2.3, 2.7, -2.3, -2.7 };
printf("value floor ceil round trunc\n");
for (int i = 0; i < 4; i++) {
double v = values[i];
printf("%5.1f %6.1f %6.1f %6.1f %6.1f\n",
v, floor(v), ceil(v), round(v), trunc(v));
}
/* modf splits a number into integer and fractional parts */
double intpart;
double frac = modf(3.75, &intpart);
printf("\nmodf(3.75): integer part = %g, fraction = %g\n", intpart, frac);
return 0;
}
Output:
value floor ceil round trunc
2.3 2.0 3.0 2.0 2.0
2.7 2.0 3.0 3.0 2.0
-2.3 -3.0 -2.0 -2.0 -2.0
-2.7 -3.0 -2.0 -3.0 -2.0
modf(3.75): integer part = 3, fraction = 0.75
For remainders of floating-point division, use fmod — the % operator only works on integers in C (our modulus operator guide covers the integer side).
Classification macros and special values (C99)
Floating-point math doesn’t stop at ordinary numbers — division by zero, overflow, and invalid operations produce infinities and NaN (“not a number”), and C99 gave the tools to detect them:
| Macro | Returns non-zero when… |
|---|---|
isnan(x) | x is NaN |
isinf(x) | x is +∞ or −∞ |
isfinite(x) | x is an ordinary, finite number |
signbit(x) | x is negative (works even for −0.0) |
Plus the constants INFINITY, NAN, and HUGE_VAL. You will see them in action in the gotchas program below.
Common math.h Errors and Gotchas
Four mistakes account for most math.h bug reports — here they all are in one compiled program:
#include <stdio.h>
#include <stdlib.h> /* abs() lives HERE, not in math.h */
#include <math.h>
int main(void)
{
/* Gotcha 1: abs() is for ints -- fabs() is for doubles */
printf("abs(-7) = %d\n", abs(-7));
printf("fabs(-7.9) = %f\n", fabs(-7.9));
/* Gotcha 2: integer division happens BEFORE the call */
printf("sqrt(1 / 2) = %f <-- 1/2 is int division = 0\n", sqrt(1 / 2));
printf("sqrt(1.0 / 2) = %f\n", sqrt(1.0 / 2));
/* Gotcha 3: domain errors return NaN, range errors return infinity */
double bad = sqrt(-1.0);
double big = exp(1000.0);
printf("sqrt(-1.0) = %f, isnan = %d\n", bad, isnan(bad));
printf("exp(1000.0) = %f, isinf = %d\n", big, isinf(big));
/* Gotcha 4: fmod keeps the sign of the FIRST argument */
printf("fmod(5.5, 2.0) = %f\n", fmod(5.5, 2.0));
printf("fmod(-5.5, 2.0) = %f\n", fmod(-5.5, 2.0));
return 0;
}
Output:
abs(-7) = 7
fabs(-7.9) = 7.900000
sqrt(1 / 2) = 0.000000 <-- 1/2 is int division = 0
sqrt(1.0 / 2) = 0.707107
sqrt(-1.0) = -nan, isnan = 1
exp(1000.0) = inf, isinf = 1
fmod(5.5, 2.0) = 1.500000
fmod(-5.5, 2.0) = -1.500000
Unpacking each:
abs()vsfabs().abs()is declared instdlib.hand works onint— call it on a double and the value silently truncates. For floating point, usefabs()from math.h.- Integer division sneaks in before the call.
sqrt(1 / 2)computes1 / 2as integer division first — result 0 — then takes the square root of 0. Make at least one operand floating point:sqrt(1.0 / 2). - Errors come back as values. A domain error like
sqrt(-1.0)returns NaN; a range error likeexp(1000.0)returns infinity (and both seterrnotoEDOM/ERANGEon typical implementations). Check withisnan()/isinf()rather than hoping. fmodtakes its sign from the first argument —fmod(-5.5, 2.0)is −1.5, not 0.5. If that surprises you in modular arithmetic, C99’sremainder()uses a different (round-to-nearest) convention.
Related Headers: tgmath.h, complex.h and Friends
tgmath.h(C99) — type-generic math: after including it, plainsqrt(x)automatically dispatches tosqrtf,sqrt, orsqrtlbased on the argument type.complex.h(C99) — complex-number arithmetic: thedouble complextype pluscsqrt,cexp,cabs, and complex versions of the whole function set.stdlib.h— home of the integer absolute values (abs,labs,llabs) and integer division with remainder (div,ldiv).fenv.h(C99) — control of the floating-point environment: rounding modes and exception flags, for when you need to detect overflow the rigorous way.float.h— the limits of your floating types (DBL_EPSILON,DBL_MAX), essential reading once you start comparing doubles for “equality.”
Key Takeaways
math.his standard C — in every conforming compiler since C89, and substantially extended in C99 withround,trunc,cbrt,log2,hypot, theisnan/isinfclassification family, andfloat/long doublevariants of everything.- On Linux/GCC, always link with
-lm—undefined reference to sqrtmeans a missing library, not a missing header. - Trig functions work in radians; convert degrees with
deg × π / 180, and get π portably viaacos(-1.0)becauseM_PIis POSIX, not standard C. - The rounding four:
floor(down),ceil(up),round(nearest, halves away from zero),trunc(toward zero) — they only disagree on negatives and halves. abs()(stdlib.h) is for ints;fabs()(math.h) is for doubles — and watch for integer division inside call arguments.- Domain and range errors return NaN and infinity rather than crashing — check results with
isnan()andisinf().
Frequently Asked Questions
Conclusion
The pattern behind every gotcha in this reference is the same one: math.h is honest about being floating-point mathematics, not the idealized kind. Results come back as approximations, errors come back as special values, and the C type system happily performs integer division before your beautifully chosen function ever runs. Learn to read those signals — a NaN here, a suspicious 0.000000 there — and the library becomes completely predictable.
From here, the natural next step is understanding the functions you will wrap around these: parameters, return values, and how C passes data around, covered in our guide to functions in C. And when a result looks almost right instead of wrong, that is floating-point representation itself talking — a topic worth a whole article of its own.

