59

Recently I came across this question: Assignment operator chain understanding.

While answering this question I started doubting my own understanding of the behavior of the addition assignment operator += or any other operator= (&=, *=, /=, etc.).

My question is, when is the variable a in the expressions below updated in place, so that its changed value is reflected in other places in the expression during evaluation, and what is the logic behind it? Please take a look at following two expressions:

Expression 1

a = 1
b = (a += (a += a))
//b = 3 is the result, but if a were updated in place then it should've been 4

Expression 2

a = 1
b = (a += a) + (a += a)
//b = 6 is the result, but if a is not updated in place then it should've been 4

In the first expression, when the innermost expression (a += a) is evaluated, it seems that it doesn't update value of a, thus the result comes out as 3 instead of 4.

However, in the second expression, the value of a is updated and so the result is 6.

When should we assume that a's value will be reflected in other places in the expression and when should we not?

k_ssb
  • 6,024
  • 23
  • 47
11thdimension
  • 10,333
  • 4
  • 33
  • 71

3 Answers3

87

Remember that a += x really means a = a + x. The key point to understand is that addition is evaluated from left to right -- that is, the a in a + x is evaluated before x.

So let's figure out what b = (a += (a += a)) does. First we use the rule a += x means a = a + x, and then we start evaluating the expression carefully in the correct order:

  • b = (a = a + (a = a + a)) because a += x means a = a + x
  • b = (a = 1 + (a = a + a)) because a is currently 1. Remember we evaluate the left term a before the right term (a = a + a)
  • b = (a = 1 + (a = 1 + a)) because a is still 1
  • b = (a = 1 + (a = 1 + 1)) because a is still 1
  • b = (a = 1 + (a = 2)) because 1 + 1 is 2
  • b = (a = 1 + 2) because a is now 2
  • b = (a = 3) because 1 + 2 is 3
  • b = 3 because a is now 3

This leaves us with a = 3 and b = 3 as reasoned above.

Let's try this with the other expression, b = (a += a) + (a += a):

  • b = (a = a + a) + (a = a + a)
  • b = (a = 1 + 1) + (a = a + a), remember we evaluate the left term before the right one
  • b = (a = 2) + (a = a + a)
  • b = 2 + (a = a + a) and a is now 2. Start evaluating the right term
  • b = 2 + (a = 2 + 2)
  • b = 2 + (a = 4)
  • b = 2 + 4 and a is now 4
  • b = 6

This leaves us with a = 4 and b = 6. This can be verified by printing out both a and b in Java/JavaScript (both have the same behavior here).


It might also help to think of these expressions as parse trees. When we evaluate a + (b + c), the LHS a is evaluated before the RHS (b + c). This is encoded in the tree structure:

   +
  / \
 a   +
    / \
   b   c

