K&R C Programs Exercise 4-5

Exercise 4-5. Add access to library functions like sin, cos, exp, and pow. See <math.h> in Appendix B, Section 4.

Math functions need the calculator to recognize words as well as single characters. getop currently returns one character at a time. The fix: if getop sees a letter, it reads the whole word into s[] and returns a new token type MATH. The main loop then dispatches on the word using strcmp. pow is special — it consumes two operands; all others consume one. Compile with -lm to link the math library.

Key Changes

/* K&R Exercise 4-5 — RPN calculator: sin, cos, exp, pow
 * Compile: gcc -ansi -Wall ex4-5.c -o ex4-5 -lm */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <math.h>

#define MAXOP  100
#define NUMBER '0'
#define MATH   'm'
#define MAXVAL 100

static double val[MAXVAL];
static int sp = 0;
void   push(double f) { if (sp < MAXVAL) val[sp++] = f; else printf("stack full\n"); }
double pop(void)      { if (sp > 0) return val[--sp]; printf("stack empty\n"); return 0.0; }

static int _buf = EOF;
int  gc(void)   { int c = _buf; _buf = EOF; return (c != EOF) ? c : getchar(); }
void ugc(int c) { _buf = c; }

int getop(char s[])
{
    int i, c;
    while ((s[0] = c = gc()) == ' ' || c == '\t')
        ;
    s[1] = '\0';
    if (isalpha(c)) {           /* read a word for math functions */
        for (i = 1; isalpha(s[i] = c = gc()); i++)
            ;
        s[i] = '\0';
        if (c != EOF) ugc(c);
        return MATH;
    }
    if (!isdigit(c) && c != '.' && c != '-')
        return c;
    i = 0;
    if (isdigit(c) || c == '-')
        while (isdigit(s[++i] = c = gc()))
            ;
    if (c == '.')
        while (isdigit(s[++i] = c = gc()))
            ;
    s[i] = '\0';
    if (c != EOF) ugc(c);
    return NUMBER;
}

int main(void)
{
    int type;
    double op2;
    char s[MAXOP];

    while ((type = getop(s)) != EOF) {
        switch (type) {
        case NUMBER: push(atof(s)); break;
        case '+':    push(pop() + pop()); break;
        case '*':    push(pop() * pop()); break;
        case '-':    op2 = pop(); push(pop() - op2); break;
        case '/':    op2 = pop(); if (op2) push(pop()/op2); break;
        case '%':    op2 = pop(); if (op2) push((int)pop()%(int)op2); break;
        case MATH:
            if (strcmp(s, "sin") == 0)       push(sin(pop()));
            else if (strcmp(s, "cos") == 0)  push(cos(pop()));
            else if (strcmp(s, "exp") == 0)  push(exp(pop()));
            else if (strcmp(s, "sqrt") == 0) push(sqrt(pop()));
            else if (strcmp(s, "pow") == 0) { op2 = pop(); push(pow(pop(), op2)); }
            else printf("unknown function %s\n", s);
            break;
        case '\n': printf("\t%.8g\n", pop()); break;
        }
    }
    return 0;
}

Test It

echo "1 sin" | ./ex4-5       # sin(1 radian) = 0.84147098
echo "2 sqrt" | ./ex4-5      # 1.4142136
echo "2 3 pow" | ./ex4-5     # 8
echo "1 exp" | ./ex4-5       # e = 2.7182818

Sample Output

        0.84147098
        1.4142136
        8
        2.7182818

What This Exercise Teaches

  • Multi-character tokens — extending a character-at-a-time lexer to recognize words; the key is the isalpha entry check in getop
  • Dispatch on stringsswitch can’t dispatch on strings; strcmp in a chain of if/else if is the idiomatic approach
  • Arity matterspow takes two operands (pop order matters: pop() gives the exponent, second pop() gives the base); all other math functions take one
  • Linking -lm — math functions are in a separate library on most Unix systems; GCC requires explicit -lm

Set Up Your C Environment

← Exercise 4-4  | 
Chapter 4 Solutions  | 
Exercise 4-6 →

Book: The C Programming Language, 2nd Ed — Kernighan & Ritchie

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>