8 2 - 3 * 4 + has no brackets and no precedence, yet it has one answer. Watch a stack work it out.
Calculating with a stack
Numbers wait on the stack; each operator takes two and gives one back
bool eval_postfix(const char *s, int *result){ int stack[MAX]; int top = 0; for (size_t i = 0; s[i] != '\0'; i++) { char c = s[i]; if (c == ' ') continue; if (c >= '0' && c <= '9') { if (top == MAX) return false; stack[top] = c - '0'; top++; continue; } if (top < 2) return false; top--; int b = stack[top]; top--; int a = stack[top]; int r; if (c == '+') r = a + b; else if (c == '-') r = a - b; else if (c == '*') r = a * b; else if (c == '/' && b != 0) r = a / b; else return false; stack[top] = r; top++; } if (top != 1) return false; *result = stack[0]; return true;}int main(void){ const char *exprs[4] = { "8 2 - 3 * 4 +", "8 2 3 * - 4 +", "8 +", "8 2" }; for (int i = 0; i < 4; i++) { int v; if (eval_postfix(exprs[i], &v)) printf("%-14s= %d\n", exprs[i], v); else printf("%-14serror\n", exprs[i]); } return 0;}i is 0: evaluate "8 2 - 3 * 4 +".
Output
Describe this picture
Lines 6 to 45 (eval_postfix) and 47 to 58 (main) of eval.c, with the line about to run marked. Beside the code is a picture in two parts. At the top, labelled S, is the string as a row of character boxes, with narrow gaps for the spaces and the operators tinted. The character being read has a ring around it; those already read are dimmed. Below, labelled STACK, is eval_postfix’s int array stack standing upright, stack[0] at the bottom, cells 0 to 3 drawn and a dashed stub for the other twenty-eight. top is a bold line across it at the boundary above the last value, labelled “top” and its value; cells at or above top are dimmed but keep their numbers. The clip plays once, about 11 seconds. 8 and then 2 land on the stack. At -, the two cells are ringed and three lines appear beside the stack: “b = 2, the top”, “a = 8, under it” and “a - b = 8 - 2 = 6”; then 6 lands in stack[0], where the 8 was. 3 lands; * lifts 6 and 3 and writes “a * b = 6 * 3 = 18”; 18 lands. 4 lands; + lifts 18 and 4 and writes “a + b = 18 + 4 = 22”; 22 lands, alone on the stack. It is written to main’s v, the words “v = 22” appear, and the clip rests on the output 8 2 - 3 * 4 + = 22. Under the picture are one sentence on the current step and the output. The transport plays and pauses, steps one beat back or forward, and scrubs; a paused step is kept in the page address as calc.step.
Numbers wait, operators work
Infix to postfix turned (8 - 2) * 3 + 4 into 8 2 - 3 * 4 +. Working that out needs one stack and one rule for each kind of token:
- A number is pushed.
- An operator pops two values, works on them, and pushes the result.
- At the end, exactly one value is left: the answer.
There is no precedence to look up and no bracket to match. The rewriting into postfix has already put every operator just after the two values it works on, so the order of the tokens says it all.
The code and its cost
The function
bool eval_postfix(const char *s, int *result)
{
int stack[MAX];
int top = 0;
for (size_t i = 0; s[i] != '\0'; i++) {
char c = s[i];
if (c == ' ')
continue;
if (c >= '0' && c <= '9') {
if (top == MAX)
return false;
stack[top] = c - '0';
top++;
continue;
}
if (top < 2)
return false;
top--;
int b = stack[top];
top--;
int a = stack[top];
int r;
if (c == '+')
r = a + b;
else if (c == '-')
r = a - b;
else if (c == '*')
r = a * b;
else if (c == '/' && b != 0)
r = a / b;
else
return false;
stack[top] = r;
top++;
}
if (top != 1)
return false;
*result = stack[0];
return true;
}
The stack is an array of 32 ints with top counting the values, as in Stacks. c - '0' turns the character '8' into the number 8, because the digit characters are consecutive. The answer goes back through the pointer result; the function itself returns whether the string made sense.
Which value is on the left
The first value popped is b, the right operand, because it was pushed last. The second is a, the left one. For 8 2 - that gives 8 − 2 = 6. Pop them the other way round and - gives −6, and / goes wrong in the same way; + and * would hide the mistake, since their order does not matter.
Errors
| String | What goes wrong | Line |
|---|---|---|
8 + | + finds only one value | 22 |
8 2 | the end leaves two values, not one | 42 |
8 0 / | division by zero | 37 |
The program in the clip checks four strings. The first two are worked out, the last two are errors:
8 2 - 3 * 4 + = 22
8 2 3 * - 4 + = 6
8 + error
8 2 error
What it costs
Each character is read once, and each token costs a fixed few steps: a push, or two pops and a push. A string of n characters takes about n steps (O(n) in the notation of lesson 3.3). Joined to 8.3’s function, an infix expression goes in and its value comes out after two passes.
Running it yourself
gcc -std=c11 -Wall -Wextra -pedantic eval.c -o eval
./eval
Worked example
4 1 3 + 2 * + 5 -. The stack after each token:
| Token | 4 | 1 | 3 | + | 2 | * | + | 5 | - |
|---|---|---|---|---|---|---|---|---|---|
| Stack | 4 | 4 1 | 4 1 3 | 4 4 | 4 4 2 | 4 8 | 12 | 12 5 | 7 |
The answer is 7, and the stack is never more than three deep.
Order matters. 9 3 / is 9 / 3 = 3. 3 9 / is 3 / 9, which is 0 in integer division.
Back to infix. 8.3’s second output, 8 2 3 * - 4 +, is 2 × 3 = 6, then 8 − 6 = 2, then 2 + 4 = 6: the value of 8 - 2 * 3 + 4.
Questions. 5 2 - 4 * is (5 − 2) × 4 = 12. 8 + is an error because + has too few operands; 8 2 is an error of the other kind, a value left over.
Where it’s used
Calculators. Some calculators take their input in postfix, called reverse Polish entry: you key in 8, 2, then -, and the display shows the stack.
Virtual machines. Many languages compile programs into instructions for a stack machine, where push 8, push 2, subtract works exactly as here.
Page languages. PostScript, the printer language, and the drawing instructions inside PDF files are written in postfix and run on a stack.
What comes next. Working out an expression tree in postorder (12.3) is this same evaluation, with the tree in place of the string.
Reference card
| Code or idea | Meaning |
|---|---|
| number | push it |
| operator | pop b, then a; push a op b |
c - '0' | the digit’s value |
| end | exactly one value: the answer |
top < 2 at an operator / top != 1 at the end | too few operands / values left over |
| cost | one pass, about n steps (O(n), lesson 3.3) |
Platform: C11, gcc on x86-64 Linux: char 1 byte, int 4, little-endian. Division of ints rounds toward zero.