K&Rを読もう(42) 演習4-3 - 4-10 変数機能つきRPN電卓

今回の演習は逆ポーランド電卓。多機能になるような演習問題で結構楽しかったです。

さて、早速遊んでみます!!

逆ポーランド電卓なのでちょっと変態的な構文です。順にいきましょう。


まずは四則演算

1 1 +
        2
1 2 3 4 5 + + + +
        15
3.1415 10 * 10 *
        314.15

小数もいけます(基本的に小数だったり)

  • 1と1を足すと2
  • 1と2と3と4と5を足して足して足して足して15
  • 3.1415と半径10を掛けて、半径10を掛けると314.15

日本語と同じ構文ですね。


次に数学関数。

3.1415 10 2 p *
        314.15

なんと2乗できます。sがsin,cがcos,tがtan,eがexp,そしてpがpowです。これ以上のサンプルは求めないでください(笑

文字列とか面倒なので関数は全て一文字にしてみました。


勿論スタック操作も出来ます。

3.1415 y *
        9.8690223
2 10 x -
        8

yはyank。値を次のスタックにコピーします。xは入れ替え。うっほメンドクセェというユーザーにオススメ。


そして、変数機能。

3.1415 w P
        8

w(write)で変数Pへの書き込みです。スタックから値が消えたので、前の演算で使用した8が見えてます。変数は大文字のアルファベットです。

取り出します。

r P
        3.1415

r(read)でスタックへ戻して、

10 2 p *
        314.15

3.1415 * pow(10,2)という演算が出来ました。

なかなか賢いです。

(RPNだと変数いらないという噂も)

ソース

個々の回答を載せると大量になるので一個だけ(やってない問題もアリ)。ソースは長いので圧縮してみました。gnu indentとか整形ツールでも使ってください。

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define MAXOP 100
#define NUMBER '0'
double pop(void);
void push(double f);
double look(void);
int getop(char s[]);
int main(void) {
    int type;
    double op1,op2;
    char s[MAXOP];
    int name;
    double var['Z' - 'A' + 1];
    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 != 0.0) push(pop() / op2); else printf("error: zero divisor"); break;
        case '%' : op2 = pop(); if (op2 != 0.0) push((int) pop() % (int) op2); else printf("error: zero divisor"); break;
        /* 数学系 */
        case 's' : push(sin(pop())); break;
        case 'c' : push(cos(pop())); break;
        case 't' : push(tan(pop())); break;
        case 'e' : push(exp(pop())); break;
        case 'p' : push(pow(pop(), pop())); break;
        /* スタック操作 */
        case 'y' : push(look());
        case 'x' : op1 = pop(); op2 = pop(); push(op1); push(op2); break;
        case 'w' : name = getop(s); if (isalpha(name)) var[name - 'A'] = pop(); else printf("can't write value"); break;
        case 'r' : name = getop(s); if (isalpha(name)) push(var[name - 'A']); else printf("can't read value"); break;
        /* 表示 */
        case '\n' : printf("\t%.8g\n", look()); break;
        default : printf(" error\n type : %d\n str  : %s\n", type, s); break;
        }
    }
    return EXIT_SUCCESS;
}
#define MAXVAL 100
int sp = 0;
double val[MAXVAL] = {0};
void push(double f) {
    if (sp < MAXVAL) val[sp++] = f; else printf("error: stack full, can't push %g\n", f);
}
double pop(void) {
    if (sp > 0) return val[--sp];
    else { printf("error: stack empty, can't pop\n"); return 0.0; }
}
double look(void) {
    if (sp > 0) return val[sp - 1]; else { return 0.0; }
}
int getch(void);
void ungetch(int c);
int getop(char s[]) {
    int i, c;
    while ((s[0] = c = getch()) == ' ' || c == '\t') ;
    s[1] = '\0';
    if (!isdigit(c) && c != '.') return c;
    i = 0;
    if (isdigit(c)) while (isdigit(s[++i] = c = getch()));
    if (c == '.') while (isdigit(s[++i] = c = getch()));
    s[i] = '\0';
    if (c != EOF) ungetch(c);
    return NUMBER;
}
int ch = '\0';
int getch(void) {
    int tmp;
    if(ch == '\0') return getchar();
    else { tmp = ch; ch = '\0'; return tmp; }
}
void ungetch(int c) { ch = c; }
/* for ex 4-7
void ungets(char s[]) {
    int len = strlen(s);
    while(len > 0) ungetch(s[--len]); s[0] = '\0';
}
*/

関数機能を付ければ立派なプログラミング言語になりそうですね。


まだまだ色々手直ししたいところだらけ。気が向いたら修正するかな。