SEN2001 · Week 6 · Expressions and Assignment Statements

Expressions and assignment: precedence, side effects, short-circuit, coercion

Operator precedence and associativity, operand evaluation order and functional side effects, short-circuit evaluation, mixed-mode expressions and type conversions, forms of assignment. The source of the “What is printed?” questions on the exam.

arithmetic expressionoperator precedenceoperator associativityoperand evaluation orderfunctional side effectreferential transparencyoperator overloadingmixed-mode expressioncoerciontype conversionwideningnarrowingcastrelational expressionshort-circuit evaluationconditional expressioncompound assignmentunary assignmentmultiple assignment

Expressions are the basic tool of computation in a program. The exam counterpart of this topic is one sentence: “What is printed?”. Someone who knows the rules of precedence, associativity, evaluation order, short-circuiting and type conversion runs the code in their head. Someone who doesn’t guesses and loses.

Golden rules:

  1. First precedence, then associativity. Operators at the same level generally go left to right; **, ?: and = go right to left.
  2. Java evaluates operands from left to right. C and C++ do not guarantee this.
  3. && and || do not evaluate the right side unless necessary. & and | always do.
  4. x++ gives the old value, ++x gives the new value.
  5. A narrowing conversion in Java happens only with a cast.

Arithmetic expressions: precedence and associativity

Operator precedence determines in which order different operators are applied. Associativity determines the order of operators at the same precedence.

Level (high → low)OperatorsDirection
1** (Ruby, Python, Fortran)right
2unary + -, ++ --, !right (prefix)
3* / %left
4binary + -left
5< <= > >=, then == !=left
6&&, then ||left
7?:right
8= += -= ...right
System.out.println(8 - 3 * 2 + 10 / 4);   // 8 - 6 + 2
System.out.println(100 / 5 / 2 * 3);      // ((100/5)/2)*3
int a = 4, b = 9;
System.out.println(a > b ? a : b > 5 ? b * 2 : b);
4
30
18

In Python 2 ** 3 ** 2 = 2 ** 9 = 512 (associates to the right) and -3 ** 2 = -9 (** is higher than unary minus). In JavaScript too, 2 ** 3 ** 2 gives 512. APL has no precedence, everything goes from right to left: a + b * c - d = a + (b * (c - d)).

Operand evaluation order and side effects

If an operand is a variable, its value is read from memory; if it is a function call, the function runs. If the function changes another operand, the order matters. This is called a functional side effect: a function changing a parameter or a nonlocal (global) variable.

static int g = 4;
static int bump() { g = g + 3; return 2; }

int x = g * bump();   // g is read first: 4 * 2
g = 4;
int y = bump() * g;   // bump first: g = 7, then 2 * 7
System.out.println(x + " " + y + " " + g);
8 14 7

In mathematics multiplication is commutative; here it is not. Because Java and C# guarantee left to right, the result is definite. C and C++ do not specify the order: the same code may give different results with different compilers.

The language designer has two options:

  1. Ban functional side effects: no two-way parameters and no nonlocal access in functions. Flexibility dies.
  2. Fix the evaluation order (what Java does). Some compiler optimizations (reordering operands) become impossible.

Referential transparency: if two expressions with the same value can be substituted for each other in a program, the program is referentially transparent. In pure functional languages (Haskell) this holds naturally because there are no variables or side effects; understanding and verifying the program gets easier.

Tracing increment operators

int c = 3;
int r = c++ * 2 + --c;     // 3*2 + 3
System.out.println(r + " " + c);
int k = 10;
k -= k++ - --k;            // k = 10 - (10 - 10)
System.out.println(k);
int p = 1;
p += p++ + ++p;            // p = 1 + (1 + 3)
System.out.println(p);
9 3
10
5

Recipe: from left to right, for each operand write “what is the value now, and what happened to the variable afterward?” In a compound assignment (k -= e), the value of the variable on the left is taken before e, so changes inside e are overwritten by the final assignment.

int[] arr = {5, 6, 7};
int idx = 0;
arr[idx++] = arr[idx] + 10;
System.out.println(arr[0] + " " + arr[1] + " " + idx);
16 6 1

Java first determines the target on the left (arr[0]), idx becomes 1; then the right side is arr[1] + 10 = 16.

Overloaded operators

