C++ • Syntax and Core Types

Arithmetic Operators & Precedence — Use arithmetic operators and precedence

Master how C++ evaluates arithmetic expressions through operator semantics, type promotion, and precedence rules.

Programming braces with a command terminal and binary digits.
Inside this lesson 10 sections 5 questions 8 figures

SECTION 1

Historical Context & Motivation

The concept of operator precedence in programming languages did not emerge in a vacuum — it was inherited from centuries of mathematical convention and refined through decades of compiler design. When Bjarne Stroustrup designed C++ as an extension of C in the early 1980s, he deliberately preserved the arithmetic operator semantics and precedence rules that Dennis Ritchie had established in C, which themselves drew heavily from Fortran and the algebraic notation standardized by mathematicians over the previous three centuries. Understanding this lineage illuminates why C++ evaluates 2 + 3 * 4 as 14 rather than 20, and why certain precedence quirks persist to this day.

1600s • Algebraic Notation Standardized

Mathematicians including Descartes and Leibniz formalized operator notation and the convention that multiplication and division bind more tightly than addition and subtraction — the precursor to modern precedence rules.

1957 • Fortran — First High-Level Compiler

John Backus and his team at IBM released Fortran, the first widely adopted compiled language. Its expression parser implemented mathematical precedence, proving that algebraic conventions could be mechanically enforced by a compiler.

1972 • C Language Created

Dennis Ritchie developed C at Bell Labs, defining a precedence table with 15 levels. The arithmetic operators (+, −, ×, /, %) followed conventional mathematical ordering, though some bitwise operator precedence choices would later be considered design mistakes by Ritchie himself.

1979–1985 • C++ Emerges

Bjarne Stroustrup extended C with classes and operator overloading, preserving C's precedence hierarchy. The ability to overload arithmetic operators for user-defined types made understanding built-in precedence even more critical.

2011–2023 • Modern C++ Standards

C++11 through C++23 introduced constexpr evaluation, but the core arithmetic operators and their precedence remain unchanged from C, reflecting a deliberate commitment to backward compatibility and mathematical convention.

The central question this lesson addresses is deceptively simple: given a C++ expression like a + b * c - d / e % f, how does the compiler determine the order of operations, what are the type semantics of each operator, and what subtle pitfalls — integer division truncation, overflow, implicit promotion — can silently corrupt your results? A rigorous understanding of these mechanics is foundational to writing correct and predictable C++ code.

SECTION 2

Core Principles & Definitions

C++ provides five fundamental arithmetic operators that operate on numeric types: addition, subtraction, multiplication, division, and the modulus (remainder) operator. These operators interact with two orthogonal but equally important concepts — precedence (which operator binds first) and associativity (which direction equal-precedence operators bind). Together, these rules completely determine how the compiler parses an expression into an abstract syntax tree (AST), thereby defining evaluation order.

1

The Five Arithmetic Operators

C++ defines + (addition), - (subtraction), * (multiplication), / (division), and % (modulus). The unary + and - are also arithmetic but have higher precedence as prefix operators.
2

Precedence

Precedence ranks operators so the compiler knows which sub-expressions to evaluate first in the absence of parentheses. Multiplicative operators (* / %) have higher precedence than additive operators (+ -), mirroring standard algebra.
3

Associativity

When operators share the same precedence level, associativity determines grouping direction. All binary arithmetic operators are left-to-right associative, so a - b - c parses as (a - b) - c.
4

Integer vs. Floating-Point Division

The / operator truncates toward zero when both operands are integers (7 / 2 == 3), but performs true division when at least one operand is a floating-point type (7.0 / 2 == 3.5).
5

Implicit Type Promotion

When operands differ in type, the compiler applies the usual arithmetic conversions: narrower types are promoted to wider types (e.g., int → double) before the operation is performed.
✦

KEY TAKEAWAY

Think of precedence like a priority queue in a scheduling system. When the compiler encounters an expression, operators with higher precedence "cut in line" and bind their operands first, just as a high-priority thread preempts a low-priority one. Parentheses act like an explicit priority override, forcing the scheduler to process a sub-expression immediately regardless of its natural priority.

SECTION 3

Visualizing Precedence & the AST

The most illuminating way to understand how the compiler interprets an arithmetic expression is to examine the abstract syntax tree (AST) it constructs. Consider the expression 2 + 3 * 4 - 6 / 2. Because * and / have higher precedence than + and -, the tree places the multiplicative operations deeper (closer to the leaves), ensuring they are evaluated before the additive operations at the top. The following diagram shows this tree structure with evaluation results propagating upward.

