LEARNING · STAGE 00 · PROGRAMMING BASICS

Operators: arithmetic, comparison, logic

The previous lesson included a warning: 10 / 3 in int gives not 3.33, but simply 3. It's time to understand why — and also to cover comparison and logical operators, without which you can't build a single condition in the next lesson.

01What you'll learn

  • Arithmetic operators + - * / % and the integer division trap
  • Comparison operators == != < > <= >= — their result is always bool
  • Logical operators && || ! and what short-circuit evaluation is
  • A common mistake — confusing = (assignment) with == (comparison)

02Core idea: operators are still a calculator, just with new buttons

A regular calculator knows + - * /. C++ adds to this set: % (division remainder), comparison buttons (equal/greater/less — but instead of a number, they produce "yes" or "no"), and logic buttons that combine several "yes/no" answers into one. There's no new concept here — an operator is still part of an expression that evaluates to a value, just like x + 5 from the second lesson. It's just that result types now vary: arithmetic produces a number, comparison and logic always produce bool.

03Arithmetic operators

Addition and subtraction already came up (x + 5) — the remaining three from the full set (* / %) work on the same principle: each operator is part of an expression that evaluates to a value, not a command by itself.

It's exactly this set where the trap warned about in the previous lesson occurred. On paper, 17 divided by 5 is 3.4. In C++, if both variables are int, 17 / 5 is exactly 3, with no rounding — the fractional part simply doesn't appear. The computer isn't making a mistake or glitching here: if the result must be int, and int cannot store fractions, the only option is to discard the fractional part entirely. To get an exact result, at least one operand must be double before the division:

main.cppC++
int total = 17;
int players = 5;

int perPlayerInt = total / players;                              // 3 — fractional part discarded
int remainder = total % players;                                  // 2 — remainder, what didn't fit
double perPlayerExact = static_cast<double>(total) / players;    // 3.4 — exact result

% (division remainder) is what complements integer division: total / players gives "how much each got", total % players gives "how much remained undistributed". static_cast<double>(total) is an explicit type cast: it converts the value of total from int into a temporary value of type double right before the division, so the division follows double rules rather than int rules (more on type casts in Stage 01). The construct has three parts, each with its own role:

  • static_cast — the operator itself: the command to "convert explicitly", not "add" or "compare".
  • <double> — the target type in angle brackets: what exactly to convert the value into.
  • (total) — the source expression in parentheses: what exactly is being converted; the result is a new temporary value, while total itself remains int and is unchanged.

The result of the whole construct is an ordinary value of type double, which you can then work with like any other double (for example, immediately divide by players). static_cast is not the only way to convert one value into another; it has three neighboring alternatives, and the choice between them is not random:

VariantWhen
static_cast<T>(x)ordinary controlled conversion — the standard choice (as here, int → double)
implicit conversionwhen the compiler safely performs the needed conversion itself — for example, double d = 5; with no static_cast at all
C-style (T)xinherited from the C language; generally avoid in modern C++ — doesn't express intent and is more dangerous than static_cast
reinterpret_casta completely different, much rarer and more dangerous task — not for numbers; out of scope for this course

04Comparison operators

"Is the character alive?" — essentially the question "is hp greater than zero?", and the answer to such a question is not a number but yes/no. That's exactly what comparison operators do: they take two values and turn them into a bool — true or false, nothing else:

OperatorMeansExample
==equal tohp == 0
!=not equal tohp != 0
<less thanhp < 20
>greater thanhp > 0
<=less than or equal tohp <= 0
>=greater than or equal tohp >= 100

The most common beginner mistake is writing hp = 0 where the comparison hp == 0 was intended. This is not a typo the compiler will catch: hp = 0 is a perfectly legal assignment (see the second lesson), it just doesn't compare — it changes the value of hp to 0. More in the "Common mistakes" section.

05Logical operators

A single comparison is often not enough: "can continue" depends not only on whether the character is alive, but also on whether they have a key — two separate bool values need to be combined into one answer. That's what logical operators are for: they take one or more bool values and combine them into a new bool value:

OperatorMeansExampleTrue when
&&AND (and)hp > 0 && hasKeyboth conditions are true
||OR (or)hp > 0 || hasShieldat least one condition is true
!NOT (not)!isAliveflips the value: was true — became false

&& and || evaluate with short-circuit evaluation: in a && b, if a is already false, C++ doesn't even evaluate b — the result will be false in any case. Similarly for a || b: if a is already true, b is not evaluated. This isn't critical now, but it will become important later, when b is not just a variable but a function call with a side effect — short-circuit evaluation means such a call may not happen at all.

06Operator precedence

Just like in mathematics (where multiplication happens before addition), C++ operators have an evaluation order. From highest to lowest precedence:

