📚 The Standard Library
math.h & time.h: numbers and clocks
▶ Open the interactive lesson — free, no signupSquare roots and angles power game physics, and a stopwatch tells you whether your code is actually fast or just feels fast — both live in today's two headers. Along the way you'll hit a rite of passage: your first sqrt program on Linux fails with an error message that has baffled C learners for decades, and you'll be one of the few beginners who knows exactly what it means — and that the fix is three characters long.
Two headers today: math.h, which gives C real mathematical muscle, and time.h, which answers both "what time is it?" and "how long did that take?". Plus the single most-Googled error message in C history — courtesy of the linker, the build stage from Part 0 that stitches compiled pieces into a program.
The math.h roster
| family | functions | notes |
|---|---|---|
| powers | pow, sqrt, cbrt, hypot | hypot(a,b) = √(a²+b²) without overflow |
| trig | sin, cos, tan, asin, acos, atan, atan2 | radians, not degrees! atan2(y,x) knows the quadrant |
| exp / log | exp, log, log2, log10, exp2 | log is natural log (ln), not base 10 |
| rounding | floor, ceil, round, trunc | four different opinions about halves and negatives — see below |
| remainder | fmod | % for doubles: fmod(7.5, 2.0) → 1.5 |
| misc | fabs, fmin, fmax | float absolute value / min / max |
Everything takes and returns double; f-suffixed variants (sqrtf, sinf…) work in float.
#include <stdio.h>
#include <math.h>
int main(void) {
printf("pow(2,10) = %.0f\n", pow(2, 10));
printf("sqrt(2) = %.6f\n", sqrt(2));
printf("cbrt(27) = %.0f\n", cbrt(27));
printf("hypot(3,4) = %.0f\n", hypot(3, 4));
printf("sin(pi/6) = %.3f\n", sin(3.14159265358979 / 6));
printf("log2(1024) = %.0f\n", log2(1024));
printf("fmod(7.5,2) = %.1f\n", fmod(7.5, 2));
printf("floor(2.7)=%.0f ceil(2.1)=%.0f round(2.5)=%.0f\n",
floor(2.7), ceil(2.1), round(2.5));
return 0;
}$ gcc mathtour.c -o mathtour -lm && ./mathtour pow(2,10) = 1024 sqrt(2) = 1.414214 cbrt(27) = 3 hypot(3,4) = 5 sin(pi/6) = 0.500 log2(1024) = 10 fmod(7.5,2) = 1.5 floor(2.7)=2 ceil(2.1)=3 round(2.5)=3
🤔 What do floor(-2.5), ceil(-2.5), trunc(-2.5) and round(-2.5) each return?
Think first
floor(-2.5) = -3 (toward −∞) · ceil(-2.5) = -2 (toward +∞) · trunc(-2.5) = -2 (toward zero — this is what casting to int does) · round(-2.5) = -3 (halves round away from zero). Four functions, three different answers for one input — pick deliberately, especially for negative numbers.
The -lm rite of passage
On Linux, the math functions live in a separate library, libm. Including the header satisfies the compiler, but the linker still needs to be told where the code is — everyone hits this once:
$ gcc area.c -o area /usr/bin/ld: /tmp/ccXty2.o: undefined reference to `sqrt' collect2: error: ld returned 1 exit status # math.h declared sqrt, but the CODE lives in libm: $ gcc area.c -o area -lm $ ./area r = 5.64
Remember the stage model from Part 0: undefined reference is always the linker talking. The declaration (math.h) was fine; the definition lives in libm — append -lm to the command line. (Library flags go after your source files.)
🧠 Checkpoint: You included <math.h> but get undefined reference to `pow'. What’s wrong?
- A typo in the include
- The compiler is too old for pow
- The linker wasn’t told to link libm — add
-lm - pow needs C23
Show answer
The linker wasn’t told to link libm — add -lm — Headers only carry declarations. The implementation of the math functions is in a separate library on Linux, so the link step needs -lm after your source files. (Some calls with constant args get computed at compile time, which is why the error can appear "randomly".)
NaN, infinity & comparing floats
Floating-point math never crashes — it produces special values instead (Part 0 flashbacks): 1.0/0.0 gives INFINITY, sqrt(-1) gives NAN. Since NaN isn't equal to anything — including itself — you must test with isnan() and isinf(), never ==:
#include <stdio.h>
#include <math.h>
int main(void) {
double inf = 1.0 / 0.0; /* no crash — infinity */
double nan = sqrt(-1.0); /* domain error — NaN */
printf("inf = %f, nan = %f\n", inf, nan);
printf("nan == nan -> %d\n", nan == nan); /* false! */
printf("isnan(nan) -> %d\n", isnan(nan));
printf("isinf(inf) -> %d\n", isinf(inf));
printf("isfinite(1.) -> %d\n", isfinite(1.0));
double a = 0.1 + 0.2;
printf("a == 0.3 -> %d\n", a == 0.3);
printf("tolerance check -> %d\n", fabs(a - 0.3) < 1e-9);
return 0;
}$ gcc special.c -o special -lm && ./special inf = inf, nan = -nan nan == nan -> 0 isnan(nan) -> 1 isinf(inf) -> 1 isfinite(1.) -> 1 a == 0.3 -> 0 tolerance check -> 1
And the golden rule stands: compare computed floats with a tolerance, e.g. fabs(a - b) < 1e-9, or relative to magnitude with DBL_EPSILON from float.h (next lesson digs into that header).
🧠 Checkpoint: Which expression reliably detects that x is NaN?
x == NANx != xx == 0.0/0.0x > INFINITY
Show answer
x != x — NaN is the only value not equal to itself, so x != x is true exactly for NaNs — that’s essentially how isnan() works. x == NAN is always false for the same reason. Prefer the readable isnan(x).
time.h: wall clocks
time(NULL) returns a time_t — on virtually every platform, seconds since the Unix epoch (Jan 1, 1970 UTC), though the standard only promises "some encoding"; portable code compares moments with difftime(t2, t1). To get human-readable parts, expand a time_t into a struct tm with localtime, then format it with strftime:
#include <stdio.h>
#include <time.h>
int main(void) {
time_t now = time(NULL); /* seconds since epoch */
printf("raw time_t : %lld\n", (long long)now);
struct tm *t = localtime(&now); /* explode into fields */
printf("year %d, month %d, day %d\n",
t->tm_year + 1900, /* years since 1900! */
t->tm_mon + 1, /* 0-based months! */
t->tm_mday);
char buf[64];
strftime(buf, sizeof buf, "%A %Y-%m-%d %H:%M:%S", t);
printf("formatted : %s\n", buf);
return 0;
}$ gcc today.c -o today && ./today raw time_t : 1754006400 year 2025, month 8, day 1 formatted : Friday 2025-08-01 02:00:00
struct tm quirks that ruin demos: tm_year is years since 1900, and tm_mon is 0-based (January = 0). Forget those and your program prints the year 126 or the wrong month. Also localtime returns a pointer to shared static storage — copy the struct if you need two at once.
time.h: stopwatches
For benchmarking, wall time is the wrong tool (other processes pollute it). clock() measures CPU time consumed by your process, in ticks of CLOCKS_PER_SEC:
#include <stdio.h>
#include <time.h>
int main(void) {
clock_t start = clock();
volatile double sum = 0; /* volatile: don't optimize away */
for (long i = 1; i <= 50000000L; i++)
sum += 1.0 / i;
clock_t end = clock();
double secs = (double)(end - start) / CLOCKS_PER_SEC;
printf("harmonic sum = %.6f\n", sum);
printf("CPU time = %.3f s\n", secs);
return 0;
}$ gcc -O2 bench.c -o bench && ./bench harmonic sum = 18.304749 CPU time = 0.184 s
C11 added timespec_get(&ts, TIME_UTC), which fills a struct timespec with seconds and nanoseconds — the portable way to get sub-second wall-clock timestamps.
🧠 Checkpoint: To measure how long your code took to compute, regardless of other programs hogging the machine, use…
time(NULL)before and afterclock()before and after, divided by CLOCKS_PER_SECstrftimedifftimeon two time_t values
Show answer
clock() before and after, divided by CLOCKS_PER_SEC — clock() counts CPU time your process actually consumed; wall-clock time (time/difftime) includes everything else running. For sub-second wall timestamps, C11’s timespec_get gives nanosecond resolution.
▶ This spot has an interactive editor widget — open the interactive lesson to play with it.
Next up: three small headers with outsized importance — character tests, assertions, and the errno error-reporting convention.
▶ Practice this lesson interactively (with live gcc)