Intuition What this page is
The parent note taught you what each operator returns . Now we drill every case that can bite you : positive vs negative division, zero and degenerate inputs, sign-preserving right shifts, short-circuit skips, precedence traps, and a real word problem. First we lay out the full matrix, then we hit every cell with a worked example.
Definition A highlighting convention used on this page
Whenever you see a term wrapped like this , it is a key term being defined right there — the double-equals is just a study highlight, nothing to do with the C equality operator. The C operator is always shown in code font as == (inside backticks). So truncating division is a highlighted term ; a == b is a comparison in C code .
Each row is a "case class" this topic can throw at you. The last column names the example that covers it.
#
Case class
The tricky part
Covered by
A
/ and % both operands positive
truncation toward zero
Ex 1
B
/ and % with a negative dividend
sign follows dividend
Ex 2
C
negative divisor
sign still follows dividend, not divisor
Ex 2
D
integer division by zero (degenerate)
undefined behaviour — must not run
Ex 3
E
int / vs float / (type-driven)
one double promotes everything
Ex 3
E2
float division by zero
gives ±Infinity/NaN, not UB
Ex 3
F
Bitwise & | ^ bit-by-bit
which bits survive
Ex 4
G
Left shift & signed right shift
arithmetic vs logical shift
Ex 5
G2
shift UB : negative / by too-large count / odd negatives
out-of-range and rounding pitfalls
Ex 5
H
Short-circuit &&/|| skipping side effects
which code never runs
Ex 6
I
Precedence trap a & b == c
== beats &
Ex 7
J
Assignment chain + compound +=
right-associativity, evaluate-once
Ex 8
K
Comma operator value & sequence point
value is the last operand
Ex 9
L
Real-world word problem
pick the right operator
Ex 10
Cells A–L are all hit below. Ranges and prerequisites link out to Data Types and Type Promotion , Two's Complement Representation , Sequence Points and Undefined Behavior , and Bit Masking Techniques .
Evaluate 17 / 5 and 17 % 5 in C, both int.
Forecast: guess the quotient and remainder before reading on.
Divide, throwing away the fraction. 17/5 = 3.4 , but integer / truncates toward zero, so 17 / 5 = 3.
Why this step? Both operands are int, so C does truncating division — no fraction survives.
Recover the remainder from the identity a % b = a − ( a / b ) ⋅ b . So 17 % 5 = 17 − 3 ⋅ 5 = 17 − 15 = 2 .
Why this step? % must hold exactly what / chopped off, so a=(a/b)b+(a\%b) reconstructs a.
Verify: 3 ⋅ 5 + 2 = 17 ✓. Both operands positive → remainder is positive, as expected.
Evaluate -7 / 2, -7 % 2, then 7 / -2 and 7 % -2 (C99).
Forecast: which of the four remainders are negative?
-7 / 2: − 7/2 = − 3.5 ; truncating toward zero gives -3 (not -4).
Why this step? Since C99, / rounds toward zero, never toward − ∞ .
-7 % 2 = − 7 − ( − 3 ) ⋅ 2 = − 7 + 6 = − 1 .
Why this step? Sign of % follows the dividend -7, so the remainder is negative.
7 / -2: 7/ − 2 = − 3.5 → truncate toward zero → -3.
Why this step? Same rounding rule regardless of which operand is negative.
7 % -2 = 7 − ( − 3 ) ⋅ ( − 2 ) = 7 − 6 = 1 .
Why this step? Dividend is +7 → remainder is positive , even though the divisor is negative. The divisor's sign never touches %.
Verify: ( − 3 ) ⋅ 2 + ( − 1 ) = − 7 ✓ and ( − 3 ) ⋅ ( − 2 ) + 1 = 7 ✓.
The number line below shows why step 1 lands on -3 and not -4: the true value -3.5 sits between them, and the yellow arrow points toward zero (rightward), so it snaps to -3. That single choice of rounding direction is what forces -7 % 2 = -1.
Common mistake Steel-man: "
-7 % 2 should be 1"
Why it feels right: in maths, "remainder mod 2" is usually taken as 0 or 1 .
The bug: C's % is not mathematical modulo; it is the remainder of truncating division, so it can be negative. Here it is -1, not 1.
Discuss 10 / 0 and 10.0 / 0.0, then evaluate 10 / 4 vs 10 / 4.0 vs (double)(10 / 4).
Forecast: which of these crash, which give Infinity, and which give 2.5?
10 / 0 (integer): dividing an int by zero is undefined behaviour (UB).
Why this step? There is no integer answer, so the C standard makes it UB — the program may crash or produce garbage. See Sequence Points and Undefined Behavior . You must guard with if (b != 0) first.
10.0 / 0.0 (floating): this is not UB. Under the usual IEEE-754 floating-point rules, 10.0 / 0.0 gives +Infinity, -10.0 / 0.0 gives -Infinity, and 0.0 / 0.0 gives NaN ("not a number").
Why this step? Float hardware has special bit patterns for infinity and NaN, so the operation is defined — but the result poisons later maths (anything with NaN stays NaN), so you still usually guard against a zero divisor.
10 / 4: both int → integer division → 2.
Why this step? / looks at operand types ; two ints ⇒ fraction discarded.
10 / 4.0: 4.0 is double, so 10 is promoted to 10.0, real division → 2.5.
Why this step? One floating operand promotes the whole expression — see Data Types and Type Promotion .
(double)(10 / 4): the parentheses force 10 / 4 = 2 first (still int), then cast → 2.0.
Why this step? The cast happens after integer division already destroyed the fraction. Casting too late does nothing.
Verify: integer 10/0 = UB (never trust it); 10.0/0.0 = +Infinity; 10/4 = 2, 10/4.0 = 2.5, (double)(10/4) = 2.0. Only step 4 recovers 2.5 ✓.
Common mistake Steel-man: "division by zero always crashes"
Why it feels right: integer /0 really is UB and often crashes.
The bug: for floating-point operands, x / 0.0 is well-defined as ±Infinity (or NaN for 0.0/0.0) — no crash, but a silently poisoned value. Different operator behaviour for the same-looking zero divisor.
With a = 0x2C (44) and b = 0x1A (26), compute a & b, a | b, a ^ b (8-bit).
Forecast: guess the decimal answers.
Write both in binary. 44 = 0010 1100 , 26 = 0001 1010 .
Why this step? Bitwise ops act column-by-column, so you must see the bits. (Uses Two's Complement Representation for storage, but these are positive.)
AND — a 1 only where both have 1: 0010 1100 & 0001 1010 = 0000 1000 = 8 .
Why this step? & keeps common bits — the mask intersection (Bit Masking Techniques ).
OR — a 1 where either has 1: 0011 1110 = 62 .
Why this step? | unions the set bits.
XOR — a 1 only where they differ : 0011 0110 = 54 .
Why this step? ^ marks disagreement, column by column.
Verify: 44 & 26 = 8, 44 | 26 = 62, 44 ^ 26 = 54. Sanity: for any bit, (AND)+(XOR bits) reconstructs OR ✓.
Read the diagram column by column: each of the four rows is one 8-bit number. In the AND (yellow) row a box is filled only where both the 44 and 26 boxes above it were filled; in the XOR (pink) row a box is filled only where the two disagree. Counting the filled place-values gives 8 and 54.
Compute 3 << 4, 240 >> 3 (unsigned view), -8 >> 1 and -3 >> 1 (signed). Then list which shifts are undefined behaviour .
Forecast: does -8 >> 1 give -4 or a huge positive number? And does -3 >> 1 give -1 or -2?
3 << 4 = 3 ⋅ 2 4 = 3 ⋅ 16 = 48 .
Why this step? Left shift by n multiplies by 2 n — every bit slides up one place = ×2, done 4 times.
240 >> 3 = ⌊ 240/ 2 3 ⌋ = ⌊ 240/8 ⌋ = 30 .
Why this step? Right shift by n on an unsigned value is floor-division by 2 n .
-8 >> 1: on almost all real C compilers, right-shifting a negative int does an arithmetic shift — it copies the sign bit in from the left, so -8 >> 1 = -4.
Why this step? Keeping the sign bit makes >> 1 behave like floor-division by 2: ⌊ − 8/2 ⌋ = − 4 .
-3 >> 1 (odd negative): arithmetic shift floors, and ⌊ − 3/2 ⌋ = ⌊ − 1.5 ⌋ = − 2 , so -3 >> 1 = -2 — not -1.
Why this step? Flooring rounds toward − ∞ , so an odd negative rounds down , unlike integer / which would truncate -3/2 toward zero to -1. So >> 1 and / 2 disagree for negative odd numbers!
Verify: 3 ⋅ 16 = 48 ✓, 240/8 = 30 ✓, ⌊ − 8/2 ⌋ = − 4 ✓, ⌊ − 3/2 ⌋ = − 2 ✓.
int i = 0 ;
int r = ( 0 && (i = 99 )) ; // case 1
int s = ( 1 || (i = 77 )) ; // case 2
What are r, s, and i afterward?
Forecast: does i ever change?
Case 1, 0 && (i = 99): left side is 0 (false), so && already knows the answer is false and never evaluates the right side .
Why this step? Once one operand of AND is false, the result is fixed → C skips the rest.
So r = 0 and the assignment i = 99 never runs → i stays 0.
Case 2, 1 || (i = 77): left side is 1 (true), so || already knows the answer is true and skips the right side .
Why this step? Once one operand of OR is true, the result is fixed.
So s = 1 and i = 77 never runs → i still 0.
Verify: r = 0, s = 1, i = 0. The skipped assignments are exactly why short-circuit lets you write if (p != NULL && p->value > 0) safely — see Control Flow — if and loops .
With a = 6, b = 4, c = 4, evaluate a & b == c. Then fix it to mean "(a & b) equals c".
Forecast: most people expect (6 & 4) == 4, i.e. 1. Is that what C computes?
Check the precedence table: == sits above &. So C reads a & (b == c), not (a & b) == c.
Why this step? Bitwise & ^ | are famously below relational/equality — the parent's "20% that causes 80% of bugs".
Evaluate b == c: 4 == 4 → 1.
Evaluate a & 1: 6 = 0000\,0110, 1 = 0000\,0001; AND → 0000\,0000 = 0. So the whole thing is 0.
Fix with parentheses: (a & b) == c → 6 & 4 = 0000\,0100 = 4, and 4 == 4 → 1.
Why this step? Parentheses force the intended grouping; the parent's rule is "when in doubt, add parentheses."
Verify: unparenthesized 6 & 4 == 4 = 0; parenthesized (6 & 4) == 4 = 1. They differ — precedence really bit us ✓.
The precedence diagram makes the trap visual: == sits on a higher rung than &, so C reaches down and evaluates b == c first, feeding the 1 it produces into the &. The parentheses in the fix pull a & b back up above ==.
int a, b, c;
a = b = c = 5 ;
a += b += c;
Final values of a, b, c?
Forecast: guess all three after line 2.
a = b = c = 5: = is right-associative (groups from the right), so this is a = (b = (c = 5)). c becomes 5, that value 5 feeds b, then 5 feeds a.
Why this step? Each = returns the value it stored, passing it leftward.
After line 1: a=5, b=5, c=5.
a += b += c: again right-associative → a += (b += c). First b += c means b = b + c = 5 + 5 = 10, and this expression's value is 10.
Why this step? x += y is x = x + y, evaluating x once , and the whole thing has the stored value.
Then a += 10 means a = a + 10 = 5 + 10 = 15.
Verify: final a = 15, b = 10, c = 5. Cross-check: a = 5 + (5+5) = 15 ✓.
Evaluate x = (1+1, 2+2, 3+3); and y = (a=4, a+1); (start a undefined).
Forecast: what does x become — 6 or something else?
(1+1, 2+2, 3+3): the comma operator evaluates each part left to right, discarding all but the last . 1+1 and 2+2 are computed then thrown away; the whole expression's value is 3+3 = 6.
Why this step? The comma operator returns only its last operand.
So x = 6.
(a = 4, a + 1): first a = 4 runs, and the comma is a sequence point (a guaranteed "finish this before starting the next" boundary) so a is fully 4 before the next part. Then a + 1 = 5 is the value.
Why this step? The sequence point guarantees the assignment finishes first — no undefined behaviour (Sequence Points and Undefined Behavior ).
So y = 5 and a = 4.
Verify: x = 6, y = 5, a = 4 ✓. (Note: in f(1, 2) the commas are argument separators, not this operator.)
You have total = 100 cookies and kids = 7. Each kid gets an equal whole number of cookies; leftovers go in a jar. How many does each kid get, and how many land in the jar? Then: is the leftover count even ?
Forecast: guess per-kid and jar counts.
Per kid = total / kids = 100 / 7 = 14 (integer division discards the fraction).
Why this step? You can't hand out a fraction of a cookie, so truncating / is exactly the right tool.
Jar = total % kids = 100 % 7. Using the identity: 100 − 14 ⋅ 7 = 100 − 98 = 2 .
Why this step? % gives the leftover after equal sharing — the "pizza slices left over" operator.
Is the leftover even? Test the lowest bit: (jar & 1) == 0. Here 2 = 0000\,0010, 2 & 1 = 0, so (2 & 1) == 0 is true → even .
Why this step? A number is even exactly when its bit-0 is 0; & 1 isolates that bit (Bit Masking Techniques ).
Verify: 14 ⋅ 7 + 2 = 100 ✓, and 2 & 1 = 0 → even ✓. Units: cookies/kid × kids + jar = total cookies ✓.
Recall Quick self-test
-9 / 4 and -9 % 4 in C99? ::: -9/4 = -2 (truncate toward zero); -9 % 4 = -9 - (-2)*4 = -1.
a & b == c with a=6,b=4,c=4 — value and why? ::: 0; == binds tighter than &, so it's 6 & (4==4) = 6 & 1 = 0.
x = (1, 2, 3); — value of x? ::: 3; comma operator yields its last operand.
1 || (i = 5) — does i change? ::: No; || short-circuits after the true left side, so the assignment never runs.
-3 >> 1 vs -3 / 2? ::: -3 >> 1 = -2 (arithmetic shift floors toward -inf); -3 / 2 = -1 (truncates toward zero) — they disagree for odd negatives.
10.0 / 0.0 in C — crash? ::: No; floating divide by zero gives +Infinity (and 0.0/0.0 gives NaN). Only integer /0 is undefined behaviour.
Mnemonic Sign & skip rules
% wears the dividend's sign. AND quits on the first FALSE, OR quits on the first TRUE. Bitwise sits BELOW equality — parenthesize. Shift only unsigned, only by an in-range count.