GroupOperatorsExample
1. Parentheses()(a + b) * c — parentheses first
2. Unary NOT!!isAlive
3. Multiplication, division, remainder* / %a + b * c — b * c first
4. Addition, subtraction+ -hp - damage > 0 — subtraction first
5. Comparison< > <= >=hp > 0 && hasKey — comparison first
6. Equality== !=hp == 0 || hasShield — equality first
7. Logical AND&&a && b || c — a && b first
8. Logical OR||evaluated after &&
9. Assignment=result = a && b — evaluated last

You don't have to keep the whole table in your head: when in the slightest doubt, add parentheses explicitly — (hp - damage) > 0 reads instantly, without needing to recall precedence.

07Building the "can proceed" condition step by step

The character program from the previous two lessons grows further: hp gains damage (damage from a hit) and hasKey (whether there is a key) — new variables for new mechanics, not directly related to the types from the previous lesson, but the same program. Same principle as before: one program that gains one more operator kind at each step — first arithmetic, then comparison, then logic.

Step 1 — arithmetic onlyC++
int hp = 40;
int damage = 25;
int hpAfterHit = hp - damage;
std::cout << "HP after hit: " << hpAfterHit << "\n";
Step 2 — added comparisonC++
int hp = 40;
int damage = 25;
int hpAfterHit = hp - damage;
bool isAlive = hpAfterHit > 0;
std::cout << "HP after hit: " << hpAfterHit << ", Alive: " << isAlive << "\n";
Step 3 — added logicC++
int hp = 40;
int damage = 25;
bool hasKey = true;
int hpAfterHit = hp - damage;
bool isAlive = hpAfterHit > 0;
bool canProceed = isAlive && hasKey;
std::cout << "Alive: " << isAlive << ", Can proceed: " << canProceed << "\n";
Step 4 — all in one expression, per the precedence tableC++
int hp = 40;
int damage = 25;
bool hasKey = true;
bool canProceed = (hp - damage) > 0 && hasKey;   // - before >, > before &&
std::cout << "Can proceed: " << canProceed << "\n";

The parentheses around (hp - damage) are not strictly required here — per the table above, subtraction would happen before comparison anyway — but they make the expression immediately readable, without cross-checking the precedence table.

08Worse → better: = instead of == — a silent bug

The task is to check that hp equals 0, and also account for having a key. One character here changes behavior completely, and the compiler won't warn about it.

Worse — typo = instead of ==C++
int hp = 0;
bool isAlive = (hp = 5);   // typo: assignment, not comparison
std::cout << isAlive << " " << hp;

Compiles without a single warning on many configurations — hp = 5 is syntactically a perfectly legal expression (an assignment statement from the second lesson), it just ended up in the wrong place here.

console (illustration)
./main 1 5 hp quietly became 5 — only a check was intended.

The program didn't crash or complain — it simply did something other than what was intended: hp was overwritten with 5, and isAlive became true, because the result of an assignment (the value 5 itself) when converted to bool — any nonzero number becomes true. This is more dangerous than a compilation error: there is nothing to fix "per the compiler's error", the bug has to be spotted by you.

Better — comparison instead of assignmentC++
int hp = 0;
bool isAlive = (hp == 5);   // comparison
std::cout << isAlive << " " << hp;