AST for: 2 + 3 * 4 − 6 / 2−result: 11+result: 14/result: 32*result: 126234Deeper nodes evaluate first
The AST for 2 + 3 * 4 − 6 / 2. Leaves are literal values; internal nodes are operators. Evaluation proceeds bottom-up: 3 * 4 → 12 and 6 / 2 → 3 resolve first, then 2 + 12 → 14, and finally 14 − 3 → 11.

Notice how the multiplication and division nodes sit deeper in the tree than the addition and subtraction nodes. This depth relationship is the geometric manifestation of precedence: higher-precedence operators are pushed further down toward the leaves, ensuring they are evaluated first during a post-order traversal. If you were to add parentheses — say (2 + 3) * 4 — the addition node would be pushed below multiplication, fundamentally reshaping the tree and changing the result from 14 to 20.

SECTION 4

Operator Semantics & Type Rules

Each arithmetic operator in C++ carries specific type semantics governed by the standard's usual arithmetic conversions (§7.4 of the C++ standard). Before any binary arithmetic operation, the compiler applies integer promotion (types narrower than int — such as char, short, and bool — are promoted to int) and then, if operand types still differ, converts the narrower type to the wider one following a well-defined hierarchy.

INTEGER DIVISION TRUNCATION

a/b=trunc(a÷b)wherea,b∈Zandb≠0a / b = trunc(a ÷ b) where a, b ∈ ℤ and b ≠ 0

For integer operands, C++ truncates the quotient toward zero. So 7 / 2 == 3 and -7 / 2 == -3 (not −4, which floor division would yield).

MODULUS IDENTITY

