Basis Lab

Answers · How a program runs

Why does 2 + 3 * 4 equal 14 and not 20?

In shortThe multiplication is done first: three times four makes twelve, and only then is the two added, giving fourteen. Reading left to right would give twenty.

Fourteen, because the three and the four are combined before the two is added to anything. The multiplication produces a single number, and the addition is then applied to that number and the two. The order the characters appear in is not the order the operations happen in.

3×4=12,2+12=143 \times 4 = 12, \qquad 2 + 12 = 14

Where the grouping comes from

The grouping is fixed before any arithmetic is considered, by a small set of rules saying what may be made of what. Arithmetic in most languages is described by nine such rules over three kinds of thing: an expression, a term, and a factor.

FIG 1The shape rules for arithmetic
plaintext
expression  ->  expression + term
term        ->  term * factor
factor      ->  number

(the full set adds the minus, divide and bracket rules,
which do not change the argument)
Nine lines. Nowhere among them is a statement that multiplication outranks addition.

Fitting the token list to these shapes is what decides the answer. The two on the left of the plus is a factor, and a factor is a term, so the two is ready as the left side of an addition. The addition rule now needs a term on its right, and it waits. What follows is three times four: three is a factor, the star arrives, and the multiplication rule takes both three and four and turns them into one term. That term is twelve. Only now does the waiting addition have something to add, and what it adds is the finished product. Nothing consulted a list of priorities; the multiplication rule could complete while the addition rule was still holding, and that is the whole of the reason.

FIG 2The tree the rules produce for 2 + 3 * 4
Five nodes, three leaves. Depth reads as priority reversed: the deeper the node, the earlier it is evaluated.
nodes=2k−1=2(3)−1=5\text{nodes} = 2k - 1 = 2(3) - 1 = 5

The natural guess treats the line as a queue: take the two, take the three, apply the plus to get five, then take the four and multiply, giving twenty. That is genuine arithmetic and 5 * 4 really is 20, but it answers a different expression. It answers (2 + 3) * 4, the one where brackets force the addition into the innermost layer so it finishes first. Reading left to right assumes the flat list of tokens is the program, when the program is the tree, and the tree is built by which rule can complete soonest. The left-to-right answer is not a different opinion about the same sum; it is the value of a differently shaped tree.

The layering is the precedence

Look again at where the two operators sit. Addition's rule is written in terms of expressions and terms, the outer layer. Multiplication's rule is written in terms of terms and factors, one layer inside. An operator in a deeper layer binds tighter precisely because the outer rule cannot combine anything until the inner rule has collapsed its own operands into a single object. The priority table printed in a language manual is a summary of this layering, not a separate mechanism that runs alongside it. Swap the two lines and you swap the order of operations for every expression in every program written in that language.

FIG 3
written asthe operation at the root of the treevalue
2 + 3 * 4addition2 + (3 * 4) = 2 + 12 = 14
(2 + 3) * 4multiplication(2 + 3) * 4 = 5 * 4 = 20
2 + 3 * 4, layers swappedmultiplication(2 + 3) * 4 = 5 * 4 = 20

Brackets are the one thing that can override the layering, and they do it by giving the grammar a rule that lets a whole expression stand where a factor is expected. The bracketed expression has to be finished before it can be used as a factor, so it rises to the innermost position regardless of what operators it contains. The brackets themselves are absent from the finished tree: their only work was to force a shape, and once the shape exists they have no further role.

The layering is not a law of arithmetic, it is a choice made in the grammar, and choices differ. Exponentiation is the usual case where the self-reference in the rule sits on the right rather than the left, so it groups from the top down: 2 ^ 3 ^ 2 is 2 ^ 9 = 512, not 8 ^ 2 = 64. A language that placed multiplication in the outer layer would genuinely evaluate 2 + 3 * 4 as 20, and no arithmetic would be violated in the process, only a shape rule changed.

Worth keeping

  • Precedence is not a table consulted at run time; it is a consequence of which layer of the grammar an operator's rule sits in.
  • An operator whose rule can complete while another rule is still waiting ends up holding its operands closer together, which is what binding tighter means.
  • Brackets do not appear in the finished tree; they exist only to force a different shape while the tree is being built.

Where this goes next

This question is one step of Turning Text Into Instructions, a course that works through its subject from the beginning. The page that teaches this part of it is From Pieces to a Tree, and it reads the whole way through whether or not you have an account.

What an account changes is the practice: problems standing under every idea, marked with a reason rather than a tick, in a copy of the course that is yours.

Read that pageThe whole course

Other questions people ask about this

  1. Why does the compiler catch a type mismatch but not division by zero?A compiler checks types before running because types are known for every possible run; it cannot know a divisor is zero until the program actually runs.
  2. How many samples does a profiler need before a percentage in the report means anything?It depends on how many samples landed on that line: you need a few hundred samples on the line you care about, because the relative error is roughly one over the square root of that count.
  3. If I make one part of my program twice as fast, how much faster does the whole program get?Less than twice as fast: only the share of run time you actually touch can improve, so the untouched remainder caps the gain, often at a few per cent.
  4. Why does reading one byte from memory load 64 bytes?Memory hardware moves data in fixed sixty-four-byte blocks, so a one-byte read fetches the whole block; the extra bytes cost almost nothing because reaching the memory row costs most of the time.
  5. Why can two threads that each work correctly still give the wrong answer?Each thread reads the shared value and writes it back in two separate steps, so the other thread can run in between and one increment silently overwrites the other.
  6. My change made it 4% faster — how do I know that isn't just noise?You cannot tell from one pair of runs. Run the unchanged program ten times first: only then does a four per cent gap mean anything.

Read alongside