Be the first to know and get exclusive access to offers by signing up for our mailing list(s).

Subscribe

We ❤️ Open Source

A community education resource

15 min read

Build a dc-style calculator in C using a simple stack

A hands-on way to learn stacks, user input, and math operations at the command line.

Learning to program guide book cover
Free eBook by Jim Hall

This article is part of the eBook: Learning to program: A starter guide to stacks and command-line calculators, a free download from We Love Open Source.

The dc command is a handy desktop calculator that you can use to perform calculations at the command line or from a shell script. I use dc instead of my desktop calculator app when I’m working in a shell session and need to calculate a value. It’s just easier for me to run a quick dc calculation to add up a few numbers, or find the average value of something.

The dc program uses Reverse Polish Notation, where you first enter values into a stack, then let an operation like + or act on those values. Most operations use just the two most recently entered values, like 3 4 * to multiply 3 times 4, to get 12.

Read more: 10 open source tools you can start using today

How the stack works

This stack model is a basic programming concept. If you haven’t used a stack before, you can implement it using an array. There are several other ways to implement a stack, but an array is a pretty easy way to explain it.

Let’s demonstrate this with a stack that stores integers. We first start with an array, and set the “index” at -1, which is outside the range of the array but indicates that the stack is empty. (An index of zero would indicate the first entry in the array.) This sample defines a stack of ten items, but using a variable for the stack size lets you optionally create a much larger stack, if you need it:

int *stack;
size_t stack_size;
ssize_t stack_index;

int main()
{
    stack_index = -1;
    stack_size = 10;
    stack = calloc(stack_size, sizeof(int));

    if (stack == NULL) {
        puts("cannot allocate stack");
        return 1;
    }

    /* rest of your program here */

    free(stack);
    return 0;
}

To act on the stack, we need to create some helper functions. The basic stack operations include “pushing” a new value onto the stack, “popping” a value from the top of the stack, and “peeking” at the top value in the stack. Writing these “push” and “pop” functions is easier if we also define two other functions to report if the stack is full (to help us avoid a “stack overflow”) or empty (to avoid a “stack underflow”).

bool isfull_stack()
{
    return ((stack_index + 1) >= stack_size);
}

bool isempty_stack()
{
    return (stack_index == -1);
}

bool push_stack(int n)
{
    if (isfull_stack()) {
        fputs("stack overflow!\n", stderr);
        return false;
    }

    stack[++stack_index] = n;
    return true;
}

int pop_stack()
{
    if (isempty_stack()) {
        fputs("stack underflow!\n", stderr);
        return 0;                      /* fail */
    }

    return stack[stack_index--];
}

int peek_stack()
{
    if (isempty_stack()) {
        fputs("stack empty!\n", stderr);
        return 0;                      /* fail */
    }

    return stack[stack_index];
}

Each function to operate on the stack is fairly short because the stack is not very complicated. To push a new number on the stack, increment the “index” before storing the new value. As you pop values from the stack, decrement the “index” after each action. Peeking at the top value in the stack is simply the value at the current “index” in the array.

The full program to implement the stack and test it might look like this:

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>                   /* bool, true, false */

int *stack;
size_t stack_size;
ssize_t stack_index;

bool isfull_stack()
{
    return ((stack_index + 1) >= stack_size);
}

bool isempty_stack()
{
    return (stack_index == -1);
}

bool push_stack(int n)
{
    if (isfull_stack()) {
        fputs("stack overflow!\n", stderr);
        return false;
    }

    stack[++stack_index] = n;
    return true;
}

int pop_stack()
{
    if (isempty_stack()) {
        fputs("stack underflow!\n", stderr);
        return 0;                      /* fail */
    }

    return stack[stack_index--];
}

int peek_stack()
{
    if (isempty_stack()) {
        fputs("stack empty!\n", stderr);
        return 0;                      /* fail */
    }

    return stack[stack_index];
}

int main()
{
    stack_index = -1;
    stack_size = 10;
    stack = calloc(stack_size, sizeof(int));

    if (stack == NULL) {
        puts("cannot allocate stack");
        return 1;
    }

    puts("** push values onto the stack ..");

    for (int i = 1; !isfull_stack(); i++) {
        push_stack(i);
        printf("push: %d  ", peek_stack());
        printf("peek: %d\n", peek_stack());
    }

    puts("** pop values from the stack ..");

    while (!isempty_stack()) {
        printf("peek: %d  ", peek_stack());
        printf("pop: %d\n", pop_stack());
    }

    /* done */

    free(stack);
    return 0;
}