(a/b)×b+(a(a / b) × b + (a % b) == a

This identity always holds for integer a and non-zero integer b. Because division truncates toward zero, the sign of a % b matches the sign of a in C++11 and later.

TYPE PROMOTION HIERARCHY

bool→char→short→int→long→longlong→float→double→longdoublebool → char → short → int → long → long long → float → double → long double

When two operands have different types, the operand with the lower-ranked type is implicitly converted to the higher-ranked type before the operation executes. Mixing signed and unsigned integers within the same rank follows additional rules that can produce surprising results.

⚠

⚠ COMMON PITFALL

Integer overflow for signed types is undefined behavior in C++. The expression INT_MAX + 1 does not wrap around to INT_MIN by specification — the compiler is free to assume it never happens and may optimize accordingly. For unsigned types, overflow wraps modulo 2ⁿ, which is well-defined.

SECTION 5

Complete Arithmetic Precedence Table

The following table presents the precedence levels relevant to arithmetic in C++, extracted from the full 17-level precedence table defined by the standard. Remember that parenthesized sub-expressions are always evaluated first, effectively overriding any natural precedence. The table also includes the unary operators because the unary minus and plus are frequently confused with their binary counterparts despite having much higher precedence.

Precedence LevelOperatorsDescriptionAssociativity
2 (highest shown)+x -xUnary plus, unary minusRight-to-left
2++x --xPrefix increment / decrementRight-to-left
5* / %Multiplication, division, modulusLeft-to-right
6+ -Addition, subtractionLeft-to-right
16= += -= *= /= %=Assignment and compound assignmentRight-to-left

Arithmetic-related precedence levels in C++ (lower number = higher precedence)

Precedence Hierarchy — Arithmetic OperatorsHIGH PRECEDENCELOW PRECEDENCELevel 1( )Parentheses — explicit grouping overrideEvaluated first, innermost to outermostLevel 2+x -x ++x --xUnary prefixRight-to-left associativityLevel 5* / %MultiplicativeLeft-to-right associativityLevel 6+ -AdditiveLeft-to-right associativityLevel 16= += -= *= /= %=AssignmentRight-to-left associativity
The precedence hierarchy for arithmetic operators in C++. Operators at higher levels (smaller numbers) bind their operands before operators at lower levels. Parentheses sit at the top and can always override natural precedence.

A critical observation is the gap between levels 2 and 5. Levels 3 and 4 are occupied by member access, dereferencing, and postfix operators, which are not arithmetic but frequently appear in arithmetic expressions (e.g., ptr->value * count). The compound assignment operators (+=, *=, etc.) sit at level 16, near the bottom, so the entire right-hand side expression is fully evaluated before the assignment takes place.

SECTION 6

Worked Example — Evaluating a Complex Expression

Let us trace the evaluation of a non-trivial expression that exercises precedence, associativity, integer division, and type promotion. Consider the following C++ code:

ℹ

EXPRESSION

int result = 10 + 20 * 3 / 4 - 5 % 3 + 1.5;

Step-by-Step Evaluation

  1. 1

    Step 1 — Identify Operators and Precedence

    The expression contains + (level 6), * (level 5), / (level 5), - (level 6), % (level 5), + (level 6), and = (level 16). Level-5 (multiplicative) operators bind first, then level 6 (additive), then assignment.
  2. 2

    Step 2 — Evaluate Multiplicative Operators (Left-to-Right)

    Among the level-5 operators, left-to-right associativity means we process 20 * 3 first. Since both are int, the result is 60. Next, 60 / 4 yields 15 (integer division, but exact in this case). Finally, 5 % 3 yields 2.
    Expression becomes: 10 + 15 - 2 + 1.5
  3. 3

    Step 3 — Evaluate Additive Operators (Left-to-Right)

    All remaining binary operators are at level 6. Left-to-right: 10 + 15 → 25; then 25 - 2 → 23; then 23 + 1.5. Here, the 1.5 is a double literal, triggering implicit promotion of 23 (int) to 23.0 (double). The result is 24.5 as a double.
    Sub-expression result: 24.5 (double)
  4. 4

    Step 4 — Assignment with Narrowing

    The target variable result is declared as int, so the double value 24.5 is truncated to 24. This is an implicit narrowing conversion — most compilers will issue a warning at higher warning levels, and brace initialization (int result{24.5}) would produce a compile-time error.
    Final result: result == 24
✦

KEY TAKEAWAY

This example reveals three distinct layers of complexity in a single line of C++: precedence determines the parse tree, type promotion determines the arithmetic domain, and assignment determines the final stored type. Like a pipeline in computer architecture — fetch, decode, execute — each layer transforms the result before the next layer acts on it. Missing any layer leads to silent bugs.

SECTION 7

Common Pitfalls & Best Practices

While C++ arithmetic operators appear straightforward, a number of subtle behaviors catch even experienced developers. The table below catalogues the most common pitfalls alongside recommended practices. Many of these arise from the interaction between precedence rules and the type system, making them particularly insidious in mixed-type expressions.

PitfallExampleBest Practice
Integer division truncationint avg = sum / count; loses fractional partCast one operand: static_cast<double>(sum) / count
Signed integer overflow (UB)int x = INT_MAX + 1; is undefinedCheck before operation, or use unsigned/wider types
Modulus with negative operands-7 % 3 == -1 may surpriseRemember: result sign matches dividend in C++11+
Precedence with bit operatorsx & 0xFF + 1 parses as x & (0xFF + 1)Always parenthesize bitwise ops in mixed expressions
Implicit signed/unsigned mixing-1 < 1u evaluates to falseEnable -Wsign-compare and match signedness
Division by zerox / 0 is UB for integers, ±inf for floatsGuard with a conditional check before dividing

Common arithmetic pitfalls in C++ and recommended mitigations

✦

KEY TAKEAWAY

When in doubt, parenthesize. Parentheses are computationally free — they generate no extra instructions — but they eliminate ambiguity for both the compiler and the human reader. Treat them like type annotations in a dynamically typed language: technically optional, practically essential for maintainability.

SECTION 8

Connection to Operator Overloading & Advanced Topics

The arithmetic operators covered in this lesson operate on built-in types, but C++ extends these semantics to user-defined types through operator overloading. When you define operator+ for a class, the precedence and associativity of + remain fixed — you cannot change them. This means the precedence table you learned here governs all uses of these operators, whether on int, double, std::complex, or your own matrix class.

FeatureBuilt-in ArithmeticOverloaded Operators
PrecedenceFixed by the standardSame — cannot be changed
AssociativityFixed (left-to-right for binary arithmetic)Same — cannot be changed
Operand typesBuilt-in numeric types onlyAny user-defined class or struct
Type promotionImplicit (usual arithmetic conversions)Explicit — you define conversion constructors
Compile-time evaluationconstexpr by defaultconstexpr if marked and body is constexpr-compatible
Overflow behaviorUB for signed, wrapping for unsignedDefined by your implementation (can throw, saturate, etc.)

Built-in vs. overloaded arithmetic operators in C++

Looking further ahead, the constexpr and consteval features of modern C++ allow arithmetic expressions to be evaluated entirely at compile time, with the compiler performing the same precedence-driven evaluation you would do by hand. In template metaprogramming, arithmetic on std::integral_constant values operates at the type level, yet the same operator semantics apply. Understanding the fundamentals covered here is therefore prerequisite to advanced techniques like expression templates, which libraries such as Eigen and Blaze use to defer and optimize chains of arithmetic operations on matrices and vectors.

SECTION 9

Practice Problems

Question 1: CONCEPTUAL

Explain why the expression 3 + 4 * 2 evaluates to 11 rather than 14. Which specific C++ rule determines this behavior, and what role does associativity play (or not play) here?
Show answer
The multiplication operator * has higher precedence (level 5) than the addition operator + (level 6). Therefore, 4 * 2 is evaluated first, yielding 8, and then 3 + 8 yields 11. Associativity is irrelevant here because the two operators are at different precedence levels; associativity only breaks ties among operators at the same level.

Question 2: BASIC CALCULATION

What is the value and type of the expression 17 / 5 + 17 % 5? Show your work for each sub-expression.
Show answer
Both / and % are at precedence level 5, so they are evaluated left-to-right. First, 17 / 5 performs integer division, truncating toward zero: result is 3. Next, 17 % 5 yields the remainder 2 (since 5 × 3 + 2 = 17). Finally, 3 + 2 = 5. The result type is int. Note that this illustrates the modulus identity: (a/b)*b + (a%b) == a, so 17/5 + 17%5 always reconstructs a/b + a%b — which equals 5 here.

Question 3: INTERMEDIATE

Determine the output of the following code. Identify where type promotion and narrowing conversion occur. int a = 7; double b = 2.0; int c = a / 3 + b * 3; std::cout << c;
Show answer
Evaluating multiplicative operators first (left-to-right within that level): a / 3 is 7 / 3 = 2 (integer division, both operands are int). Then b * 3: the 3 is promoted to double, yielding 6.0 (type promotion occurs here). Now 2 + 6.0: the 2 is promoted to 2.0, yielding 8.0 (second type promotion). Assignment to int c truncates 8.0 to 8 (narrowing conversion). Output: 8.

Question 4: APPLIED

You are computing a simple moving average of sensor readings stored in an int array. Your colleague writes: int avg = (readings[0] + readings[1] + readings[2]) / 3; Given readings {10, 11, 12}, what does avg equal? What if readings are {10, 11, 13}? Propose a fix that preserves floating-point precision.
Show answer
For {10, 11, 12}: sum = 33, 33 / 3 = 11 — correct because the division is exact. For {10, 11, 13}: sum = 34, 34 / 3 = 11 (integer truncation), but the true average is 11.333…. The fix is to ensure floating-point division: double avg = static_cast<double>(readings[0] + readings[1] + readings[2]) / 3; The cast converts the sum to double before division, triggering type promotion of 3 to 3.0, yielding 11.333.... Note that the cast must be on the sum, not the individual element, to avoid precision issues from summing doubles unnecessarily.

Question 5: CRITICAL THINKING

Consider the expression unsigned int x = 5; int y = -3; auto z = x + y; What is the value and type of z? Explain the implicit conversion that occurs, why the result is mathematically correct despite the unsigned conversion of y, and under what conditions this pattern would produce a logically incorrect result.
Show answer
When unsigned int and int are mixed at the same rank, the signed operand is converted to unsigned (per §7.4 of the standard). So y = -3 becomes UINT_MAX - 2 (a very large number, e.g., 4294967293 on 32-bit). Adding 5 + 4294967293 overflows (wrapping modulo 2³²), yielding 2. The type of z is unsigned int. The result (2) is mathematically correct here because 5 + (−3) = 2, and the modular arithmetic happens to produce the right answer. However, if y were −10, the result would be UINT_MAX - 4 — a huge positive number, not −5. Any time the true mathematical result is negative, the unsigned conversion yields a logically incorrect (but well-defined) positive value.

SUMMARY

Lesson Summary

Summary

C++ provides five arithmetic operators — +, -, *, /, and % — governed by a strict precedence hierarchy inherited from mathematical convention and the C language. Multiplicative operators (* / %) bind more tightly than additive operators (+ -), and all binary arithmetic operators are left-to-right associative. Parentheses override all precedence rules and should be used liberally for clarity.

Beyond grouping, the type system profoundly affects results: integer division truncates toward zero, the modulus identity (a/b)*b + a%b == a always holds, and implicit type promotion silently converts narrower types to wider ones. Signed integer overflow is undefined behavior, while unsigned overflow wraps modulo 2ⁿ. These same precedence and associativity rules extend unchanged to overloaded operators on user-defined types, making mastery of these fundamentals essential for all subsequent C++ development.

Varsity Tutors • C++ • Arithmetic Operators & Precedence