🔧 C Programming · Chapter 3
Operators, integer division and overflow
In C, 7 / 2 is 3, -1 is not less than 1u, and 2.5 billion fits in nothing you declared. Arithmetic does exactly what the types say — not what you meant.
Study this lesson free in the app →
The basics
Operators are + - * / %, comparisons == != < >, logic && || !, and assignment = += ++. In C, every one of them works on the types of its operands, and the result has a type too.
The problem it solves: knowing the rules is how you stop "my average is always a whole number" and "the total went negative" bugs before they ship.
The mental model: the CPU does integer maths and floating-point maths with different instructions. C picks one based on the types:
int / int→ integer division: the fraction is thrown away (truncated toward zero)- if either side is
double→ real division - a value too big for its type overflows. Unsigned wraps around like an odometer. Signed overflow is undefined behaviour: anything may happen.
Words you must know 4
- truncation
- dropping the fractional part: 3.9 → 3, −3.9 → −3
- cast
(double)xconverts a value to another type- overflow
- a result outside the type's range
- precedence
- which operator binds first (
*before+,==before&)
Explained
int done = 7, total = 2;
double avg = done / total; // 3.0, not 3.5The compiler didn't make a mistake. It did integer division because both sides were int, then stored the 3 in a double. Every C programmer writes this bug once. This lesson makes sure it's in practice, not in production.
Sharing laddoos
7 laddoos, 2 children, and a strict rule: no breaking laddoos.
Each child gets 3, and 1 is left on the plate. That's 7 / 2 == 3 and 7 % 2 == 1: integer division and remainder. Division answers "how many whole ones each"; % answers "what's left".
If you're allowed to break them (a knife = double), each gets 3.5.
And the odometer on an old Maruti: at 99999 km, one more km shows 00000. That's unsigned overflow: it wraps. C's signed integers don't promise even that much.
The rules, precisely
Division and remainder (integers) | Expression | Result | Why | | --- | --- | --- | | 7 / 2 | 3 | fraction dropped | | -7 / 2 | -3 | truncates toward zero (since C99) | | -7 % 2 | -1 | sign follows the left operand | | 7.0 / 2 | 3.5 | one double → real division | | (double)7 / 2 | 3.5 | cast first, then divide | | (double)(7 / 2) | 3.0 | divide first (3), then cast — too late |
Integer division by zero (x / 0, x % 0) is undefined behaviour. On Linux it usually crashes with "Floating point exception".
Ranges (<limits.h>): INT_MAX = 2,147,483,647 · UINT_MAX = 4,294,967,295 · LLONG_MAX ≈ 9.22 × 10¹⁸.
Diwali night, 11:40 PM
A Bengaluru ride app sums the day's fares in paise into an int:
- 1,00,000 rides × ₹250 average = ₹2.5 crore
- in paise: 2,500,000,000
INT_MAXis 2,147,483,647
Around ride 85,900 the running total passes INT_MAX. Signed overflow is undefined behaviour. In practice on x86 it usually wraps to about −2.1 billion, and the finance dashboard shows a negative day.
Fix: int64_t total (max ≈ 9.2 × 10¹⁸). And watch for the hidden version: int a = 50000, b = 50000; long long c = a * b; still overflows, because a * b is computed in int before it is stored. Write (long long)a * b.
Unsigned wrap, and the comparison trap
#include <stdio.h>
#include <limits.h>
int main(void) {
unsigned int u = 0;
u = u - 1;
printf("%u\n", u); // 4294967295 — wrapped, well-defined
unsigned int max = UINT_MAX;
printf("%u\n", max + 1); // 0
int i = -1;
unsigned int one = 1;
if (i < one) printf("less\n");
else printf("NOT less\n"); // prints NOT less
return 0;
}When a signed and an unsigned int meet, the signed one is converted to unsigned. −1 becomes 4,294,967,295, which is not less than 1. sizeof returns unsigned too, so if (-1 < sizeof(int)) is false. -Wall -Wextra warns: comparison of integer expressions of different signedness.
Precedence ambush
int x = 6;
if (x & 1 == 0)
printf("even\n");
else
printf("odd\n");Gotchas
if (x = 5)assigns 5 and is always true. You meant==.-Wallwarns.- Integer division in an average or a percentage.
done * 100 / totalis fine;done / total * 100is 0 whenever done < total. - Overflow before widening.
long long c = a * b;multiplies in int first. Cast one operand. - Mixing signed and unsigned in comparisons, especially with
sizeofandstrlen, which return unsignedsize_t. i = i++;ora[i] = i++;modify and read the same variable without a sequence point: undefined behaviour. Use++as a statement on its own.&&/||short-circuit.if (n != 0 && sum / n > 100)is safe because the right side is skipped when n is 0. Swap the order and it crashes.
The types decide the maths, and the right side is computed before it is stored.
int / int drops the fraction. Signed overflow is undefined behaviour; unsigned wraps. Signed meets unsigned → signed becomes unsigned. Parenthesise bitwise expressions.
Words to know
| Integer division | Division of two integers; the fractional part is discarded (truncated toward zero). |
|---|---|
| Modulo / remainder (%) | What's left after integer division. Its sign follows the left operand in C. |
| Cast | An explicit conversion written (type)value, e.g. (double)total. |
| Overflow | A result outside the range of its type. Wraps for unsigned; undefined behaviour for signed. |
| Usual arithmetic conversions | The rules that bring two operands to a common type before an operation, e.g. int + double → double. |
| Precedence | Which operator applies first when there are no parentheses. |
| Short-circuit evaluation | && stops at the first false, || at the first true; the rest isn't evaluated. |
In two minutes
- Operators work on the types of their operands; int / int is integer division that truncates toward zero.
- The remainder % takes the sign of the left operand: -7 % 2 == -1.
- Cast before dividing, (double)a / b, to get a real result. Casting after is too late.
- Signed overflow is undefined behaviour; unsigned overflow wraps modulo 2ⁿ.
- The right-hand side is computed in its own type before being stored, so widen before multiplying.
- When signed meets unsigned of the same rank, the signed value converts to unsigned, so -1 < 1u is false.
- == binds tighter than &, |, ^, so always parenthesise bitwise expressions.
- && and || short-circuit, and you can rely on it for guards like n != 0 && sum / n.
Test yourself
1. What is 17 / 5 in C (both int)?
- 3.4
- 3
- 4
- 3.0
Show the answer
3 — int / int is integer division: the 0.4 is discarded. It doesn't round, so it's not 4.
2. What is -17 % 5?
- 3
- -2
- 2
- -3
Show the answer
-2 — -17 / 5 truncates toward zero to -3; -3 × 5 = -15; -17 - (-15) = -2. In C the remainder takes the sign of the left operand. Python's -17 % 5 would be 3.
3. int a = 3, b = 4; double r = a / b; What is r?
- 0.75
- 0.0
- 1.0
- Compile error
Show the answer
0.0 — a / b is computed as int (0) first, then converted to 0.0. Write (double)a / b.
4. Which correctly computes a percentage of completed rides as a double?
- (double)(done / total) * 100
- done / total * 100.0
- 100.0 * done / total
- (int)done / total * 100
Show the answer
100.0 * done / total — 100.0 * done is double first, so the division is real. The first two still do int / int before anything becomes double.
5. unsigned int u = 0; u--; What is u (32-bit unsigned)?
- -1
- 0
- 4294967295
- Undefined behaviour
Show the answer
4294967295 — Unsigned arithmetic is defined to wrap modulo 2³², so 0 - 1 = UINT_MAX. Only signed overflow is undefined behaviour.
6. int x = INT_MAX; x = x + 1; What does the C standard say?
- x becomes INT_MIN
- x becomes 0
- Undefined behaviour
- Compile error
Show the answer
Undefined behaviour — Signed overflow is undefined behaviour. It often wraps to INT_MIN in practice, but optimisers may assume it never happens and remove your overflow checks, so you can't rely on it.
There are 10 questions, 3 puzzles and 5 recall cards for this lesson in the app — the recall cards come back on later days, just before you would forget.
Start this lesson →