C Operators — How Integer Division Cost a Bank
Integer division in C truncates toward zero — a banking app silently underpaid 12,000 accounts.
20+ years shipping performance-critical C and C++ systems. Notes here come from systems that actually shipped.
- Arithmetic operators (+, -, *, /, %) perform math — integer division truncates decimals silently, and the sign of % follows the dividend
- Relational (==, !=, >, <, >=, <=) compare values and return 1 (true) or 0 (false)
- Logical (&&, ||, !) combine conditions with short-circuit evaluation for efficiency
- Assignment (=, +=, -=, etc.) store values — prefix ++ versus postfix differs in expression result, and mixing them in a single expression is undefined behaviour
- Bitwise (&, |, ^, ~, <<, >>) manipulate bits directly, used for flags and fast multiply/divide by powers of 2 — only safe on unsigned integers for shift and complement
- Biggest mistake: using = in conditions (if (x = 5)) — compiles fine, always true, and corrupts x
Think of operators like the buttons on a calculator. The numbers are your data, and the buttons (+, -, ×, ÷) tell the calculator what to do with those numbers. In C, operators are the symbols that tell your program how to work with values — add them, compare them, combine them, or flip them. Without operators, your program would just be a list of numbers sitting there doing nothing. The tricky part is that C's operators are closer to the hardware than most languages, which means they are fast, powerful, and occasionally surprising if you do not know the rules.
Every useful program on the planet — from a banking app calculating your balance to a game engine deciding if a bullet hit a target — makes decisions and performs calculations. None of that is possible without operators. They are the verbs of your code. If variables are the nouns, operators are the actions performed on those things. Understanding them deeply is not optional — it's the foundation everything else is built on.
Before operators existed in programming languages, writing even simple math required multiple low-level machine instructions. C gave programmers a concise, expressive set of symbols that map closely to what the hardware actually does — which is why C is still used in embedded systems, operating systems, and performance-critical software today. Operators solve the problem of expressing computation, comparison, and logic in a way that is both human-readable and highly efficient.
By the end of this article you'll know every major category of C operator, understand exactly what each one does and why it exists, be able to read real C code without getting tripped up by symbols like >>= or !=, know the silent rules around integer division, negative modulus, and undefined behaviour with increment operators, and you'll know the most common operator mistakes beginners make — so you can avoid them from day one.
Why Integer Division Is Not Your Friend
C operators are symbols that tell the compiler to perform specific mathematical, relational, or logical manipulations on data. At their core, they map directly to CPU instructions — no abstraction layer, no safety net. The critical distinction from higher-level languages is that C operators operate on raw memory values with fixed bit widths, and integer division truncates toward zero, discarding the fractional part entirely.
When you write a / b in C with both operands as integers, the result is an integer. There is no implicit promotion to floating-point. This means 5 / 2 yields 2, not 2.5. The remainder is computed separately with the % operator. This behavior is defined by the C standard (C99 onward) as truncation toward zero — positive results round down, negative results round up. The same rule applies to the modulo operator: -5 % 2 gives -1, not 1.
Use integer division when you explicitly need whole-number results — counting items, indexing arrays, or computing discrete quantities. Never use it for financial calculations, percentages, or any operation where fractional precision matters. The infamous 1994 Pentium FDIV bug aside, integer division in C has silently cost banks millions: a currency conversion that truncated cents instead of rounding them, applied across millions of transactions, produces a systematic loss that only shows up in audit.
Arithmetic Operators — Your Program's Built-In Calculator
Arithmetic operators do exactly what the name suggests: math. C gives you six of them — addition (+), subtraction (-), multiplication (*), division (/), modulus (%), and unary negation (-). You already know the first four from school. The ones that surprise beginners are modulus and integer division.
Modulus (%) gives you the remainder after integer division. So 10 % 3 gives you 1, because 10 divided by 3 is 3 with 1 left over. This is useful in real code for checking if a number is even or odd, wrapping a value around a range (keeping a game character on screen, cycling through a circular buffer), or extracting digits from a number. However, there is a rule that catches people: in C99 and later, the sign of the modulus result follows the sign of the dividend, not the divisor. So -7 % 3 gives -1, not 2. If you need a guaranteed non-negative remainder, use the safe idiom: ((n % m) + m) % m.
The bigger trap is integer division. When you divide two integers in C, you get integer division — the decimal part is thrown away, not rounded, just discarded silently. 7 / 2 gives 3. There is no warning, no error, no indication that anything was lost. If you need the decimal result, at least one of the operands must be a float or double before the division happens — casting after the fact is too late, the truncation has already occurred.
Here's a real-world example from a payment system: the code computed int cents_per_user = total_cents / user_count;. Because both operands were int, the ten-thousandths of a cent were silently dropped on every transaction. After 50,000 transactions, the cumulative error exceeded $10. The fix was a single cast to double. Always cast before division, not after.
C Operators Quick Reference Tables
Below are compact tables for every major operator category in C. Each table lists the operator symbol, its name or description, a short explanation, and a simple code example. Use these as a quick lookup when you need to recall the syntax or behaviour of a particular operator.
---
Arithmetic Operators
| Symbol | Name | Description | Example |
|---|---|---|---|
+ | Addition | Adds two operands | a + b |
- | Subtraction | Subtracts right from left | a - b |
* | Multiplication | Multiplies two operands | a * b |
/ | Division | Divides left by right (integer division if both are integers) | a / b |
% | Modulus | Remainder of integer division | a % b |
- (unary) | Unary minus | Negates a single operand | -a |
---
Relational Operators
| Symbol | Name | Description | Example |
|---|---|---|---|
== | Equal to | Returns 1 if operands are equal | a == b |
!= | Not equal to | Returns 1 if operands differ | a != b |
> | Greater than | Returns 1 if left > right | a > b |
< | Less than | Returns 1 if left < right | a < b |
>= | Greater than or equal to | Returns 1 if left >= right | a >= b |
<= | Less than or equal to | Returns 1 if left <= right | a <= b |
---
Logical Operators
| Symbol | Name | Description | Example | ||||
|---|---|---|---|---|---|---|---|
&& | Logical AND | Returns 1 if both operands are non-zero (short‑circuits) | a && b | ||||
| `\ | \ | ` | Logical OR | Returns 1 if at least one operand is non-zero (short‑circuits) | `a \ | \ | b` |
! | Logical NOT | Returns 1 if operand is zero, 0 otherwise | !a |
---
Bitwise Operators
| Symbol | Name | Description | Example | ||
|---|---|---|---|---|---|
& | Bitwise AND | Sets each bit to 1 if both bits are 1 | a & b | ||
| `\ | ` | Bitwise OR | Sets each bit to 1 if at least one bit is 1 | `a \ | b` |
^ | Bitwise XOR | Sets each bit to 1 if bits differ | a ^ b | ||
~ | Bitwise NOT | Inverts all bits | ~a | ||
<< | Left shift | Shifts bits left, fills with zeros | a << n | ||
>> | Right shift | Shifts bits right (implementation‑defined for signed) | a >> n |
---
Assignment Operators
| Symbol | Name | Description | Example | ||
|---|---|---|---|---|---|
= | Simple assignment | Assigns right operand to left | a = b | ||
+= | Add and assign | Adds left and right, assigns result to left | a += b | ||
-= | Subtract and assign | Subtracts right from left, assigns result | a -= b | ||
*= | Multiply and assign | Multiplies, assigns result | a *= b | ||
/= | Divide and assign | Divides, assigns result | a /= b | ||
%= | Modulus and assign | Takes modulus, assigns result | a %= b | ||
&= | Bitwise AND and assign | Bitwise AND then assign | a &= b | ||
| `\ | =` | Bitwise OR and assign | Bitwise OR then assign | `a \ | = b` |
^= | Bitwise XOR and assign | Bitwise XOR then assign | a ^= b | ||
<<= | Left shift and assign | Shifts left, assigns result | a <<= n | ||
>>= | Right shift and assign | Shifts right, assigns result | a >>= n |
---
Increment and Decrement Operators
| Symbol | Name | Description | Example |
|---|---|---|---|
++ (prefix) | Prefix increment | Increments operand, returns new value | ++a |
++ (postfix) | Postfix increment | Returns current value, then increments | a++ |
-- (prefix) | Prefix decrement | Decrements operand, returns new value | --a |
-- (postfix) | Postfix decrement | Returns current value, then decrements | a-- |
---
Other Operators
| Symbol | Name | Description | Example |
|---|---|---|---|
? : | Ternary conditional | If condition true, returns second operand; else returns third | cond ? x : y |
, | Comma | Evaluates left, discards it, returns right | (a, b) |
sizeof | Size of | Returns size in bytes of a type or expression | sizeof(int) |
& (unary) | Address of | Returns memory address of a variable | &a |
* (unary) | Dereference | Accesses value at a pointer | *ptr |
-> | Structure pointer | Member access through a pointer | ptr->member |
. | Structure member | Direct member access | struct.member |
Operator Precedence and Associativity — The Complete Table
Operator precedence determines the order in which operators are evaluated in an expression without parentheses. Associativity determines the order when operators of the same precedence appear together (left‑to‑right or right‑to‑left). C has 15 precedence levels. When in doubt, use parentheses — they never cost runtime performance and always make the intent explicit.
Below is the complete precedence table, with level 1 being the highest precedence (evaluated first) and level 15 the lowest.
| Level | Operators | Description | Associativity | ||
|---|---|---|---|---|---|
| 1 | () [] -> . | Function call, array subscript, struct member access | Left to right | ||
| 2 | ! ~ ++ -- + - * & (type) sizeof | Unary operators (logical NOT, bitwise NOT, pre‑increment, pre‑decrement, unary plus/minus, dereference, address‑of, cast, sizeof) | Right to left | ||
| 3 | * / % | Multiplication, division, modulus | Left to right | ||
| 4 | + - | Addition, subtraction | Left to right | ||
| 5 | << >> | Bitwise left shift, bitwise right shift | Left to right | ||
| 6 | < <= > >= | Relational less/greater than operators | Left to right | ||
| 7 | == != | Relational equality/inequality | Left to right | ||
| 8 | & | Bitwise AND | Left to right | ||
| 9 | ^ | Bitwise XOR | Left to right | ||
| 10 | `\ | ` | Bitwise OR | Left to right | |
| 11 | && | Logical AND | Left to right | ||
| 12 | `\ | \ | ` | Logical OR | Left to right |
| 13 | ?: | Ternary conditional | Right to left | ||
| 14 | `= += -= *= /= %= &= ^= \ | = <<= >>=` | Assignment and compound assignment | Right to left | |
| 15 | , | Comma operator | Left to right |
- Unary operators (level 2) bind tighter than any binary operator.
-a bmeans(-a) b, not-(a * b). - Arithmetic (levels 3‑4) beats shift (level 5) beats relational (level 6) beats bitwise (levels 8‑10) beats logical (levels 11‑12).
- Assignment (level 14) is one of the lowest, so
a = b + cisa = (b + c). The comma (level 15) is lowest of all. - Right‑to‑left associativity at levels 2, 13, and 14 means chained assignments like
a = b = care evaluated asa = (b = c)— the rightmost assignment happens first.
Always parenthesize when mixing bitwise and logical operators with relational ones: if ((x & MASK) == 0) — without the parentheses, & binds tighter than ==, so x & MASK == 0 is parsed as x & (MASK == 0), which is almost certainly wrong.
Another common pitfall: if (a = b & c) is parsed as a = (b & c) because assignment is level 14 and bitwise AND is level 8. That may be what you want, but the intent is unclear. Use parentheses to show intent.
if (x & MASK == 0) is parsed as if (x & (MASK == 0)) because == has higher precedence than &. The intended check if ((x & MASK) == 0) requires explicit parentheses. This mistake is so common that GCC 13+ added a -Wparentheses warning for it. Always parenthesize bitwise and logical operators when combined with relational or equality operators.if (flags & PROTOCOL_MASK == 0) to check if the protocol field was zero. Because == binds tighter, the condition always tested flags & 0 — which is always 0 — so the if‑block never executed, and every packet was misclassified. The bug lived unnoticed in the codebase for six months because the test suite only used flags with the expected pattern, never the zero case. Adding parentheses (flags & PROTOCOL_MASK) == 0 and a unit test with flags = 0 would have caught it immediately.Relational and Logical Operators — Teaching Your Program to Make Decisions
Arithmetic operators crunch numbers. Relational operators compare them and return either 1 (true) or 0 (false). They are the basis of every if-statement, every loop condition, every decision your program makes. C has six: == (equal to), != (not equal to), > (greater than), < (less than), >= (greater than or equal to), and <= (less than or equal to).
Logical operators let you combine those comparisons. Think of them as the words 'and', 'or', and 'not' in English. In C: && means AND (both conditions must be true), || means OR (at least one must be true), and ! means NOT (flips true to false or false to true).
Here is a real-world framing: you are building access control for a theme park ride. The rule is: the rider must be at least 120 cm tall AND no taller than 200 cm AND either be 18 or over OR have a parent present. That is three conditions, two operators, and the kind of compound logic that appears in virtually every non-trivial program ever written.
Short-circuit evaluation is the behaviour you must understand: with &&, if the left side is false, C skips the right side entirely — it cannot change the result. With ||, if the left side is true, the right side is skipped. This matters enormously when the right side has side effects (function calls, pointer dereferences, increments). The idiom if (ptr != NULL && *ptr == 'A') is safe precisely because of short-circuit evaluation — the dereference only happens when the pointer is non-null. Flip the order and you have a crash waiting to happen.
Another real example: a logging module had if (log_level >= DEBUG && printf("%s", msg)) — the developer intended to log only if level was high enough, but the printf always executed because it was on the right of && and the left was true. Short-circuit saved the printf from running when the left was false, but when left was true, the printf ran and its return value (number of chars printed) was truthy. That's not a bug per se, but it shows how side-effects in conditions can lead to confusion.
if (fd = open(path, O_RDONLY)) — the assignment always produced a non-zero file descriptor on success, so the error path never executed even when open failed with -1. The program read from a bad file descriptor and silently corrupted output for weeks before anyone noticed. -Wall would have caught it on the first build. Rule: compile with -Wall -Wextra on every build, in CI and locally, from day one.Assignment and Compound Assignment Operators — Writing Less, Doing More
The single equals sign (=) in C is the assignment operator. It takes the value on the right and stores it in the variable on the left. It does not check equality — that is ==. A useful mental habit: read = as 'gets the value of', not 'equals'. It prevents the = versus == confusion at the thinking stage, before your fingers reach the keyboard.
C also gives you compound assignment operators, which combine an arithmetic operation with assignment into one step. Instead of writing score = score + 10, you write score += 10. They are shorthand — nothing more. But they appear everywhere in real C code and reading them fluently matters.
The full set is: += (add and assign), -= (subtract and assign), *= (multiply and assign), /= (divide and assign), and %= (modulo and assign). There are also bitwise compound assignments: &=, |=, ^=, <<=, >>= — you will see these in embedded and systems code for manipulating hardware registers.
Then there is the increment (++) and decrement (--) operator pair. These add or subtract exactly 1. They exist in two forms: prefix (++counter) and postfix (counter++). The difference only matters when the result is used inside a larger expression. Prefix increments first and returns the new value. Postfix returns the current value and then increments. In a simple for loop where the result is discarded, the two are identical. When used inside a larger expression, the difference is concrete — and modifying the same variable twice in the same expression crosses into undefined behaviour territory, which means the compiler can do whatever it likes and still be technically correct.
A classic interview question: int i = 0; int a = i++ + ++i; What is a? The answer: undefined behaviour. You're modifying i twice between sequence points. The compiler may produce 0, 2, or anything else.
Prefix vs Postfix Increment and Decrement — Side-by-Side Comparison
The increment (++) and decrement (--) operators each come in two forms: prefix (before the variable) and postfix (after the variable). Understanding the difference is critical because they behave identically in simple statements but diverge when used inside larger expressions. This side-by-side comparison gives you a clear mental model.
When the result is used (assigned, passed, etc.): - Prefix: changes the variable first, then returns the new value. - Postfix: returns the old value first, then changes the variable.
When the result is discarded (standalone statement): - Both prefix and postfix produce the same final value in the variable. The difference is irrelevant.
When the variable is modified more than once between sequence points: - Both are undefined behaviour — the compiler may do anything. Always avoid.
| Scenario | Prefix (++x) | Postfix (x++) |
|---|---|---|
Initial value of x | 5 | 5 |
Expression y = ++x | x becomes 6, then y gets 6 | y gets 5, then x becomes 6 |
Final x after expression | 6 | 6 |
| Use inside larger expression | arr[++i] increments i first, then uses it as index | arr[i++] uses current i as index, then increments i |
| Performance (theoretical) | No temporary copy needed — possible micro‑optimisation | May require a temporary copy of the old value |
Readability in for loops | for (int i = 0; i < n; ++i) — idiomatic, no difference | for (int i = 0; i < n; i++) — equally idiomatic, no difference |
| Undefined behaviour risk | Less likely to accidentally use in a multi‑expression context because prefix reads as a more 'active' operation | More likely to be mistakenly combined with other uses of the same variable in one expression |
A quick rule of thumb: if you are just incrementing a loop counter and don't need the old value, use prefix (++i) to avoid any temptation to write i++ inside a larger expression. In practice, any good compiler will generate identical machine code for both in a standalone statement, so focus on clarity. The real danger is expressions like arr[i++] = arr[i] + 1 — that modifies i once in a compound assignment and also reads i on the right, which is undefined behaviour because there is no sequence point between the read on the right and the modification on the left. Always write such code as separate statements.
Also note: the comma operator does NOT introduce a sequence point between its left and right operands; it only guarantees left-to-right evaluation with a sequence point at the comma. So i++, j = i is well-defined because the comma operator has a sequence point. But i++ + i++ does not have a sequence point between the two evaluations of i, hence undefined.
++i by default because it doesn't create a temporary for user‑defined types. In C, the difference is only a concern inside expressions. Still, adopting the prefix habit makes it easier to avoid accidentally writing i++ inside a larger expression, which is a common source of undefined behaviour.#define LOG(msg) (++log_count, printf("[%d] %s\n", log_count, msg)). The developer inadvertently used log_count++ in a conditional expression, causing log_count to be incremented twice per macro invocation — once by the comma operator and once by the postfix increment. The bug showed up as erratic log numbering that skipped numbers. Fix: use prefix increment to make the order explicit: ++log_count.Bitwise Operators — Working Directly With the Zeros and Ones
Every value in your computer is stored as binary — a sequence of 0s and 1s called bits. Bitwise operators let you manipulate those individual bits directly. This sounds advanced, but it is used everywhere: setting hardware flags in embedded systems, optimising memory in tight loops, building permission systems (the read/write/execute flags in Linux file permissions are a textbook example), and implementing fast operations in graphics and networking code.
C has six bitwise operators: & (AND), | (OR), ^ (XOR), ~ (NOT/complement), << (left shift), and >> (right shift).
AND (&) returns 1 for each bit position where both operands have a 1. It is the masking operator — use it to check whether a specific bit is set. OR (|) returns 1 where either operand has a 1 — use it to set a bit. XOR (^) returns 1 where the bits differ and 0 where they match — which is why XORing a value with 1 always flips that bit. This makes XOR the toggle operator: apply it twice to the same value with the same mask and you get back exactly where you started.
NOT (~) inverts every bit. Left shift (<<) shifts all bits toward higher significance — shifting left by 1 is equivalent to multiplying by 2, shifting left by n is multiplying by 2^n. Right shift (>>) moves bits toward lower significance — for unsigned integers this divides by 2^n. For signed negative integers, right shift behaviour is implementation-defined: some platforms fill the vacated bits with the sign bit (arithmetic shift), others fill with zeros (logical shift). This is why the rule exists: always use unsigned int or a fixed-width type from stdint.h for bitwise operations.
A practical example from a network protocol: parsing a 4-byte header where bits 0-3 contain the version, bits 4-7 contain the header length, and bits 8-31 contain the payload length. You'd mask and shift like: version = header & 0x0F; header_len = (header >> 4) & 0x0F; payload_len = (header >> 8) & 0xFFFFFF;. Without unsigned types, shifting a signed integer right could propagate the sign bit and corrupt the values.
Ternary Operator and Comma Operator — Compact but Dangerous
C offers two lesser-known operators that can make code either elegantly compact or genuinely hard to read. Knowing both is important — you will encounter them in production codebases and interviews, and misreading either one leads to real bugs.
The ternary operator (?:) is a shorthand if-else that returns a value. Syntax: condition ? value_if_true : value_if_false. It returns a value, so you can use it inside assignments, function arguments, or anywhere an expression is expected. It is best used for simple, clear conditional assignments. Chaining ternaries — condition_a ? a : condition_b ? b : c — is technically legal but a code smell that harms readability without saving meaningful lines. If you find yourself nesting them, use an if-else chain instead.
The comma operator (,) evaluates its left operand, discards the result, then evaluates its right operand and returns that. It has the lowest precedence of any C operator, which means assignment binds tighter: y = 1, 2, 3 is parsed as (y = 1), 2, 3 — y gets 1, and 2 and 3 are evaluated and discarded. The comma is most legitimately used in for-loop headers to initialise or update multiple variables in lockstep. Its use elsewhere is mostly a maintenance hazard — it appears in macros and obfuscated code, and the precedence surprise catches people who are not expecting it.
Another use: if (err && (printf("error: %s ", err), 0)) — this prints the error and then evaluates to 0, so the if body is skipped. But it's ugly. Most style guides forbid comma operator outside of for-loops.
const char *label = (count == 1) ? "item" : "items"; is cleaner than a four-line if-else. But the moment either branch contains a function call with side effects, or you find yourself nesting ternaries, stop. Switch to if-else. The compiler produces identical output, the next developer produces fewer mistakes, and the on-call engineer debugging at 2am is grateful.#define RESET(a, b) a = 0, b = 0. At the call site, if (condition) RESET(x, y); expanded to if (condition) x = 0, y = 0; — which, because of precedence, only put x = 0 inside the if body. The y = 0 always executed regardless of the condition. The fix: always wrap macro bodies in a do-while(0) block and parenthesise every argument use. Never use the bare comma operator in a macro that could appear in a branch context.sizeof Is a Compiler-Constant, Not a Function — Stop Writing sizeof(int)
Every junior thinks sizeof is a function. It isn't. It's a unary operator evaluated entirely at compile time. No runtime overhead. Zero. The parentheses are only needed when the operand is a type name, not a variable. When you write sizeof buffer instead of sizeof(buffer), you'll still get the same bytes. But when you refactor from an array to a pointer, sizeof(buffer) silently gives you 8 bytes (the pointer) instead of the array's full allocation. That's a real bug I've caught in production code that corrupted heap buffers for months. The fix: always use sizeof on the referenced variable, not the type. And if you must use a type, consider sizeof *pointer first — that trick survives pointer declaration changes. The sizeof operator exists because the hardware needs to know how much stack to allocate or how many bytes to memcpy. It's not magic. It's a constant folded into your binary at compile time.
Address-of and Dereference — Your Two Lowest-Level Weapons
The & operator gives you the memory address of a variable. The operator fetches the value at that address. That's it. No magic. But the WHY matters more than the HOW. You need & when a function wants to modify your variable directly — that's pass-by-reference in C. You need when you're walking a linked list or reading hardware registers mapped to a memory address. Production rule: use & to hand off ownership to a callee that mutates state. Use * to read what a pointer points to, but always check for NULL first. I've seen segfaults in embedded systems because someone dereferenced a NULL pointer they got from an uninitialized device driver. Always guard dereferences with a null check. And never return a pointer to a stack-local variable — that address is invalid as soon as the function returns. That's why you need malloc or static buffers. The operators themselves are dirt simple. The bugs come from ignoring lifespan and null state.
Cast Operators — The Only Thing Worse Than a Cast Is No Cast at All
Casts are explicit type conversions. You're telling the compiler 'I know what I'm doing, shut up and reinterpret these bytes.' That's terrifying and essential. In production code, every implicit conversion is a potential bug. An int to a short truncates silently. A float to an int drops precision without warning. Cast operators make those conversions explicit so your code reviewer can see the risk. WHY you cast: (1) converting between arithmetic types when you need to control truncation, (2) casting void* from malloc to the target pointer (in C++ you must, in C you don't need to but it's harmless), (3) casting between struct pointers in tagged unions or protocol parsing. HOW to cast: type_to_castexpression. That's it. But production rule: prefer explicit casts over implicit conversions. If you see a compiler warning about truncation, put a cast there so everyone knows you meant it. Never cast away const unless you absolutely have to — and even then, consider redesigning. Casts mask bugs. Use them like a scalpel, not a chainsaw.
Integer Division Truncation in a Banking Interest Calculation
int avg_balance = total / days; believing that dividing two integers yields a fractional result when the quotient is not whole. They had tested with values like 100 / 7, seen 14 in the output, and assumed the display was just truncating for formatting. The variable type being int did not register as the problem.double avg_balance = (double)total / days;. Updated the variable type from int to double to preserve the fractional portion through the entire calculation chain. Added a unit test asserting that dividing 17 by 5 yields a value greater than 3.3 — a test that would have caught this on day one.- When performing division that needs a fractional result, always ensure at least one operand is float or double before the division occurs — not after.
- Cast explicitly rather than relying on implicit conversion.
(double)total / daysmakes the intent clear to every developer who reads the code after you. - In financial code, never use integer division for rates, averages, or any proportional calculation. Use double or a fixed-point library.
- Write a unit test for any arithmetic path that involves division. A test asserting the result is within 0.01 of the expected value would have caught this immediately.
getchar()) != EOF).gcc -Wall -Wextra -Werror -o prog prog.cgrep -n 'if\s*(\s*[a-zA-Z_][a-zA-Z0-9_]*\s*=[^=]' prog.cget_value()) != SENTINEL) to make intent explicit.Key takeaways
Common mistakes to avoid
6 patternsUsing = instead of == in conditions
getchar()) != EOF).Assuming integer division returns a decimal
Forgetting that modulus sign follows dividend
Using signed integers for bitwise shifts and complement
Modifying a variable twice in the same expression (e.g. arr[i++] + arr[i++])
Ignoring operator precedence (e.g. x & MASK == 0)
Interview Questions on This Topic
What is the difference between prefix and postfix increment in C?
Frequently Asked Questions
20+ years shipping performance-critical C and C++ systems. Notes here come from systems that actually shipped.
That's C Basics. Mark it forged?
21 min read · try the examples if you haven't