This is a reverse polish notation (ie stack-based) calculator program written in ANSI C. It uses a dynamically allocated stack, so it can hold any amount of data in the stack up to the memory limits of the computer you are running on. It is currently using double-precision floating point operations although I might write a version using GMP (GNU multi-precision library) instead which will be much more accurate.
Overview:
The source code is split up into several files – here is a description of the functionality handled by each of these files:
rpn.h – Header file with function prototypes and a few #DEFINES.
main.c – Contains the main loop, which reads input from the keyboard, does the requested operation, and then loops.
stack.c – This is my implementation of a LIFO (last-in-first-out) stack. It provides functions for pushing data onto the stack and poping data off it. It also handles growing, shrinking, printing and duplicating the stack. Internally the stack is represented by a dynamically allocated array, and a stack pointer to store the current size of the stack.
getop.c – This file contains the getop() function, which reads data from the keyboard, and interprets it. It is called from the main loop.
inputbuf.c – This file handles input buffering. Since this program will use the getche() function if available to read unbuffered input from the keyboard (so operations happen immediately without needing to push enter), we need to provide our own input buffer for situations where a operator is entered as the terminating character for a entered number.
help.c – This function provides a function to print the help for the program.
util.c – A few miscellaneous functions – clear() for clearing the screen, radToDegrees() and degreesToRad() for converting angles.
This program provides access to much of the standard libraries’ math functions, like sin, cos, tan and sqrt.
The biggest challenge in writing this was in the input handling. I wanted operations to happen immediately as on my Hp 50g, rather than having to be typed and then enter pushed to get the calculator to read the input. This is not provided for by the ANSI standard, so my program currently detects if it is being compiled on windows, and if it is, uses the getche() function which does allow for unbuffered input. Also handling multiple character function names was tricky, since when the user types the first letter of, say ‘sin’ you don’t know weather they meant to swap the elements on the stack, or are wanting to do the sine function. So, all multiple-character functions must be entered starting with a exclamation mark.
Another thing not provided for in the ANSI standard is clearing the screen – so I have written a clear() command which tries to clear the screen by running the cls or clear commands through system(). This will work for windows and unix platforms, which is fairly comprehensive but not ideal.
Documentation:
Operators: +, – , * , /
% – Modulus. Works on floating points.
Enter – duplicate item at bottom of stack
n – Make number on bottom of stack negative
b or backspace – Remove number on bottom of stack
c – Clear stack
w – Print current size of stack array
s – Swap bottom 2 elements
Math Functions:
!sin – Sine (Radians)
!cos – Cosine (Radians)
!tan – Tangent (Radians)
Add an a to the beginning of trigonometric function for the inverse.
Add an d to the end to do a trigonometric function in degrees.
^ – Power function.
sqrt – Square Root.
!! – Factorial.
q – Quit
Abridged source code: (the dotted lines are where repetitive code has been removed)
main.c:
#include "rpn.h" int main (void) { int type; double op1; double op2; char s[MAXOP]; allocateStack(10); printf("Type h and press enter for help.\n"); while ((type= getop(s))) { switch (type) { case NUMBER: push (atof(s)); printStack(); break; case FUNCTION: if(strcmp(s,"sin") == 0) { push(sin(pop())); } else if (strcmp(s,"cos") == 0) { push(cos(pop())); } else if (strcmp(s,"tan") == 0) { push(tan(pop())); } .............................. else if (strcmp(s,"!") == 0) { op1 = floor(pop()); for (op2=op1-1; op2>0; op2--) { op1 = op1 * op2; } push(op1); } printStack(); break; case '+': push(pop() + pop()); printStack(); break; case '*': push(pop() * pop()); printStack(); break; case '-': op2 = pop(); push(pop() - op2); printStack(); break; case '^': op1 = pop(); op2 = pop(); push(pow(op2,op1)); printStack(); break; case '/': op2=pop(); if(op2 != 0.0) push(pop()/op2); else printf("Zero devisor error, yo"); printStack(); break; case 'n': push(0 - pop()); printStack(); break; case 'b': pop(); printStack(); break; case '%': op1 = pop(); op2 = pop(); push(op2-((int)(op2/ op1)*op1)); printStack(); break; case ' ': dup(); printStack(); break; case 'q': exit(0); break; case 's': //Swap first 2 elements op1 = pop(); op2 = pop(); push(op1); push(op2); printStack(); break; case 'w': printf("Stack Size: %d\n", getStackSize()); break; case 'c': clearStack(); printStack(); break; case 'f': setClear(1); break; case 'g': setClear(0); break; case 'h': printHelp(); break; default: printf("unknown command %s.", s); break; } } return 0; } |
stack.c:
#include "rpn.h" void freeStack(void); int stackPosition =0; int stackSize = 0; double* stack; int doClear = 1; int print = 1; void allocateStack(int size) { double* stacknew; if(stackSize == 0) { //We are allocating the initial stack. stacknew = malloc(size * sizeof(double)); } else { //We are growing/shrinking the stack stacknew = realloc(stack, size * sizeof(double)); if(stackPosition > size) { stackPosition = size; } } if (stacknew == NULL) { if (stackSize != 0) { free(stack); } printf("Error: Could not allocate memory for stack of size %d",size); exit(1); } else { //Memory allocation succeded stack = stacknew; stackSize = size; } } /* push value onto stack */ void push(double newValue) { if(stackPosition < stackSize) { stack[stackPosition++] = newValue; } else { allocateStack(stackSize+5); stack[stackPosition++] = newValue; } } double pop(void) { double value; if(stackPosition > 0) {// If there is data on the stack. stackPosition--; value = stack[stackPosition]; freeStack(); return value; } else { printf("error : pop() called, but stack empty"); return 0.0; } } void dup(void) { if(stackPosition > 0) {// If there is data on the stack. push(stack[stackPosition-1]); } } void clearStack(void) { stackPosition = 0; freeStack(); } int getStackSize(void) { return stackSize; } void setClear(int newDoClear) { doClear = newDoClear; } void setPrint(int newPrint) { print = newPrint; } void freeStack(void) { if((stackSize - stackPosition) > 15) { //Reduce the size of the stack to free some memory. allocateStack(stackPosition+5); } } void printStack(void) { int i,position=stackPosition+1; if(doClear) { clear(); } if(print) { for (i=0; i |
getop.c:
#include "rpn.h" int getop(char s[]) { int i=0,c,d; while ((s[0] = c = getchb()) == ' ' || c =='\t') ; s[1] = '\0'; if(!isdigit(c) && c != '.' && c != 1 && c != 8) { if(c == 33) { i=-1; while ((s[++i] = d = getchb()) && (d != 13)) ; ungetchb(d); s[i] = '\0'; return FUNCTION; } return c; } if (c == 13) { return ' '; } if (c == 8) { return 'b'; } if(isdigit(c)) { while (isdigit(s[++i] = c = getchb())) ; } if (c == '.') { while (isdigit(s[++i] = c = getchb())) ; } s[i] = '\0'; if (i > 0) { ungetchb(c); } return NUMBER; } |
inputbuf.c:
#include "rpn.h" // If using the VC++ compiler, use unbuffered input. Otherwise, use normal buffered input. // Ideally should write some ncurses code to allow for unbuffered input on unix platforms. #if defined(_WIN32) // Use VC++ input. #define INPUT_FUNCTION getche() #else #define INPUT_FUNCTION getchar() #endif static int buffer[BUFSIZE]; static int bufPos; int getchb(void) { if (bufPos > 0) { return buffer[--bufPos]; } else { return INPUT_FUNCTION; } } void ungetchb(int input) { if (bufPos < BUFSIZE) { if(input != 13 && input != 46) { buffer[bufPos++] = input; } } else { printf("Input buffer overrun... Not cool, guys!"); } } |
util.c:
#include "rpn.h" void clear(void) { //Clear the screen. Hopefully this works on every os, not ANSI compliant. if (system("cls")) system("clear"); } double radToDegrees(double radians) { return (radians/PI)*180; } double degreesToRad(double degrees) { return (degrees/180)*PI; } |
Code download: I have posted a .zip file of this code along with a visual studio project that can be used to compile it here. If you just want to run it and your on windows, there is a binary in the Release directory that should work :).
Acknowledgements:
The basic idea and some of the basic code for this came from Kernigan and Ritchie 2nd edition.
I really think you should post this IN the wiki itself. What good is a wiki if all the information is pointed to an external source?
True, but what good is a programming wiki with no syntax highlighting?