Note that we don't have any parentheses anymore -- the order of operations is encoded into the tree structure. When we evaluate the nodes in the tree, we process the node's children in a fixed order (i.e., left-to-right for +). For instance, when we process the root node +, we evaluate the left subtree a before the right subtree (b + c), regardless of whether the right subtree is enclosed in parentheses or not (since the parentheses aren't even present in the parse tree).

Because of this, Java/JavaScript do not always evaluate the "most nested parentheses" first, in contrast to rules you might have been taught for arithmetic.

See the Java Language Specification:

15.7. Evaluation Order

The Java programming language guarantees that the operands of operators appear to be evaluated in a specific evaluation order, namely, from left to right.
...

15.7.1. Evaluate Left-Hand Operand First

The left-hand operand of a binary operator appears to be fully evaluated before any part of the right-hand operand is evaluated.

If the operator is a compound-assignment operator (§15.26.2), then evaluation of the left-hand operand includes both remembering the variable that the left-hand operand denotes and fetching and saving that variable's value for use in the implied binary operation.

More examples similar to your question can be found in the linked part of the JLS, such as:

Example 15.7.1-1. Left-Hand Operand Is Evaluated First

In the following program, the * operator has a left-hand operand that contains an assignment to a variable and a right-hand operand that contains a reference to the same variable. The value produced by the reference will reflect the fact that the assignment occurred first.

class Test1 {
    public static void main(String[] args) {
        int i = 2;
        int j = (i=3) * i;
        System.out.println(j);
    }
}

This program produces the output:

9

It is not permitted for evaluation of the * operator to produce 6 instead of 9.

k_ssb
  • 6,024
  • 23
  • 47
  • 1
    I don't understand the reasoning behind evaluating from left to right in presence of higher precedence operator that is `()`, More over the first expression can simple be written as `b = a += a += a`, which should be evaluated from right to left, because of the right associativity of assignment operator. – 11thdimension Jun 15 '18 at 07:07
  • 2
    @11thdimension That's just how the language works. Suppose we want to evaluate `a + (b + c)`. The LHS `a` is evaluated first, regardless of whether the RHS is enclosed in parentheses or not. Then the RHS `(b + c)` is evaluated. This might go against the "rules" in arithmetic where we evaluate the "innermost" parentheses first. Instead, it's helpful to think of these computations as a parse tree, which eliminates all parentheses and bakes in the order of operations into the tree structure. – k_ssb Jun 15 '18 at 07:11
  • I don't think that in `a + (b + c)` evaluation would start from left to right, as the postfix notation would be `abc++` which would mean `b+c` would be evaluate first. – 11thdimension Jun 15 '18 at 07:20
  • 1
    @11thdimension In postfix notation, the value of `a` is _pushed to the stack first_, then the value of `b`, then `c`. Then we add together `b` and `c`, and finally add `a` to that result. This is consistent with my claims: `a` is "evaluated" when its value is pushed to the stack. If `a` were instead `(x + y)`, then the postfix notation is `xy+bc++`, and notice how the sum `x+y` is computed first. The language is consistent with evaluation order, whether `a` is just a single variable or an expression like `(x + y)`. – k_ssb Jun 15 '18 at 07:24
  • You're right, now the answer seems to be coming together. Missing piece was that a variable is evaluated as soon as it's pushed back on Stack after it's processed by parser. Which all other answers have conceptualized as language specific guarantee of processing from left to right. Secondly, inner most brackets would be evaluated first that's correct as this bracket's operator will occur first in postfix, however that doesn't stop parser from replacing previously processed variables with their value after they've been parsed. – 11thdimension Jun 15 '18 at 07:44
  • @11thdimension: it's important to not make confusion between *associativity and precedence* in the operators and the *evaluation order*. For example in `a() + b()*c()` you can have order of evaluation `a→b→c`; that the multiplication is performed before the addition is irrelevant. – 6502 Jun 20 '18 at 08:16
7

Following are the rules that need to be taken care of

  • Operator precedence
  • Variable assignment
  • expression evaluation

    Expression 1

    a = 1
    b = (a += (a += a))
    
    b = (1 += (a += a))  // a = 1
    b = (1 += (1 += a))  // a = 1
    b = (1 += (1 += 1))  // a = 1
    b = (1 += (2))  // a = 2 (here assignment is -> a = 1 + 1)
    b = (3)  // a = 3 (here assignment is -> a = 1 + 2)
    

    Expression 2

    a = 1
    b = (a += a) + (a += a)
    
    b = (1 += a) + (a += a) // a = 1
    b = (1 += 1) + (a += a) // a = 1
    b = (2) + (a += a) // a = 2 (here assignment is -> a = 1 + 1)
    b = (2) + (2 += a) // a = 2 (here here a = 2)
    b = (2) + (2 += 2) // a = 2
    b = (2) + (4) // a = 4 (here assignment is -> a = 2 + 2)
    b = 6 // a = 4
    

    Expression 3

    a = 1
    b = a += a += a += a += a
    
    b = 1 += 1 += 1 += 1 += 1 // a = 1
    b = 1 += 1 += 1 += 2 // a = 2 (here assignment is -> a = 1 + 1)
    b = 1 += 1 += 3 // a = 3 (here assignment is -> a = 1 + 2)
    b = 1 += 4 // a = 4 (here assignment is -> a = 1 + 3)
    b = 5 // a = 5 (here assignment is -> a = 1 + 4)
    
Nikhil Aggarwal
  • 28,197
  • 4
  • 43
  • 59
  • 2
    It really doesn't make sense to write `1 += 2` or anything similar, since `1` isn't a variable that can be assigned to. – k_ssb Jun 15 '18 at 06:41
  • @pkpnd - True. However, the above is for sake of understanding. – Nikhil Aggarwal Jun 15 '18 at 06:42
  • 6
    Well to me it makes your answer less understandable because it's not obvious what you mean by `1 += 2` (since that isn't valid code). – k_ssb Jun 15 '18 at 06:43
  • Shouldn't inner braces be evaluated first? I would assume it's using a Stack, meaning it doesn't make sense to start evaluation from left in presence of unevaluated braces. – 11thdimension Jun 15 '18 at 06:43
  • @11thdimension Yes it's a stack, but in the case of `(a + (b + c))` the `a` is evaluated first, then `(b + c)` is pushed to the stack, then `b` is evaluated, and lastly `c`. See my answer, it makes this process more clear – k_ssb Jun 15 '18 at 06:46
  • @11thdimension - Inner braces are evaluated first. However, there is a difference between variable assignment and expression evaluation. The variables are replaced with the actual value in order. e.g. `a+=1` is `a = a+1`. So, first the variable `a` is replaced by its value and then the addition is performed. – Nikhil Aggarwal Jun 15 '18 at 06:47
  • It still doesn't make sense how `b = (1 += (2)) // a = 2` becomes `b = (3) // a = 3` -- if the first expression has no `a` anywhere, how did the value of `a` suddenly change? @NikhilAggarwal your answer will be more understandable if you can fix this source of confusion. – k_ssb Jun 15 '18 at 06:57
  • @pkpnd - You are right. I have updated. Thanks mate :) – Nikhil Aggarwal Jun 15 '18 at 07:07
