C++ Module 2 – Types, values and expressions
C++ operators, precedence and integer division
How C++ groups an expression by precedence and associativity, why -7 / 2 is -3 and -7 % 3 is -1, and which precedence traps the compilers flag.
What you will learn
- Evaluate expressions using precedence and associativity
- Use / and % correctly with negative numbers
- Add parentheses where they make intent clear
Before you start
What you’ll learn · Before you start
What you will learn
- Evaluate expressions using precedence and associativity
- Use / and % correctly with negative numbers
- Add parentheses where they make intent clear
Before you start
On this page
When an expression has several operators, precedence decides which operands each one takes, and
associativity decides how a run of operators of the same precedence is grouped. In C++, *, / and % bind
more tightly than + and -, comparisons more tightly than &&, and && more tightly than ||, so
a + b * c < d && e means ((a + (b * c)) < d) && e. Most operators group from the left, 10 - 4 - 3 is
(10 - 4) - 3, while assignment groups from the right, so a = b = 0 sets both. Integer division truncates towards
zero, which gives -7 / 2 == -3, and % takes the sign of its left operand, so -7 % 3 == -1. Precedence is also
where many bugs hide: x & 1 == 0 does not test whether x is even. This lesson shows the rules with Apple clang 21
and GCC 16, including the warnings each compiler gives, and ends with a leap-year and weekday calculation that uses
all of them.
Precedence and associativity
#include <print>
int main() {
int a = 2, b = 3, c = 4;
std::println("a + b * c = {:>3} same as a + (b * c)", a + b * c);
std::println("(a + b) * c = {:>3}", (a + b) * c);
std::println("20 - 6 - 4 = {:>3} same as (20 - 6) - 4", 20 - 6 - 4);
std::println("120 / 6 / 2 = {:>3} same as (120 / 6) / 2", 120 / 6 / 2);
std::println("a < b == b < c = {} same as (a < b) == (b < c)", a < b == b < c);
std::println("a > b || b < c && c > 9 = {} same as (a > b) || ((b < c) && (c > 9))", a > b || b < c && c > 9);
int x, y;
x = y = 7; // assignment groups from the right: x = (y = 7)
x += y *= 2; // y becomes 14, then x becomes 7 + 14
std::println("x = {}, y = {}", x, y);
std::println("c > 3 ? 1 : 0 + 10 = {} the ?: comes last: c > 3 ? 1 : (0 + 10)", c > 3 ? 1 : 0 + 10);
} from session import run
run("""
clang++ -std=c++23 -Wall -Wextra grouping.cpp -o grouping
./grouping
g++-16 -std=c++23 -Wall -Wextra grouping.cpp -o grouping
./grouping
""") Output
$ clang++ -std=c++23 -Wall -Wextra grouping.cpp -o grouping
grouping.cpp:10:107: warning: '&&' within '||' [-Wlogical-op-parentheses]
10 | std::println("a > b || b < c && c > 9 = {} same as (a > b) || ((b < c) && (c > 9))", a > b || b < c && c > 9);
| ~~ ~~~~~~^~~~~~~~
grouping.cpp:10:107: note: place parentheses around the '&&' expression to silence this warning
10 | std::println("a > b || b < c && c > 9 = {} same as (a > b) || ((b < c) && (c > 9))", a > b || b < c && c > 9);
| ^
| ( )
1 warning generated.
$ ./grouping
a + b * c = 14 same as a + (b * c)
(a + b) * c = 20
20 - 6 - 4 = 10 same as (20 - 6) - 4
120 / 6 / 2 = 10 same as (120 / 6) / 2
a < b == b < c = true same as (a < b) == (b < c)
a > b || b < c && c > 9 = false same as (a > b) || ((b < c) && (c > 9))
x = 21, y = 14
c > 3 ? 1 : 0 + 10 = 1 the ?: comes last: c > 3 ? 1 : (0 + 10)
$ g++-16 -std=c++23 -Wall -Wextra grouping.cpp -o grouping
grouping.cpp: In function 'int main()':
grouping.cpp:9:72: warning: suggest parentheses around comparison in operand of '==' [-Wparentheses]
9 | std::println("a < b == b < c = {} same as (a < b) == (b < c)", a < b == b < c);
| ~~^~~
grouping.cpp:10:107: warning: suggest parentheses around '&&' within '||' [-Wparentheses]
10 | std::println("a > b || b < c && c > 9 = {} same as (a > b) || ((b < c) && (c > 9))", a > b || b < c && c > 9);
| ~~~~~~^~~~~~~~
$ ./grouping
a + b * c = 14 same as a + (b * c)
(a + b) * c = 20
20 - 6 - 4 = 10 same as (20 - 6) - 4
120 / 6 / 2 = 10 same as (120 / 6) / 2
a < b == b < c = true same as (a < b) == (b < c)
a > b || b < c && c > 9 = false same as (a > b) || ((b < c) && (c > 9))
x = 21, y = 14
c > 3 ? 1 : 0 + 10 = 1 the ?: comes last: c > 3 ? 1 : (0 + 10)
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 grouping_session.py
The $ lines are commands, and what each one printed follows it until the next $. Every line prints the value of an expression next to the same expression with all its parentheses written out.
The minus and the division group from the left: grouping from the right would give 20 - (6 - 4) = 18 and
120 / (6 / 2) = 40. Assignment groups from the right, so x = y = 7 stores 7 in y and then the result in x, and
x += y *= 2 doubles y before adding it to x. The conditional operator ?: sits almost at the bottom of the
table, below +, so in c > 3 ? 1 : 0 + 10 the addition belongs to the third operand.
Look at the warnings too. Both compilers accept the program, yet GCC asks for parentheses around a < b == b < c, and
both ask for them around the && inside an ||. The grouping there is exactly what the program prints, but it is a
grouping that readers often get wrong, which is why rule ES.41 of the C++ Core Guidelines says to parenthesise when
in doubt.
The C++ standard defines precedence through its grammar rather than a table, but the result can be summarised. From the operators that bind most tightly to those that bind least:
| Rank | Operators | Grouping |
|---|---|---|
| 1 | scope a::b |
left |
| 2 | postfix a++ a--, call f(x), subscript a[i], member a.b p->b |
left |
| 3 | prefix ++a --a, unary +a -a !a ~a, *p, &a, sizeof, casts |
right |
| 4 | multiplicative * / % |
left |
| 5 | additive + - |
left |
| 6 | shifts << >> |
left |
| 7 | three-way comparison <=> |
left |
| 8 | relational < <= > >= |
left |
| 9 | equality == != |
left |
| 10 | bitwise AND & |
left |
| 11 | bitwise XOR ^ |
left |
| 12 | bitwise OR | |
left |
| 13 | logical AND && |
left |
| 14 | logical OR || |
left |
| 15 | conditional ?:, assignment = += and the other compound assignments, throw |
right |
| 16 | comma , |
left |
Two rows deserve a second look. The shifts sit below +, so 1 << n + 1 shifts by n + 1; and the bitwise
operators sit below the comparisons, which is the next section’s trap. When << writes to std::cout, the same row
applies: std::cout << a + b prints the sum, but std::cout << a & b tries to apply & to the stream and does not
compile.
Integer division and the remainder
For integers, / discards the fraction, truncating towards zero, and % gives the remainder that goes with it,
so that (a / b) * b + a % b == a always holds. Since C++11 the standard fixes both, and the consequence is that
the remainder has the sign of the left operand:
#include <print>
#include <utility>
// Rounds the quotient down instead of towards zero (b must not be 0).
int floor_div(int a, int b) {
int q = a / b;
if (a % b != 0 && (a < 0) != (b < 0)) --q;
return q;
}
// A remainder from 0 to b - 1 for a positive b, as calendars and clocks want.
int wrap_mod(int a, int b) {
int r = a % b;
return r < 0 ? r + b : r;
}
int main() {
std::println(" a b | a / b a % b | floor_div | (a / b) * b + a % b == a");
for (auto [a, b] : {std::pair{7, 2}, {-7, 2}, {7, -2}, {-7, -2}, {-7, 3}}) {
std::println("{:>3} {:>3} | {:>5} {:>5} | {:>9} | {}", a, b, a / b, a % b, floor_div(a, b), (a / b) * b + a % b == a);
}
std::println("-7 % 3 = {}, but wrap_mod(-7, 3) = {}", -7 % 3, wrap_mod(-7, 3));
} from session import run
run("""
clang++ -std=c++23 -Wall -Wextra division.cpp -o division
./division
g++-16 -std=c++23 -Wall -Wextra division.cpp -o division
./division
""") Output
$ clang++ -std=c++23 -Wall -Wextra division.cpp -o division $ ./division a b | a / b a % b | floor_div | (a / b) * b + a % b == a 7 2 | 3 1 | 3 | true -7 2 | -3 -1 | -4 | true 7 -2 | -3 1 | -4 | true -7 -2 | 3 -1 | 3 | true -7 3 | -2 -1 | -3 | true -7 % 3 = -1, but wrap_mod(-7, 3) = 2 $ g++-16 -std=c++23 -Wall -Wextra division.cpp -o division $ ./division a b | a / b a % b | floor_div | (a / b) * b + a % b == a 7 2 | 3 1 | 3 | true -7 2 | -3 -1 | -4 | true 7 -2 | -3 1 | -4 | true -7 -2 | 3 -1 | 3 | true -7 3 | -2 -1 | -3 | true -7 % 3 = -1, but wrap_mod(-7, 3) = 2
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 division_session.py
-7 / 2 is -3 rather than -4, and -7 % 2 is -1. Mathematics usually prefers the quotient rounded down and a remainder
between 0 and b - 1; for positive operands the two agree, and for negative ones you write it yourself, as
floor_div and wrap_mod do. wrap_mod(-7, 3) gives 2, the answer calendars and clocks need. Two more rules
complete the picture: dividing by zero is undefined behaviour, and so is INT_MIN / -1, whose true result,
2,147,483,648, does not fit in an int.
&& and || stop early
&& and || evaluate their left operand first and evaluate the right one only when it can still change the result.
This is called short-circuit evaluation:
#include <print>
#include <vector>
bool check(const char* name, bool result) {
std::println(" evaluating {}", name);
return result;
}
int main() {
std::println("false && B:");
bool r1 = check("A", false) && check("B", true);
std::println("true || B:");
bool r2 = check("A", true) || check("B", true);
std::println("false & B, with the bitwise &:");
bool r3 = check("A", false) & check("B", true);
std::println("results: {} {} {}", r1, r2, r3);
// Short-circuiting makes the index check protect the element access.
std::vector<int> v{4, 8};
std::size_t i = 5;
if (i < v.size() && v[i] > 0) std::println("positive");
else std::println("index {} is outside the vector, and v[{}] was never read", i, i);
} from session import run
run("""
clang++ -std=c++23 -Wall -Wextra short_circuit.cpp -o short_circuit
./short_circuit
g++-16 -std=c++23 -Wall -Wextra short_circuit.cpp -o short_circuit
./short_circuit
""") Output
$ clang++ -std=c++23 -Wall -Wextra short_circuit.cpp -o short_circuit
short_circuit.cpp:15:15: warning: use of bitwise '&' with boolean operands [-Wbitwise-instead-of-logical]
15 | bool r3 = check("A", false) & check("B", true);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| &&
short_circuit.cpp:15:15: note: cast one or both operands to int to silence this warning
1 warning generated.
$ ./short_circuit
false && B:
evaluating A
true || B:
evaluating A
false & B, with the bitwise &:
evaluating A
evaluating B
results: false true false
index 5 is outside the vector, and v[5] was never read
$ g++-16 -std=c++23 -Wall -Wextra short_circuit.cpp -o short_circuit
$ ./short_circuit
false && B:
evaluating A
true || B:
evaluating A
false & B, with the bitwise &:
evaluating A
evaluating B
results: false true false
index 5 is outside the vector, and v[5] was never read
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 short_circuit_session.py
After false && and after true ||, the second check never runs: its output line is missing. The bitwise & has no
such rule, so both checks run, and Apple clang warns that a & between two bool values was probably meant to be
&&. Short-circuiting is what makes the last test safe: i < v.size() && v[i] > 0 reads v[i] only after the index
has passed the check, so element 5 of a two-element vector is never touched. Swap the two operands and the program
reads outside the vector before it tests anything.
Precedence traps the compilers flag
How x & 1 == 0 is grouped
Text description of the diagram
Two expression trees side by side. In each, an operator is a circle whose branches lead to its operands.
- Left, the expression as written, x & 1 == 0. Because == has higher precedence than &, the top node is &, with x on one branch and the comparison 1 == 0 on the other. 1 == 0 is false, which converts to 0, so the result is x & 0: always false.
- Right, the expression as meant, (x & 1) == 0. The top node is ==, comparing x & 1 with 0. x & 1 is the lowest bit of x, so the result is true for every even x.
The comparisons bind more tightly than the bitwise operators &, ^ and |, an ordering inherited from C. So
x & 1 == 0 compares 1 with 0 first, which gives false, converted to 0, and then computes x & 0, which is 0 for
every x. A chain of comparisons misleads in a different way: low < mid < high is (low < mid) < high, which
compares a bool, promoted to 0 or 1, with high.
parity.cpp
#include <print>
int main() {
for (int x : {6, 7}) {
bool even_wrong = x & 1 == 0; // parsed as x & (1 == 0), which is x & 0
bool even_right = (x & 1) == 0;
std::println("x = {}: x & 1 == 0 gives {}, (x & 1) == 0 gives {}", x, even_wrong, even_right);
}
} chained.cpp
#include <print>
int main() {
int low = 1, mid = 5, high = 3;
bool in_order = low < mid < high; // parsed as (low < mid) < high, which is true < 3
std::println("low < mid < high gives {}", in_order);
std::println("low < mid && mid < high gives {}", low < mid && mid < high);
} from session import run
run("""
clang++ -std=c++23 -Wall -Wextra parity.cpp -o parity
./parity
g++-16 -std=c++23 -Wall -Wextra parity.cpp -o parity
./parity
clang++ -std=c++23 -Wall -Wextra chained.cpp -o chained
g++-16 -std=c++23 -Wall -Wextra chained.cpp -o chained
./chained
""") Output
$ clang++ -std=c++23 -Wall -Wextra parity.cpp -o parity
parity.cpp:5:29: warning: & has lower precedence than ==; == will be evaluated first [-Wparentheses]
5 | bool even_wrong = x & 1 == 0; // parsed as x & (1 == 0), which is x & 0
| ^~~~~~~~
parity.cpp:5:29: note: place parentheses around the '==' expression to silence this warning
5 | bool even_wrong = x & 1 == 0; // parsed as x & (1 == 0), which is x & 0
| ^
| ( )
parity.cpp:5:29: note: place parentheses around the & expression to evaluate it first
5 | bool even_wrong = x & 1 == 0; // parsed as x & (1 == 0), which is x & 0
| ^
| ( )
1 warning generated.
$ ./parity
x = 6: x & 1 == 0 gives false, (x & 1) == 0 gives true
x = 7: x & 1 == 0 gives false, (x & 1) == 0 gives false
$ g++-16 -std=c++23 -Wall -Wextra parity.cpp -o parity
parity.cpp: In function 'int main()':
parity.cpp:5:33: warning: suggest parentheses around comparison in operand of '&' [-Wparentheses]
5 | bool even_wrong = x & 1 == 0; // parsed as x & (1 == 0), which is x & 0
| ~~^~~~
$ ./parity
x = 6: x & 1 == 0 gives false, (x & 1) == 0 gives true
x = 7: x & 1 == 0 gives false, (x & 1) == 0 gives false
$ clang++ -std=c++23 -Wall -Wextra chained.cpp -o chained
chained.cpp:5:31: error: chained comparison 'X < Y < Z' does not behave the same as a mathematical expression [-Wparentheses]
5 | bool in_order = low < mid < high; // parsed as (low < mid) < high, which is true < 3
| ^
1 error generated.
[exit status 1]
$ g++-16 -std=c++23 -Wall -Wextra chained.cpp -o chained
chained.cpp: In function 'int main()':
chained.cpp:5:25: warning: comparisons like 'X<=Y<=Z' do not have their mathematical meaning [-Wparentheses]
5 | bool in_order = low < mid < high; // parsed as (low < mid) < high, which is true < 3
| ~~~~^~~~~
$ ./chained
low < mid < high gives true
low < mid && mid < high gives false
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 traps_session.py
Both compilers warn about x & 1 == 0 under -Wparentheses, which -Wall enables, and the program prints false
for 6 and for 7 alike. With the parentheses, (x & 1) == 0 is true for 6. The chained comparison shows a difference
between the compilers. GCC warns and builds it, and the program says that 1 < 5 < 3 is true. Apple clang refuses to
build it at all: since Clang 21, a chained comparison is an error by default, as its release notes state, and
-Wno-error=parentheses would turn it back into a warning. The fix in both cases is the expression you meant:
(x & 1) == 0, and low < mid && mid < high.
Putting it together: leap years and weekdays
A Gregorian year is a leap year when it is divisible by 4, except century years, which are leap years only when
divisible by 400. In C++ that is (year % 4 == 0 && year % 100 != 0) || year % 400 == 0. The parentheses are not
needed, because && binds more tightly than ||, but they show the reader the rule’s structure, and both compilers
ask for them.
The day of the week can be computed with Zeller’s congruence, a formula from a short paper by Christian Zeller. It counts
January and February as months 13 and 14 of the previous year, splits the year into the century j and the year
within the century k, and takes the sum of a few terms modulo 7. The formula is often printed with a - 2j term,
which is the same as + 5j modulo 7 in mathematics but not in C++: when the sum is negative, % returns a negative
number, and an index into a list of day names cannot be negative. This program tries both against the calendar of
<chrono>, for every day of a 400-year cycle:
#include <chrono>
#include <print>
bool is_leap_year(int year) {
// && binds tighter than ||; the parentheses say so for the reader (and silence -Wparentheses).
return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
}
// Zeller's congruence for the Gregorian calendar: 0 = Saturday, 1 = Sunday, ..., 6 = Friday.
// January and February count as months 13 and 14 of the previous year.
int zeller(int year, int month, int day) {
if (month < 3) {
month += 12;
year -= 1;
}
int k = year % 100;
int j = year / 100;
return (day + 13 * (month + 1) / 5 + k + k / 4 + j / 4 + 5 * j) % 7;
}
// The same formula as it is often printed, with - 2j instead of + 5j (equal modulo 7).
int zeller_minus(int year, int month, int day) {
if (month < 3) {
month += 12;
year -= 1;
}
int k = year % 100;
int j = year / 100;
return (day + 13 * (month + 1) / 5 + k + k / 4 + j / 4 - 2 * j) % 7;
}
int main() {
constexpr const char* names[] = {"Saturday", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday"};
using namespace std::chrono;
int checked = 0, plus_ok = 0, minus_ok = 0, negative = 0, leap_ok = 0;
// Every day of a 400-year cycle, checked against <chrono>.
for (sys_days d = sys_days{2000y / January / 1}; d < sys_days{2400y / January / 1}; d += days{1}) {
year_month_day ymd{d};
int y = int(ymd.year()), m = int(unsigned(ymd.month())), dd = int(unsigned(ymd.day()));
int expected = (weekday{d}.c_encoding() + 1) % 7; // c_encoding: 0 = Sunday
++checked;
plus_ok += zeller(y, m, dd) == expected;
minus_ok += zeller_minus(y, m, dd) == expected;
negative += zeller_minus(y, m, dd) < 0;
}
for (int y = 1600; y < 2400; ++y) leap_ok += is_leap_year(y) == year{y}.is_leap();
std::println("days checked: {}", checked);
std::println("+ 5j version correct: {}", plus_ok);
std::println("- 2j version correct: {} (negative results: {})", minus_ok, negative);
std::println("is_leap_year agrees with <chrono> for {} of 800 years", leap_ok);
std::println("2000-03-01 was a {}; 2100-03-01 will be a {}", names[zeller(2000, 3, 1)], names[zeller(2100, 3, 1)]);
} from session import run
run("""
clang++ -std=c++23 -Wall -Wextra weekday.cpp -o weekday
./weekday
g++-16 -std=c++23 -Wall -Wextra weekday.cpp -o weekday
./weekday
""") Output
$ clang++ -std=c++23 -Wall -Wextra weekday.cpp -o weekday $ ./weekday days checked: 146097 + 5j version correct: 146097 - 2j version correct: 141421 (negative results: 4676) is_leap_year agrees with <chrono> for 800 of 800 years 2000-03-01 was a Wednesday; 2100-03-01 will be a Monday $ g++-16 -std=c++23 -Wall -Wextra weekday.cpp -o weekday $ ./weekday days checked: 146097 + 5j version correct: 146097 - 2j version correct: 141421 (negative results: 4676) is_leap_year agrees with <chrono> for 800 of 800 years 2000-03-01 was a Wednesday; 2100-03-01 will be a Monday
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 weekday_session.py
The + 5j version agrees with <chrono> on all 146,097 days, the length of the Gregorian calendar’s 400-year
cycle. The - 2j version is wrong on exactly the 4,676 days where its sum came out negative, and right on all the
others, so the formula was never at fault: the remainder operator was. is_leap_year matches <chrono>’s
is_leap for all 800 years it was given. In your own code, prefer <chrono>’s calendar types to hand-written
formulas, and keep a formula like this one as an exercise in reading operators.
Interview questions
Fresher · What are -7 / 2 and -7 % 2 in C++, and why?
-7 / 2 is -3 and -7 % 2 is -1. Since C++11, integer division truncates towards zero, so the exact quotient -3.5
becomes -3, not -4. The remainder is defined so that (a / b) * b + a % b == a holds: -3 × 2 + (-1) = -7. As a
result the remainder always has the sign of the left operand, which differs from the mathematical convention of a
remainder from 0 to b − 1. When you need that, for example to wrap a negative value onto a clock or a ring of
indexes, compute r = a % b and add b when r is negative, or keep the values non-negative from the start.
Check yourself
6 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.
Key takeaways
- Precedence decides grouping:
*/%before+-, shifts, comparisons, the bitwise operators,&&,||, then?:and assignment. - Most operators group from the left; prefix operators,
?:and assignment group from the right. - Integer
/truncates towards zero and%takes the sign of the left operand:-7 / 2 == -3,-7 % 3 == -1. Write a floor division or a wrapping remainder when you need one. - Dividing by zero and
INT_MIN / -1are undefined behaviour. &&and||short-circuit, soi < v.size() && v[i] > 0is safe;&and|always evaluate both sides.x & 1 == 0meansx & (1 == 0), anda < b < cmeans(a < b) < c. Both compilers warn; Clang 21 rejects the chained comparison. Parenthesise when in doubt.
References
- Working Draft, Programming Languages: C++ (N4950): [expr] Expressions, [expr.mul], [expr.log.and], [expr.log.or] (ISO/IEC JTC1/SC22/WG21)
- Options to Request or Suppress Warnings (-Wparentheses) (GNU Project)
- Clang 21.1.0 Release Notes (chained comparisons are an error by default) (LLVM Project)
- Diagnostic flags in Clang (LLVM Project)
- C++ Core Guidelines: ES.41, If in doubt about operator precedence, parenthesize (Standard C++ Foundation)
- Kalender-Formeln (Chr. Zeller, Acta Mathematica 9, 1887) (Acta Mathematica)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress