Week 13 · Phase 2 — The Ancestry
The math behind the machine — and why 5 / 2 is sometimes 2 and sometimes 2.5.
Photo · Harshil Prajapati / Unsplash
Variables sit on the counter. Operators are how you do things to them — add, multiply, compare, manipulate. C has roughly thirty operators, and almost every one of them survived, unchanged, into every modern language. + for plus. % for modulo. && for AND. ?: for "if-this-then-that-else-the-other". You'll meet them again in JavaScript, Java, Python, Swift, Go, Rust, Kotlin — same symbols, same meanings.
The operators themselves are the easy part. The interesting part is the traps: integer overflow, integer-vs-float division, operator precedence, the difference between = and ==. We'll cover the operators, then we'll cover the traps. The traps cause more bugs than the operators do work.
The math you already know — but on integers, with one twist (%).
Each one returns 1 if true, 0 if false.
5 / 2 isn't 2.5This is the single most common beginner C bug.
int a = 5;
int b = 2;
double avg = a / b; // avg is 2.0, NOT 2.5 !
Why? Because a and b are both int. C looks at the operator /, looks at its two operands, and picks integer division — which throws away the fractional part. The result is 2. Only then does that 2 get converted to a double (2.0) for assignment.
The fix: tell the compiler that at least one operand is floating-point.
double avg = (double)a / b; // 2.5 ✓
// or
double avg2 = a / 2.0; // 2.5 ✓
Once one side is floating-point, the other is converted up, and you get real division.
What happens when an int can't hold the result?
int x = 2147483647; // the largest 32-bit signed int
x += 1;
printf("%d\n", x); // prints -2147483648 (!!)
Adding 1 to the maximum signed 32-bit integer doesn't give you the next number — it wraps around to the most negative value the type can hold. The chef does it silently. There's no exception, no warning, just a number that has slipped through the floor of the world.
This is one of the most consequential properties of C. Many real-world security bugs (Heartbleed-class, integer-overflow-into-buffer-overflow vulnerabilities) start exactly here. Modern C codebases use -fsanitize=undefined in development builds to catch this; production C uses smaller types defensively or wraps risky math in checked-arithmetic helpers. Higher-level languages (Python, Swift) detect overflow automatically — and pay a runtime cost for doing so.
For floating-point, a similar but gentler thing happens: numbers don't wrap, but precision is lost silently. 0.1 + 0.2 in any IEEE-754 language gives 0.30000000000000004. This is not a C bug; it's how binary floating-point works.
= vs ==One equals sign means assign. Two means compare. C will helpfully let you confuse them and silently turn a comparison into an assignment.
if (x = 5) { // always true! x is now 5
printf("yes!\n");
}
That code compiles, runs, and prints "yes!" every time. The condition x = 5 is an assignment — its value is 5, which is non-zero, which counts as "true". Modern compilers warn (-Wall will catch it: "suggest parentheses around assignment"), but they only warn — they don't refuse.
Many C codebases adopt the "Yoda condition" style to make this impossible: if (5 == x). You can't accidentally assign 5 to a literal, so the compiler refuses. Some find Yoda style ugly. Both sides have a point.
C has rules about which operator binds tightest. They mostly match school algebra: * and / bind tighter than + and -. So 2 + 3 * 4 is 14, not 20. So far, so familiar.
But there are corners that surprise everyone, even experienced C programmers:
& and | (bitwise) bind looser than == and !=. So x & 1 == 0 is parsed as x & (1 == 0) = x & 0 = 0. Probably not what you wanted. Always parenthesise: (x & 1) == 0.&& binds tighter than ||. So a || b && c means a || (b && c).?: binds very loosely. a + b ? c : d is (a + b) ? c : d, which works, but parenthesise anyway because future-you will not remember.The professional rule: when in doubt, parenthesise. Compilers don't charge per parenthesis. Readers do, in cognitive load when they're not there.
The logical operators && and || have a useful, slightly magic property. C evaluates the left side first; if the answer is already determined, the right side is never evaluated.
// safe pointer check — short circuits if p is NULL:
if (p != NULL && p->value == 42) { /* ... */ }
If p is null, p != NULL is false, and the &&'s right side never runs — so the dangerous dereference p->value never happens. Without short-circuiting, you'd crash. This idiom is everywhere in real C; it's also why "always evaluate both sides" is a real, occasionally annoying, feature of some languages and not C.
Every neural-network training loop does, somewhere down the bottom of its inner loop, weight = weight - learning_rate * gradient. That single line, repeated trillions of times, is what training is. Floating-point precision matters here: float's 7 significant decimal digits is sometimes too coarse, which is why training uses tricks like "mixed precision" (FP16 for speed, FP32 for accumulators) and "loss scaling" (multiply gradients by 1024 before storing them, divide back later — to keep the small values in range).
And every time you've heard of an LLM "going crazy with NaNs" — somebody hit floating-point overflow or underflow in a mathematical operation that should have been bounded. The chef did exactly what was asked, with no safety net. Welcome to numerical computing.
Operators are simple. The numbers they touch are surprising. The bugs are at the boundary.
int / int and int / 2.0). Confirm the difference.int, set it to 2147483647, add 1, print. Watch it wrap.n % 2 == 0 tells you whether n is even.-Wall -Wextra -fsanitize=undefined on Linux/macOS. Re-run the overflow test. The sanitizer will catch and report it at runtime — that's how production code stays safe.0.1 + 0.2 == 0.3. False. Welcome to floating-point.Now we can compute things. Next week we make the computer decide: do this if that's true, otherwise do this other thing. Three lines from here, you'll have built the conceptual atom of every algorithm ever written.
Week 14 is The Fork in the Road — if, else, and the boolean logic at the heart of every program.
Photo free under the Unsplash license. Calculator · Harshil Prajapati.