Why JavaScript ‘seems’ to get addition wrong


Introduction

JavaScript is a dynamic weakly-typed language so it’s possible to have expressions like this:

var foo = "string" + 22 * 3 - 4;

This post explains how JavaScript evaluates such complex ‘mix-n-matches’ and at the end of this, you should know why foo is NaN.

First, a screenshot showing more funny behaviour:

Addition and Subtraction
Addition and Subtraction

A brief Maths Refresher

Associativity

The result of the mathematical operation is always same regardless of the ‘consumption’ order of the operands during the operation. Associativity deals with the operators and is important in resolving situations that involve an operand between two operators. In the examples below, there is always a number between the two mathematical operators. Associativity rules remove the ambiguity that might arise in these situations.

Addition and multiplication are associative operations.

(1 + 2) + 3  = 1 + (2 + 3);
(1 * 2) * 3  = 1 * (2 * 3);

Side Note: Mathematical operations on floating point values (IEEE 794) suffer from rounding errors and can give funny results.

Non-associativity

Order matters, opposite of associativity. Operations could be left-associative or right-associative.

5 - 3 - 2 = (5 - 3) - 2; //left associativity
var a = b = 7; // a = (b = 7); //right associativity

Commutativity

The result of the mathematical operation is always the same regardless of the position of the operands. Commutativity, as opposed to associativity, focuses more on the operands – if swapping the place of  operands does not affect the result then it is commutative. As again, addition and multiplication are commutative (and associative as well) while division and subtraction are not.

1 + 2 = 2 + 1; //commutative

3 * 5 = 5 * 3; //commutative

1 - 2 != 2 - 1; //not commutative

Mathematics and Programming: The Interesting Divide

Operators can be overloaded in Mathematics and programming and in both cases the input values (i.e. operands) determine the right operation. For example, the multiplication symbol X can either signify pure arithmetic multiplication if both values are numbers or a vector cross-product if both inputs are vectors or even scalar vector multiplication. Similarly in programming, the + operator is usually overloaded to mean both addition and string concatenation, depending on context and usage.

Overloading has constraints; for example, the expression 1 + “boy” is invalid (and quite absurd) in the mathematics realm; operands have to be members of well-defined sets in other to get meaningful results.

Operators in strongly-typed programming languages, like their Mathematical counterparts, only allow operations on compatible types. Programmers have to explicitly coerce types to expected values if they want to mix and mash.

Weakly-typed languages offer no such restrictions, rather they attempt to automatically deduce the programmer’s intent and coerce values based on some heuristics. As expected, surprises occur when the language’s interpretation differs from the programmer’s intentions.

For example, consider the expression 1 + “2” in a weakly-typed programming language, this is ambiguous since there are two possible interpretations based on the operand types (int, string) and (int int).

  • User intends to concatenate two strings, result: “12”
  • User intends to add two numbers, result: 3

The only way out of the conundrum is the use of operator precedence and associativity rules – these determine the result.

How JavaScript adds numbers

Steps in the addition algorithm

  • Coerce operands to primitive values: The JavaScript primitives are string, number, null, undefined and boolean (Symbol is coming soon in ES6). Any other value is an object (e.g. arrays, functions and objects). The coercion process for converting objects into primitive values is described thus:
  1. If a primitive value is returned when object.valueOf() is invoked, then return this value, otherwise continue
  2. If a primitive value is returned when object.toString() is invoked, then return this value, otherwise continue
  3. Throw a TypeError

Note: For date values, the order is to invoke toString before valueOf.

  • If any operand value is a string, then do a string concatenation
  • Otherwise, convert both operands to their numeric value and then add these values

The case for the unary + operator

The unary + operator is quite different – it forcibly casts its single operand to a number.

//Cast to number

+"3";

//Convert to string

"" + 3;

The first case uses the unary operator which will convert the string to a number while the second case casts to a string by passing a string as one of the operands to the addition operator.

But what about the – operator?

Subtraction is great because it is not overloaded to signify other operations; when used, the intent is always to subtract the RHS from the LHS. Thus, both operands are converted to numbers and then subtraction is carried out on the numeric values. And this is why you can use the – operator to cast values too.