The program tests the stack functions by first pushing a list of integers onto the stack. Counting from 1, the for loop pushes each number until the stack is full. The isfull_stack() function returns a true value only when the stack is full, so the !isfull_stack() test returns true when the stack still has room for new values. That’s how the for loop is able to safely fill the stack with numbers, without overflowing the stack. After pushing each value, the loop also peeks at the top value in the stack so we can see that the program stored the correct value.

The program then pops each value from the stack using a while loop. The isempty_stack() function returns a true value when the stack is empty, so the !isempty_stack() test is true only if the stack still has values in it. Before popping a value, the program peeks at the top value so we can see that the popped value is what we expect.

If you save this program as stack.c then compile and run it, you should see the program push the numbers 1 to 10, then pop the values in reverse order from 10 to 1:

$ gcc -Wall -o stack stack.c
$ ./stack
** push values onto the stack ..
push: 1  peek: 1
push: 2  peek: 2
push: 3  peek: 3
push: 4  peek: 4
push: 5  peek: 5
push: 6  peek: 6
push: 7  peek: 7
push: 8  peek: 8
push: 9  peek: 9
push: 10  peek: 10
** pop values from the stack ..
peek: 10  pop: 10
peek: 9  pop: 9
peek: 8  pop: 8
peek: 7  pop: 7
peek: 6  pop: 6
peek: 5  pop: 5
peek: 4  pop: 4
peek: 3  pop: 3
peek: 2  pop: 2
peek: 1  pop: 1

Read more: A throwback experiment with Linux and Unix

A simple desktop calculator

We can implement a stack of floating point numbers to create a simple version of the dc desktop calculator. This implementation is just to demonstrate the stack; A full version of dc would require much more work because a calculator is more than just doing math on floating point values. Use man dc to see all the features of the dc desktop calculator, including the macro language and adjustable precision.

For this example, we’ll start with a few basic arithmetic operations, then add a few other stack operations. By keeping it simple, you should be able to see how a stack makes it easy to write programs that use one value at a time.

As a sample program, I don’t need to define a very large stack. I’ll create a stack of ten items, which means the “index” and “size” can use the short data type. Otherwise, the rest of the stack implementation is the same as before, except for float values instead of int:

float *stack;
short stack_size;
short stack_index;

bool isfull()
{
    return (stack_index + 1) == stack_size;
}

bool isempty()
{
    return stack_index == -1;
}

bool push(float f)
{
    if (isfull()) {
        puts("stack overflow");
        return false;
    }

    stack[++stack_index] = f;
    return true;
}

float pop()
{
    if (isempty()) {
        puts("stack underflow");
        return false;
    }

    return stack[stack_index--];
}

float peek()
{
    if (isempty()) {
        puts("stack empty");
        return 0.0;
    }

    return stack[stack_index];
}

To implement a calculator program, we need to read input from the user, and store values or apply operations. Reading user input can be dangerous; a user might enter a very short number like 1 or a very long number like 1.000000000000000000000000000000000. These are actually the same value, but one requires more memory if we read this as a string, which is a simple way to do it.

The fgets function is the classic way to safely read input from the user. However, fgets reads text into a string of a fixed size, which means we’d need to define the string to be large enough to accept whatever the user is likely to enter.

A more flexible way to read text is with the getline function, which is part of the C standard library. This reads input from the user into a string variable, similar to the fgets function, but with a key difference: As the user enters more text, getline dynamically adds more space to the string variable so it can store everything.

To use getline, define a string variable and initialize it with a NULL value. Also define a size, and set it to zero. When getline sees these values, it will allocate memory before reading data, and continue to resize the string variable as the user enters more data. Here’s a sample program to demonstrate how to use getline:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    char *line = NULL;
    size_t size = 0;
    ssize_t length;

    fputs("enter a string: ", stdout);
    length = getline(&line, &size, stdin);

    printf("size:%ld\n", size);
    printf("[%s]\n", line);
    printf("length:%ld\n", length);

    free(line);
    return 0;
}

The newline character at the end of the user’s input will be stored in the string. You can see this if you compile the program, run it, and type “Hello world”:

$ gcc -Wall -o line line.c
$ ./line
enter a string: Hello world
size:120
[Hello world
]
length:12

The string is 12 characters long (11 for “Hello world” plus 1 for the newline character). If the user instead pressed ctrl+d at the keyboard, then the getline function will return -1 for the length to indicate the end of input:

$ ./line
enter a string: size:120
[]
length:-1

