Addition assignment += behavior in expression

JavascriptJavaLanguage LawyerCompound Assignment

Javascript Problem Overview


Recently I came across this question: https://stackoverflow.com/questions/50673012/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?

Javascript Solutions


Solution 1 - Javascript

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.

Solution 2 - Javascript

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)
    

Solution 3 - Javascript

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Question11thdimensionView Question on Stackoverflow
Solution 1 - Javascriptk_ssbView Answer on Stackoverflow
Solution 2 - JavascriptNikhil AggarwalView Answer on Stackoverflow
Solution 3 - JavascriptSheshank S.View Answer on Stackoverflow