0002.j Expressions and operators
On this page
Inside every decision and every assignment is an expression, a combination of values, variables, and operators that the computer evaluates to a single value. Operators fall into three families, and the clearest way to understand them is by the types they take and the types they produce. Arithmetic operators take numbers and produce numbers. Relational operators take values, usually numbers, and produce a bool, TRUE or FALSE. Logical operators take bool values and produce a bool. The families therefore feed into one another in a fixed order: arithmetic computes quantities, comparisons turn quantities into true-or-false answers, and logic combines those answers into the conditions that drive the diamonds and if statements of 0002.i. This section follows that chain and ends with assignment, which stores an expression's result. The table under "Operators" in "From Python to typed pseudocode" lists the notation, and this section explains how the pieces behave.
Arithmetic#
The arithmetic operators are +, -, *, /, ^ for exponentiation, and % for modulo. Note that ^ always means an exponent, so 2 ^ 3 is 8. Each operator works on a matched pair, two int values or two float values. Python quietly converts 2 * 1.5, but in typed pseudocode an int and a float don't combine, so multiplying an int quantity by a float price is a type mismatch. When a program needs to mix them, it converts one value first, using a conversion function that a library provides and the code defines, such as Cast.inttofloat(qty) * price.
When an expression has several operators, they follow the usual order from mathematics, often remembered as PEMDAS: parentheses first, then exponents, then multiplication and division, then addition and subtraction. This order explains a common bug in averages. a + b + c / 3 divides only c, because division comes before addition, and the correct version groups the sum first, as in (a + b + c) / 3. Order also determines results like 4 * 5 ^ 2 + 3, where 5 ^ 2 is 25, multiplying by 4 gives 100, and adding 3 gives 103. When in doubt, add parentheses, which cost nothing and show the reader what you mean.
Integer division and modulo#
Division between two int values deserves special care, because the result must also be an int. In this notation, / on an int pair returns a rounded int, so 8 / 3 is 3, 175 / 3 is 58, and 23 / 4, which is 5.75, becomes 6. Python behaves differently: its / gives a float, as in 7 / 2 giving 3.5, and its // rounds down. The notation doesn't say how it rounds a tie such as 7 / 2. Because rounding happens at every division, the order of operations can change an answer. A percentage computed as correct / total * 100 with 17 correct out of 20 first rounds 17 / 20, which is 0.85, to 1, and then gives 100. Multiplying first, as in correct * 100 / total, gives 1700 / 20, which is exactly 85. Repeated division compounds the rounding: dividing 17 by 3 again and again gives 6, then 2, then 1.
The other half of whole-number division is the remainder, and the modulo operator % gives it for int values, so 17 % 5 is 2. Several common tests are built from it. A number is even exactly when dividing by 2 leaves no remainder, so n % 2 == 0 tests for an even number, which is how a web page might shade every even-numbered row of a table. Divisibility in general works the same way, as in a leap year rule: a year is a leap year if y % 4 == 0, except that a year with y % 100 == 0 must also have y % 400 == 0. Modulo also wraps values around, because a remainder after dividing by m is always between 0 and m - 1. A Caesar shift computes (x + 3) % 26, which always stays between 0 and 25. A rotation among four students uses i = (i + 1) % 4, so after index 3 it returns to 0. And on a 24-hour clock, a shift starting at hour 22 and lasting 5 hours ends at 27 % 24, which is 3. One case surprises people: when a non-negative a is smaller than b, b goes into a zero times, so a % b is a itself. Thus 5 % 12 is 5, while 12 % 5 is 2.
Comparisons#
Arithmetic produces numbers, and the relational operators turn them into answers. <, >, <=, and >= compare a matched int or float pair, and == (equal) and != (not equal) compare matched pairs of any type, including String and bool. All of them evaluate to a bool. Two consequences follow from these type rules. First, ordering comparisons aren't defined for strings, so "apple" < "banana", which Python allows, isn't valid here, and comparing values of different types with ==, such as an int with a String, gives ERROR. Second, comparison results can themselves be stored and compared, so if c holds TRUE and d holds FALSE, then c == d is FALSE.
The difference between > and >= decides whether a boundary value is included, which is where many errors start. A program that should print "pass" for a score of 60 or more needs score >= 60, since score > 60 would fail the student who scored exactly 60. The same reasoning applies to loop conditions: a countdown meant to show 10, 7, 4, and 1 uses for (int i = 10; i > 0; i = i - 3), because the condition holds for 1 and fails for -2.
Python allows chained comparisons such as 1 < x < 9, but this notation doesn't. Evaluated one operator at a time, 1 < x produces a bool, and a bool can't then be compared with 9 using <. Testing whether a value lies between two bounds requires two comparisons joined by a logical operator, which is the next step in the chain.
Logical operators#
Logical operators combine conditions. AND is true when all of its conditions are true, OR is true when at least one is true, and NOT(...) reverses a condition. A rule such as "children under 5 and residents over 64 ride free" is a combination of this kind, and getting it right depends on the order in which the parts are evaluated. In this notation, parentheses are evaluated first, and then the operators run strictly from left to right, with no precedence between AND and OR. This differs from Python, where and is evaluated before or.
01 output(TRUE OR FALSE AND FALSE)
This outputs >>> FALSE, because TRUE OR FALSE is TRUE, and then TRUE AND FALSE is FALSE. Python would give True. Consider a fare rule written as age < 5 OR age > 64 AND resident. For a four-year-old who isn't a resident, left to right gives TRUE OR FALSE, which is TRUE, and then TRUE AND FALSE, which is FALSE, so the child is wrongly charged. Parentheses attach the resident requirement to the seniors only: (age < 5) OR (age > 64 AND resident). Similarly, a condition such as i == 10 OR i > 3 AND i < 8 is evaluated as (i == 10 OR i > 3) AND i < 8, which is true for 4 through 7 and false for 10. The safe habit is to use parentheses whenever AND and OR appear together, so that the grouping is visible and doesn't depend on a rule the reader has to remember.
The choice between AND and OR also matters with negative conditions. A quiz that accepts only the answers 1 and 2 should reject a value only if it is neither, which is a != 1 AND a != 2. Writing a != 1 OR a != 2 gives a condition that is always true, because no value can equal both 1 and 2, so at least one of the two inequalities always holds.
Ranges#
The most common use of logical operators is testing a range. A value inside a range must satisfy both bounds at once, so the test uses AND, as in (x >= 1) AND (x <= 9). Using OR here would be true for nearly every value, since every number is either at least 1 or at most 9. Whether the endpoints are included depends on choosing >= and <= over > and <: a discount for ages 13 through 18 needs (age >= 13) AND (age <= 18), which includes both 13 and 18. A value outside a range is below the low end or above the high end, and it can't be both, so that test uses OR, as in (m < 1) OR (m > 12) for an invalid month. Equivalently, NOT((x >= 1) AND (x <= 9)) is true exactly for the values outside the range. Conditions like these combine naturally with loops, as when a program counts the students whose attendance is at least 90 and whose grade is at least 70, checking both arrays at the same index.
Assignment and equality#
Every expression above produces a value, and assignment is how a program keeps it. = assigns a value to a variable, while == tests whether two values are equal, and confusing them is one of the most common beginner errors. An account that should lock after the third failed login needs if (tries == 3). Writing if (tries = 3) isn't a test at all, and it is an error. Assignment also reads differently from algebra: x = x + 1 isn't an equation with no solution, but an instruction to compute the right side using the current value of x and then store the result in x. Each line uses whatever the variables hold at that moment, so if x is 2, then x = x * 2 followed by x = x * 2 leaves 8.
Assigning one variable to another copies the current value, as described for int under 0002.h. After int saved = score, later changes to score don't affect saved. This is why exchanging two values needs a temporary variable, as 0002.f showed: a = b overwrites a, so a following b = a just copies the new value back. The two operators can also appear in one line. bool y = x == 4 compares x with 4 and stores the bool result in y, leaving x unchanged.
These operators are the smallest pieces of an algorithm's logic. The next section steps back to the scale of whole algorithms, asking how to predict what they produce and how their costs grow.
- expression: a combination of values, variables, and operators that evaluates to a single value
- arithmetic operators:
+,-,*,/,^(exponent), and%(modulo), on matchedintorfloatpairs - integer division:
/on twointvalues, which returns a roundedint - modulo:
%, the remainder after dividingintvalues - relational operators:
<,>,<=,>=for matched numbers, and==,!=for matched values of any type, all producing abool - logical operators:
AND,OR, andNOT(...), evaluated after parentheses from left to right - assignment:
=, which stores a value in a variable - equality:
==, which tests whether two values are equal
Practice: practice questions for 0002.j