Using the same symbol with more than one meaning: + in Java is both addition and String concatenation. In C, & is both bitwise AND and address-of; - is both binary subtraction and unary minus. User-defined overloading (C++, C#, Python) can improve readability (a + b for matrix addition) or kill it (a class that does subtraction with +). Java does not allow user-defined overloading.

Type conversions

  • Narrowing: the target type cannot hold all the values of the source (double → int, int → byte). Information may be lost.
  • Widening: the target type holds all the values (at least approximately) (int → double). Generally safe; but int → float can lose precision for large numbers.
  • Mixed-mode expression: operands of different types. The language either does coercion (implicit conversion) or gives an error.
  • Cast: an explicit conversion written by the programmer.
int n = 5;
System.out.println(n / 2 + n / 2.0);
System.out.println((byte) 200);
System.out.println((int) -7.9 + " " + (int) 7.9);
char ch = 'a'; ch += 2;
System.out.println(ch + " " + ('a' + 2));
4.5
-56
-7 7
c 99

Coercion makes writing easier but hides type errors (reliability drops). ML and F# do no coercion at all in expressions. Java does not do narrowing coercion in assignment: int g = 3.5; does not compile.

Relational and boolean expressions

Relational operators compare two operands and produce a boolean. C89 has no boolean type; 0 is false, others are true. So a < b < c is valid in C but does not do what you think:

console.log(1 < 2 < 3, 3 > 2 > 1);
console.log("5" == 5, "5" === 5, "5" + 1, "5" - 1);
true false
true false 51 4

3 > 2 > 1: (3 > 2) = true → true > 1 → 1 > 1 → false. JavaScript’s == operator does coercion, === does not. + concatenates when it sees a String, - converts to numbers. In Java 3 > 2 > 1 does not compile.

Short-circuit evaluation

If the result is determined by the first operand, the second is not evaluated. false && X is always false, true || X is always true.

int i = 2, j = 7;
boolean r1 = (i > 5) && (++j > 0);     // skipped
boolean r2 = (i < 5) || (j++ > 0);     // skipped
boolean r3 = (i < 5) | (j++ > 100);    // ran: j = 8
boolean r4 = (i > 5) & (++j > 0);      // ran: j = 9
System.out.println(r1 + " " + r2 + " " + r3 + " " + r4 + " " + j);
false true true false 9

Short-circuiting is needed not only for speed but also for correctness:

while (k < n && list[k] != key) k++;

When k == n, list[k] is outside the array. Thanks to short-circuiting it is never read. If you write the conditions in the reverse order, the protection is lost.

LanguageShort-circuit
C, C++, Java, C#&& || yes; & | no
Python, Ruby, Perl, JavaScriptAll logical operators
AdaThe programmer chooses: and then, or else

Forms of assignment

FormExampleNote
Simplea = bIn C = is assignment, == is equality; Ada and Pascal :=
Conditional target(flag ? a : b) = 0 (Perl)
Compounda += bFrom ALGOL 68 to C
Unarysum = ++countIncrement first, then assign. count++ assigns first
Assignment as an expressionwhile ((ch = getchar()) != EOF)Short but hard to read, side effect
Multiple(x, y) = (y, x) (Perl, Python)Swap
my ($x, $y) = (3, 8);
($x, $y) = ($y, $x);
print "$x $y\n";
8 3

PITFALL · if (x = 0) in C. Assignment is an expression and its value is 0. C accepts any arithmetic expression as a condition: it compiles, the condition is always false and x is zeroed. In Java the condition must be a boolean, and an int does not convert to boolean: compile error. Java chose reliability here.

Common mistakes

WrongCorrect
100 / 5 / 2 * 3 from the rightAssociates to the left: 30
?: associates to the leftTo the right: a ? b : c ? d : e = a ? b : (c ? d : e)
g * bump() = bump() * gDifferent if there is a side effect
C also guarantees left to rightIt does not (unspecified)
& short-circuitsIt does not, the j++ on the right runs
k -= k++ - --k → the inner change staysThe old value of k was taken first, the result is 10
(int) -7.9 = -8-7, truncated toward zero
3 > 2 > 1 is truefalse in JavaScript, compile error in Java

Test yourself

Exam-style questions. Solve them on paper first, then reveal the answer.

Question 1easy

What is printed in Java?

System.out.println(8 - 3 * 2 + 10 / 4);
Show answer
4

First * and / from left to right: 3 * 2 = 6, 10 / 4 = 2 (int division). Then - and + from left to right: 8 - 6 = 2, 2 + 2 = 4.

Question 2easy

What is printed in Java?

System.out.println(100 / 5 / 2 * 3);
Show answer
30

/ and * are at the same level and associate to the left: ((100 / 5) / 2) * 3 = (20 / 2) * 3 = 10 * 3 = 30. If you had gone from the right, it would be 100 / (5 / (2 * 3)) = 100 / 0: the direction of associativity changes the result completely.

Question 3medium

What is the value of m?

int a = 4, b = 9;
int m = a > b ? a : b > 5 ? b * 2 : b;
Show answer
18

The conditional operator (?:) associates to the right: a > b ? a : (b > 5 ? b * 2 : b). a > b is false → the second part. b > 5 is true → b * 2 = 18.

Question 4medium

What is printed in Java?

int i = 2, j = 7;
boolean r1 = (i > 5) && (++j > 0);
boolean r2 = (i < 5) || (j++ > 0);
boolean r3 = (i < 5) | (j++ > 100);
boolean r4 = (i > 5) & (++j > 0);
System.out.println(r1 + " " + r2 + " " + r3 + " " + r4 + " " + j);
Show answer
false true true false 9

r1: the left is false, && short-circuits, ++j does not run (j = 7). r2: the left is true, || skips the right (j = 7). r3: | does not short-circuit, j++ runs: 7 > 100 is false, j = 8; true | false = true. r4: & also evaluates both sides: ++j → 9; false & true = false. Final j = 9.

Question 5hard

What is printed in Java?

static int g = 4;
static int bump() { g = g + 3; return 2; }
// in main:
int x = g * bump();
g = 4;
int y = bump() * g;
System.out.println(x + " " + y + " " + g);
Show answer
8 14 7

Java evaluates operands from left to right. x: first g is read (4), then bump() makes g 7 and returns 2: 4 * 2 = 8. y: first bump(): g = 7, returns 2; then g is read: 7 → 2 * 7 = 14. g ends up 7. In mathematics g * bump() and bump() * g should have been the same; a functional side effect breaks this.

Question 6hard

What is printed in Java?

int c = 3;
int r = c++ * 2 + --c;
System.out.println(r + " " + c);
Show answer
9 3

From left to right: c++ gives the old value (3) and makes c 4. 3 * 2 = 6. --c decrements first: c = 3, value 3. 6 + 3 = 9. Final c = 3.

Question 7hard

What is printed in Java?

int[] arr = {5, 6, 7};
int idx = 0;
arr[idx++] = arr[idx] + 10;
System.out.println(arr[0] + " " + arr[1] + " " + idx);
Show answer
16 6 1

In an assignment Java first determines the location of the array element on the left: idx++ gives index 0, and idx becomes 1. Then the right side: arr[idx] = arr[1] = 6, + 10 = 16. arr[0] = 16. In C the result of this line is undefined (unspecified order).

Question 8medium

What is the value of d?

int n = 5;
double d = n / 2 + n / 2.0;
Show answer
4.5

n / 2: two ints, integer division: 2. n / 2.0: mixed-mode, n is coerced to double: 2.5. 2 + 2.5 = 4.5 (the int is widened to double).

Question 9medium

What is printed in Java?

System.out.println((byte) 200);
System.out.println((int) -7.9);
Show answer
-56 then -7

(byte) 200: a narrowing conversion, byte is -128..127. 200 - 256 = -56. (int) -7.9: the fractional part is dropped, toward zero: -7 (no rounding). Narrowing conversions can lose information, which is why Java does them only with an explicit cast.

Question 10medium

What does 3 > 2 > 1 evaluate to in JavaScript, and why? What happens in Java?

Show answer
false in JavaScript; compile error in Java

It associates to the left: (3 > 2) > 1 → true > 1. true is coerced to a number: 1 > 1 → false. It is similar in C (1 > 1 = 0). In Java a boolean cannot be compared with an int: compile error. Also 1 < 2 < 3 gives true in JavaScript, but not because it is "correct".

Question 11hard

What is printed in Java?

int k = 10;
k -= k++ - --k;
System.out.println(k);
Show answer
10

Compound assignment k -= e = k = k - (e), and the value of the left k is taken FIRST: 10. e: k++ → 10 (k = 11), --k → 10 (k = 10); 10 - 10 = 0. k = 10 - 0 = 10. The increments/decrements in between are overwritten by the final assignment.

Question 12easy

What does this Perl code print?

my ($x, $y) = (3, 8);
($x, $y) = ($y, $x);
print "$x $y\n";
Show answer
8 3

Multiple assignment: the list on the right is evaluated completely first (8, 3), then assigned to the left in order. A swap without a temporary variable. The same in Python and Ruby: x, y = y, x.

Verification: All Java answers were run with the JDK (java verify/SEN2001/W06.java); the JavaScript comparison/coercion examples with node (verify/SEN2001/06.mjs), Perl multiple assignment and logical operators with perl (verify/SEN2001/06.pl). 2 ** 3 ** 2 = 512 was run with node; since Python is not installed, the fact -3 ** 2 = -9 relies on the precedence table in the Python language documentation. The claim about C's unspecified evaluation order relies on the C standard and cannot be shown by running.

Review card

precedence: ** > unary > * / % > + - > comparison > && > || > ?: > = * / % same level, left: 100/5/2*3 = 30 ; ** and ?: and = RIGHT APL: no precedence, right to left. Parentheses override everything Java/C# evaluate operands LEFT TO RIGHT; C/C++ do not specify (unspecified) functional side effect: a function changes a global/parameter → g*f() != f()*g solution: ban side effects (loss of flexibility) OR fix the order (loss of optimization) referential transparency: expressions with the same value can be swapped (pure functional) && || short-circuit, & | (boolean) do not → the x++ on the right runs x++ old value, ++x new value. k -= e → the old value of k is taken FIRST widening (int→double) safe; narrowing (double→int, int→byte) needs a cast (int)-7.9 = -7 (byte)200 = -56 n/2 + n/2.0 = 2 + 2.5 coercion = implicit, cast = explicit. coercion lowers reliability (none in ML/F#) JS: 3 > 2 > 1 false, "5" == 5 true, "5" === 5 false, "5"+1 "51", "5"-1 4 Perl/Python multiple assignment: (x, y) = (y, x) swap