We can use that return value in our dc implementation to recognize when the user pressed ctrl+d, then exit the program:

int main()
{
    char *inp = NULL;
    size_t size = 0;
    ssize_t len;

    stack_index = -1;
    stack_size = 10;
    stack = calloc(stack_size, sizeof(float));
    if (stack == NULL) {
        puts("out of memory?");
        return 1;
    }

    do {
        if ((len = getline(&inp, &size, stdin)) != -1) {

        /* rest of the program here */

        }
    } while (len != -1);

    /* done */

    free(stack);
    free(inp);

    return 0;
}

The user will need to enter values for the calculator to work with. As a simple implementation, let’s assume that each line starts with something we can recognize: If the first character is 0 to 9, assume it’s a number. If the first character is _, we’ll take that as a negative number; it’s easy enough to change that to a so we can convert the string to a number.

        if ((len = getline(&inp, &size, stdin)) != -1) {
            switch (inp[0]) {
            case '_':
                inp[0] = '-';
            case '.':
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
                push(strtof(inp, NULL));
                break;
            }
        }

We can expand the switch block to recognize other characters to perform certain actions: + to add, to subtract, * to multiply, and / to divide. To peek at the top value in the stack, enter p on a line by itself.

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

float *stack;
short stack_size;
short stack_index;

bool isfull()
{
    return (stack_index + 1) == stack_size;
}

bool isempty()
{
    return stack_index == -1;
}

bool push(float f)
{
...
}

float pop()
{
...
}

float peek()
{
...
}

int main()
{
    char *inp = NULL;
    size_t size = 0;
    ssize_t len;
    float x, y;

    stack_index = -1;
    stack_size = 10;
    stack = calloc(stack_size, sizeof(float));
    if (stack == NULL) {
        puts("out of memory?");
        return 1;
    }

    /* calculator */

    do {
        printf("[%d] ", stack_index + 1);

        if ((len = getline(&inp, &size, stdin)) != -1) {
            switch (inp[0]) {
            case '_':
                inp[0] = '-';
            case '.':
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
                push(strtof(inp, NULL));
                break;
            case '+':                 /* add */
                y = pop();
                x = pop();
                push(x + y);
                break;
            case '-':                 /* subtract */
                y = pop();
                x = pop();
                push(x - y);
                break;
            case '*':                 /* multiply */
                y = pop();
                x = pop();
                push(x * y);
                break;
            case '/':                 /* divide */
                y = pop();
                if ((y > -.00001) && (y < .00001)) {
                    puts("divide by zero");
                    push(y);
                }
                else {
                    x = pop();
                    push(x / y);
                }
                break;
            case 'p':                 /* peek */
                printf("%f\n", peek());
                break;
            default:
                puts("?");
                break;
            case 'q':                 /* quit */
            }
        }
    } while ((len != -1) && (inp[0] != 'q'));

    /* done */

    free(stack);
    free(inp);

    return 0;
}

This version of the program also displays the number of entries in the stack as a prompt for the user. The Unix dc command doesn’t actually display a prompt, but this printf line helps us to verify that the program is working the way we intend it to.

Save this as a new file; it’s not quite a dc replacement, so I saved my copy as rp.c (RP stands for Reverse Polish, which is the entry method that dc uses.) If we compile and run this program, we can perform basic arithmetic on floating point values:

$ gcc -Wall -o rp rp.c
$ ./rp
[0] 2
[1] 3
[2] 4
[3] +
[2] p
7.000000
[2] /
[1] p
0.285714
[1] q

By pushing the values 2, 3, 4 onto the stack, the stack gets set like this:

4
3
2

The top two values are 4 and 3. The + operation pops 4 then 3 from the stack, adds them, then pushes the answer 7 back onto the stack. We can print this value with the p command. This leaves the stack with these two values:

7
2

The final / operation pops 7 then 2, and divides the second value by the first value, or 2 divided by 7, to get 0.285714.

Read more: FreeDOS 1.4 is the retrocomputing system you’ve been waiting for

Cool tricks with the stack

Now that the program has a basic approximation of a calculator, we can add other features by manipulating the stack. For example, we can duplicate (d) the top item in the stack by calling the push() function with the return value of the peek() function. We can reverse (r) the order of the last two entries in the stack by popping two values, storing them, and pushing them back onto the stack in reverse order.

To print the full (f) contents of the stack, we can create a for loop to count “down” through the array. Or, we can clear (c) the contents of the stack with a while loop that pops the stack until the stack is empty.