1

It just uses a variation of order of ops.

If you need a reminder of order of ops:

PEMDAS:

P = Parenthesis

E = Exponents

MD = Multiplication/Division

AS = Addition/Subtraction

The rest left to right.

This variation just is read left to right, but if you see a parenthesis do everything inside it, and replace it with a constant then move on.

First ex:

var b = (a+=(a+=a))

var b = (1+=(1+=1))

var b = (1+=2)

var b = 3

Second ex:

var b = (a+=a)+(a+=a)

var b = (1+=1)+(a+=a)

var b = 2 + (2+=2)

var b = 2 + 4

var b = 6

var a = 1
var b = (a += (a += a))
console.log(b);

a = 1
b = (a += a) + (a += a)
console.log(b);

a = 1
b = a += a += a;
console.log(b);

The last one b = a += a += a since there are no parenthesis, it automatically becomes b = 1 += 1 += 1 which is b = 3

Sheshank S.
  • 3,053
  • 3
  • 19
  • 39
  • In the first expression you've replaced a's value with 1 in all occurrences at the same time, however in the second expression it's done one at a time and each time last calculated value is used, that's the question what is the rule for doing that, thanks – 11thdimension Jun 15 '18 at 06:08
  • @11thdimension In the first expression i replaced all a's values with 1 in the first parenthesis set. – Sheshank S. Jun 15 '18 at 06:10
  • 1
    @11thdimension Basically whenever the code goes to a point with "a" it replaces it with the current value of a. before it reaches to the next place in cases with parenthesis, a changes therefore when the code reaches a is different. does that make sense? – Sheshank S. Jun 15 '18 at 06:10
  • @11thdimension For example with `b = a += (a +=a )` it starts with ` b = 1 += (a += a)` that is the first a it sees. then it goes to the next one `b= 1 += (1+=a)` then the next one `b= 1+= (1+=1)` then solves. – Sheshank S. Jun 15 '18 at 06:12
  • why would it happen in this way for one expression not for the other, meaning why is a's value replaced with current value in all the braces in first expression, which it's delayed in the second one? – 11thdimension Jun 15 '18 at 06:13
  • for `b=(a+=a)+(a+=a)` it starts like this `b=(1+=a)+(a+=a)` then `b=(1+=1)+(a+=a)` then before moving on does the value in the parenthesis (also setting a to that value) so now `a=2` and `b=2+(a+=a)` then it moves on to `b=2+(2+=a)` then `b=2+(2+=2)` making `b=4` Does that help? – Sheshank S. Jun 15 '18 at 06:14
  • @11thdimension Because of the parenthesis. the code runner reads from left to right, **but** if it sees parenthesis it starts solving that part then moves on. – Sheshank S. Jun 15 '18 at 06:15
  • @11thdimension Does that make sense? – Sheshank S. Jun 15 '18 at 06:16
  • It doesn't, that's the question. Sorry I also banged my head against the wall for hours. Let me explain, `(a += (a += a))` is a nested braces expression `( () )` and I expect that almost all compilers would use a stack to solve this, meaning inner braces will the on top of the stack and thus would be solved first. That means expression in the outer braces would have to wait for inner expression to be evaluated. After inner expression is evaluated, a's value should be `2`, but it's old value is used to evaluate the outer expression, question is what's the special rule here – 11thdimension Jun 15 '18 at 06:20