Trying to subtract a string of characters from another string of characters is undefined and you’ll always get a NaN.

"3" - "";

; 3

// Relying on implicit conversion in - operator

Examples

The table of coercions

First, a table showing the generated values from coercion operations. This makes it very easy to deduce the result of mix-n-mash expressions.

Primitive ValueString valueNumeric value
null“null”0
undefined“undefined”NaN
true“true”1
false“false”0
123“123”123
[]“”0
{}“[object Object]”NaN

Examples – The fun starts

Some examples, try to see if you can explain the results. Believe me, this is a fun fun ride. Enjoy!

1 + 2;

Output: 3
Why?: Addition of two numbers

'1' + 2;

Output: ’12’
Why?: Addition of a number and a string – both operands are converted to strings and concatenated.

2 - 1;

Output: 1
Why?: Subtraction of two numbers

'2' - 1;

Output: 1
Why?: Subtraction of a number from a string – both operands are converted into numeric values

2 - '1a';

Output: NaN
Why?: Subtraction of a string from a number – conversion of ‘1a’ into a number value gives NaN and any Maths op involving a NaN gives a NaN.

2 + null;

Output: 2
Why?: Addition of a number and the null primitive, numeric value of null primitive is 0 (see table of coercions). 2 + 0 is 2.

2 + undefined;

Output: NaN
Why?: Addition of a number and the undefined primitive – numeric value of undefined primitive is NaN (see table of coercions) and operations involving a NaN give a NaN.

2 + true;

Output: 3
Why?: Addition of a number and the true primitive – numeric value of true primitive is 1 (see table of coercions). 2 + 1 = 3.

2 + false;

Output: 2
Why?: Addition of a number and the false primitive – numeric value of the false primitive is 0 (see table of coercions). 2 + 0 = 2.

Math operations on JavaScript objects

The preceding part covered mostly primitives (with the exception of strings), now on to the big objects; pun intended.

First objects

2 + {};

Output: 2[object Object]
Why?: {}.toValue returns {} (which is not a primitive) so {}.toString() is invoked and this returns the string ‘[object Object]’. String concatenation occurs.

{} + 2;

Output: 2
Why?: This one is quite tricky I admit. JavaScript sees the {} as an empty execution block, so technically the above sample is equivalent to + 2 which is 2.

var a = {};
a + 2;

Output: [object Object]2
Why?: The assignment removes the ambiguity – JavaScript knows for sure it is an object literal. The rules of conversion follow as earlier described.

Math operations on JavaScript Arrays next!

2 + [];

Output: “2”
Why?: [].toValue returns the array (which is not a primitive) hence [].toString() is invoked and this returns the empty string. The operation is now 2 + “” and this results in string concatenation.

[] + 2;

Output: “2”
Why?: Same as above

Associativity and Evaluation

JavaScript + operator is left-associative, this means operands are evaluated from left to right when they occur more than once in a series. Thus 1 + 2 + 3 in JavaScript (being left-associative) will be evaluated as (1 + 2) + 3 and so on. You can read more here.

Now to the samples again!

1 + 2 + "3";

Output: “33”
Why?: left-associativity ensures this is (1 + 2) + “3”, which goes to 3 + “3”, giving 33

1 + "2" + 3;

Output: “123”
Why?: This will be evaluated as (1 + “2”) + 3, and then “12” + 3

"1" + 2 + 3;

Output: “Left as an exercise ;)”.
Why?: Share your answer in the comments.

Conclusion

This post was actually motivated by Gary Bernhardt’s very popular WAT talk, at this stage I hope you have gained the following:

  • Ability to fearlessly refactor JavaScript code that is lacking parentheses or has no clear operator/operand ordering.
  • A deeper understanding of how JavaScript evaluates expressions and operations on primitives and object types

Do let me know your thoughts in the comments!

Related Posts

20 thoughts on “Why JavaScript ‘seems’ to get addition wrong

    1. First, thanks for this great post, it made me clear about coercion, except {} and [] part, I couldn’t understood, please tell me more about .toValue and to.String, why object and array first try to evaluate .toValue and then later to .toString

      Like

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.