Now hp stays equal to 0 (comparison doesn't change the value), and isAlive is honestly false, because 0 ≠ 5.

Best — comparison and logic together, with labeled outputC++
int hp = 0;
bool hasKey = true;
bool canProceed = (hp == 0) && hasKey;
std::cout << "hp: " << hp << ", canProceed: " << canProceed << "\n";

The parentheses around hp == 0 are not strictly required here (by precedence, the comparison would happen before && anyway), but they explicitly show exactly what is being compared before the result participates in the logical AND — that's precisely the habit that saves debugging time on similar typos in the future.

09Try changing it

  • In the example from the "Arithmetic operators" section, replace total with 20 — perPlayerInt becomes 4, remainder becomes 0, perPlayerExact stays 4.0.
  • Output hp > 0 directly via std::cout — you'll see 1 or 0, not the words true/false (same as with a plain bool in the previous lesson).
  • Replace && with || in the two-variable condition — the result may change if at least one part is true.

10Live debugging: common mistakes

An important difference from previous lessons: all three mistakes below are not compilation errors. The code will build and run successfully in every case; the problem only appears at runtime — either as an incorrect result or as a program crash. These are exactly the bugs that are hardest to find, because the compiler gives no hint where to look.

= instead of ==
if (hp = 0)   // typo: assignment instead of comparison (should be ==)

A condition with this expression will always be false, and hp will be silently zeroed. Full breakdown with sample output is in the "Worse → better" section above; the if syntax is covered in detail in the next lesson.

Integer division instead of exact division
int total = 17;
int players = 5;
double average = total / players;   // divides as int/int BEFORE assignment to double
std::cout << average;
console (illustration)
./main 3 Expected 3.4 — got 3.

The fractional part is lost at the moment of the division itself, not when storing into double — the variable average merely stores the already-rounded result 3 as 3.0. You need to cast to double before division: static_cast<double>(total) / players.

Division by zero in remainder
int total = 17;
int divisor = 0;
int x = total % divisor;   // division/remainder by 0 for int — undefined behavior
console (illustration)
./main Floating point exception (core dumped)

In practice this is most often an immediate program crash (the message above is a typical appearance on Linux, despite the name "floating point" — a historical naming detail of that signal; the division itself here is integer). Before dividing by a variable that could theoretically be 0, it's worth ensuring it isn't — this check will become natural once if appears in the next lesson.

11Check yourself

Three questions: predicting the result of integer division, spotting a classic typo, and breaking down operator precedence.

Predict the result
What is a equal to — and why not 3.4?
int total = 17;
int players = 5;
int a = total / players;
Show explanation

a = 3 — integer division int / int discards the fractional part entirely, without rounding to the nearest integer. To get 3.4, you need to cast at least one operand to double BEFORE the division (static_cast<double>(total) / players).

Find the bug
The programmer wanted to check that hp is zero. The compiler accepted the code without a single warning. What will the program actually output?
int hp = 10;
bool isAlive = (hp = 0);
std::cout << hp << " " << isAlive;
Show explanation

It outputs 0 0. hp = 0 is not a comparison but an assignment (a single = instead of ==): hp becomes 0, and the result of the entire assignment expression is the assigned value itself (0), which when converted to bool gives false — zero is the only number that gives false. The compiler is not obligated to flag this; it's legal syntax (see §08).

Operator precedence
In what order will this expression be evaluated?
bool result = hp - damage > 0 && hasKey;
Show explanation

First hp - damage (arithmetic, group 4 in the table in §06), then the result is compared with 0 (comparison, group 5), and only then is that comparison combined with hasKey via && (group 7) — arithmetic and comparison always happen before logical operators. Explicit parentheses — (hp - damage) > 0 && hasKey — would produce the same result, but read faster, without checking the table.

12Practice

★★ Based on total and players, compute: integer division (/), remainder (%), and exact division in double (with a type cast). Output all three values.

★★ You have hp and hasKey. Compute and output via std::cout the result of a comparison (hp > 0 or hp == 0) and the result of a logical AND/OR combining that comparison with hasKey.

★★★ Given int hp, int damage, and bool hasShield. Compute hp after damage (arithmetic), check that it's greater than 0 (comparison), and compute canProceed — you can proceed if the character is alive OR has a shield (logic). Use at least one operator from each group: arithmetic, comparison, logic. Output the result via std::cout.

13Terms

TermMeans
Operatora symbol like +, ==, && that computes a new value from one or more operands
Integer divisiondivision of int by int, where the fractional part of the result is discarded
Operator precedencethe order in which operators are evaluated in a single expression without parentheses
Short-circuit evaluationskipping evaluation of the second operand of &&/|| when the result is already clear from the first

14What you can do now

Before this lesson, variables could only be stored and output. Now you can compute with them:

  • You can predict the result of integer division int / int and explain why, for an exact result, you need to cast one operand to double before the division, not after.
  • You can turn any condition of the form "A is greater/equal/less than B" into an expression of type bool using comparison operators.
  • You can combine several bool conditions into one via &&/||/! and predict when short-circuit evaluation will kick in.
  • You can spot a typo of = instead of == in someone else's or your own code and explain why the compiler doesn't catch it.
  • You can apply the operator precedence table to predict the evaluation order of a mixed expression of arithmetic, comparison, and logic — or add parentheses if you're unsure.

15Summary

  • + - * / % — arithmetic; / for two ints discards the fractional part; for an exact result, cast to double before division.
  • % — remainder of integer division.
  • == != < > <= >= — comparison, the result is always bool.
  • && || ! — logical operators on bool; &&/|| evaluate with short-circuit evaluation.
  • = (assignment) and == (comparison) are different operators; confusing them is not always caught by the compiler.

Now you can compute a bool condition like canProceed — but you still can't do anything with it depending on the result. The next lesson introduces if/else: the very thing this whole lesson about comparison and logic was building toward.

🎮 How does this relate to Unreal Engine?

All these operators are part of the C++ language itself and work in Unreal code unchanged. For more complex math (powers, roots, min/max, range clamping), Unreal adds the FMath library on top of the standard operators:

Example (Unreal C++)C++
float clampedHp = FMath::Clamp(hpAfterHit, 0.0f, 100.0f);   // keeps HP from going outside 0..100
float dist = FMath::Sqrt(FMath::Square(dx) + FMath::Square(dy));  // distance between points

And for vector quantities (position, velocity, direction — triples {X, Y, Z}), the same arithmetic and logical operators work through the FVector type, which overloads them:

Example (Unreal C++)C++
FVector velocity = FVector(1.0f, 0.0f, 0.0f) * speed;   // the same *, but for a vector
bool reachedGoal = FVector::Dist(currentPos, goalPos) < 50.0f;   // the same <

The basic + - * / % == && || and the precedence between them remain exactly the same as in this lesson — Unreal just adds types and functions on top of them. Details on FMath and FVector are in Stage 07+.