With a little extra code that uses the stack in a thoughtful way, you can implement a bunch of other features, but let’s limit the new functionality so it’s easy to see. Here’s the updated main function that implements just the features I’ve described:

int main()
{
    char *inp = NULL;
    size_t size = 0;
    ssize_t len;
    float x, y;

    stack_index = -1;
    stack_size = 10;
    stack = calloc(stack_size, sizeof(float));
    if (stack == NULL) {
        puts("out of memory?");
        return 1;
    }

    /* calculator */

    do {
        /* printf("[%d] ", stack_index + 1); */

        if ((len = getline(&inp, &size, stdin)) != -1) {
            switch (inp[0]) {
            case '_':
                inp[0] = '-';
            case '.':
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
                push(strtof(inp, NULL));
                break;
            case '+':                 /* add */
                y = pop();
                x = pop();
                push(x + y);
                break;
            case '-':                 /* subtract */
                y = pop();
                x = pop();
                push(x - y);
                break;
            case '*':                 /* multiply */
                y = pop();
                x = pop();
                push(x * y);
                break;
            case '/':                 /* divide */
                y = pop();
                if ((y > -.00001) && (y < .00001)) {
                    puts("divide by zero");
                    push(y);
                }
                else {
                    x = pop();
                    push(x / y);
                }
                break;
            case 'd':                 /* duplicate */
                push(peek());
                break;
            case 'r':                 /* reverse */
                y = pop();
                x = pop();
                push(y);
                push(x);
                break;
            case 'p':                 /* peek */
                printf("%f\n", peek());
                break;
            case 'f':                 /* full stack */
                for (short i = stack_index; i >= 0; i--) {
                    printf("%f\n", stack[i]);
                }
                break;
            case 'c':                 /* clear */
                while (!isempty()) {
                    pop();
                }
                break;
            default:
                puts("?");
                break;
            case 'q':                 /* quit */
            }
        }
    } while ((len != -1) && (inp[0] != 'q'));

    /* done */

    free(stack);
    free(inp);

    return 0;
}

In this updated version, I’ve removed the prompt that displays the number of items in the stack. We can use the f command to examine the contents of the stack.

If you update the main function and compile the new program, you should find rp acts very similarly to dc. Let’s demonstrate with a single session that first calculates the average of a short list of numbers, using the r command to do proper division at the end. After clearing the stack, we’ll calculate “3 squared” using the d and * operations.

$ gcc -Wall -o rp rp.c
$ ./rp
3
2
4
5
f
5.000000
4.000000
2.000000
3.000000
+
+
f
11.000000
3.000000
r
f
3.000000
11.000000
/
p
3.666667
c
f
3
d
f
3.000000
3.000000
*
p
9.000000
q

The first line pushes the values 3 (the number of entries) then the next few lines push 2, 4, and 5. The f command shows the four values in the stack. After two + operations, the stack now holds 11 (the sum of the three numbers) and 3 (the count of entries). But they are in the wrong order to calculate the average; the r command swaps the values in the stack so we can use the / command to divide them. The average of the three numbers is 3.666667.

The c command clears the stack so we can start fresh; using f shows the stack is empty. I entered 3, then the d command to duplicate this. One more f command shows the stack has two “3” values, which I then multiply with the * command to get 9.

Read more: 10 open source tools you can start using today

From small beginnings

Writing this simple approximation of the dc command is a good exercise for how to use a stack in your programs. You can further modify this program with other features that work with the stack. At the same time, you’ll learn more about the dc command. For example, you might add % to calculate the remainder after division, ^ to perform exponentiation or powers, or v to calculate a square root. Exploring these features is a great way to get started with a new programming concept.

Download the free starter guide to command-line stacks

We simply ask you to provide an email address to get the free download.
Increase your open source IQ with our weekly newsletter, curated by humans who love open source

About the Author

Jim Hall is an open source software advocate and developer, best known for usability testing in GNOME and as the founder + project coordinator of FreeDOS. At work, Jim is CEO of Hallmentum, an IT executive consulting company that provides hands-on IT Leadership training, workshops, and coaching.

Read Jim's Full Bio

The opinions expressed on this website are those of each author, not of the author's employer or All Things Open/We Love Open Source.

Want to contribute your open source content?

Contribute to We ❤️ Open Source

Help educate our community by contributing a blog post, tutorial, or how-to.

We're hosting two world-class events in 2026!

Join us for All Things AI, March 23-24 and for All Things Open, October 19-20.

Open Source Meetups

We host some of the most active open source meetups in the U.S. Get more info and RSVP to an upcoming event.