関数電卓 - 完成

実装した機能

対数(log)

 使い方の例は、log2(8)と打つと底が2のlogの計算ができる。
 また、ln(10)と打つとログナチュラルの計算ができるように改良した。

exp

 expは正直いらないかもしれないと思ったが、そんなに手間もかからないので機能に加えておいた。
 exp(3)のように使うことでe(ネイピア数)の3乗となる。
 (この関数があるため、ネイピア数の実装はいいかなと思っている)

三角関数(sin,cos,tan,arcsin,arccos,arctan)

 代表的な三角関数を実装した。cotとかは自分がちゃんと覚えていなかったので作れなかったのが本音。
 sin(2 * pi)のように入力すると使える。45°の正弦(サイン)の値が知りたいとかであれば、sin(45 / 180 * pi)とかで頑張ってください。
 次に関数電卓をいじるなら、sinhとcoshあたりを追加するときかなと考えているのでもしかしたら度数で使う用の三角関数もついでに加えるかもしれない。

累乗(^)

 これは関数電卓の最初のバージョンをいじっている時に気づいた。

 あれ、10乗とか計算したいのにその機能がないんだが……?
 pow()で実装するか^で実装するか悩んだのですが、なるべく数学寄りの表記にしたかったので、^の方にした。
 使い方は、27^6と打つと27の6乗が計算できるようになっている。

階乗(!)

 6!と打つと6*5*4*3*2*1の計算結果が返ってくる。正直この演算子の演算の優先度がわからなくてかなり悩んだ。

円周率(π)

 円周率はpiで表せる。

四則演算子(+, -, *, /)

 言わずと知れた演算子。上記の関数と組み合わせて計算することもできる。
 log10(56) * sin(pi) + exp(4) + (2^2)! みたいな意味不明な計算式も計算してくれる。

括弧()

 忘れていたが括弧も使える。(1 + (3 * 2) ^ 2)のように入れ子状にして使うことも可能である。

空白処理

 半角スペースは読みとばし処理をしているので、log2( 10 * 3 )のように変に空白を入れてもトークンとしてまとまっていれば、ちゃんと動く。

ソースコード

GitHubでソースコードを挙げているがここでも掲載しておく。
ソースコードを使って遊んでみたい人は是非GitHubから持って行ってくれると嬉しい。
(もし変なところを見つけた方はこっそり教えてくださいね)
このブログのコードをコピペするならgcc -o mycalc lexcalc.c yacccalc.cみたいな感じでコンパイルしてください。

ヘッダーファイル(token.h)

#ifndef TOKEN_H_INCLUDED
#define TOKEN_H_INCLUDED

typedef enum {
  BAD_TOKEN,
  NUMBER_TOKEN,
  ADD_OPERATOR_TOKEN,
  SUB_OPERATOR_TOKEN,
  MUL_OPERATOR_TOKEN,
  DIV_OPERATOR_TOKEN,
  END_OF_LINE_TOKEN,
  LEFT_BRACKET_TOKEN,
  RIGHT_BRACKET_TOKEN,
  LOG_FUNCTION_TOKEN,
  LOG_NATURAL_FUNCTION_TOKEN,
  SIN_FUNCTION_TOKEN,
  COS_FUNCTION_TOKEN,
  TAN_FUNCTION_TOKEN,
  ARC_SIN_FUNCTION_TOKEN,
  ARC_COS_FUNCTION_TOKEN,
  ARC_TAN_FUNCTION_TOKEN,
  EXP_FUNCTION_TOKEN,
  SQRT_FUNCTION_TOKEN,
  FACTORIAL_FUNCTION_TOKEN,
  POWER_FUNCTION_TOKEN
} TokenKind;

#define MAX_TOKEN_SIZE (100)

typedef struct {
  TokenKind kind;
  double value;
  char str[MAX_TOKEN_SIZE];
} Token;

void set_line(char *line);
void get_token(Token *token);
double parse_line();

#endif

ソースコード(lexcalc.c)

#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include "token.h"

static char *st_line;
static int st_line_pos;
static int st_func_flag;

typedef enum {
  INITIAL,
  INTEGER_PART,
  FRACTIONAL_PART,
  DOT
} LexerStatus;

void get_token(Token *token) {
  char current_char;
  int out_pos = 0;
  LexerStatus status = INITIAL;
  token->kind = BAD_TOKEN;

  while(st_line[st_line_pos] != '\0') {
    current_char = st_line[st_line_pos];
    /*(ひとつ前に入れた文字が整数部分又は少数部分) かつ 今見ている一文字が数字ではない かつ 今見ている一文字が小数点でもない場合*/
    if((status == INTEGER_PART || status == FRACTIONAL_PART) && !isdigit(current_char) && current_char != '.') {
      token->kind = NUMBER_TOKEN;
      sscanf(token->str, "%lf", &token->value);
      return;
    }
    /*改行又はスペース又はタブの場合*/
    if(isspace(current_char)) {
      /*改行の場合*/
      if(current_char == '\n') {
        token->kind = END_OF_LINE_TOKEN;
        return;
      }
      st_line_pos++;
      continue;
    }
    /*トークンのサイズがオーバーしていないかチェックしている*/
    if(out_pos >= MAX_TOKEN_SIZE - 1) {
      fprintf(stderr, "token size is over. token is too long!\n");
      exit(1);
    }

    token->str[out_pos] = st_line[st_line_pos];
    st_line_pos++;
    out_pos++;
    token->str[out_pos] = '\0';

    if(current_char == '(') {
      token->kind = LEFT_BRACKET_TOKEN;
      return;
    } else if(current_char == ')') {
      token->kind = RIGHT_BRACKET_TOKEN;
      return;
    } else if(current_char == '+') {
      token->kind = ADD_OPERATOR_TOKEN;
      return;
    } else if(current_char == '-') {
      token->kind = SUB_OPERATOR_TOKEN;
      return;
    } else if(current_char == '*') {
      token->kind = MUL_OPERATOR_TOKEN;
      return;
    } else if(current_char == '/') {
      token->kind = DIV_OPERATOR_TOKEN;
      return;
    } else if(current_char == '^') {
      token->kind = POWER_FUNCTION_TOKEN;
      return;
    } else if(current_char == '!') {
      token->kind = FACTORIAL_FUNCTION_TOKEN;
      return;
    } else if(('a' <= current_char && current_char <= 'z') || ('A' <= current_char && current_char <= 'Z')) {
      st_func_flag++;
      if(strcmp(token->str, "log") == 0 || strcmp(token->str, "LOG") == 0) {
        token->kind = LOG_FUNCTION_TOKEN;
        st_func_flag = 0;
        return;
      } else if(strcmp(token->str, "ln") == 0 || strcmp(token->str, "LN") == 0) {
        token->kind = LOG_NATURAL_FUNCTION_TOKEN;
        st_func_flag = 0;
        return;
      } else if(strcmp(token->str, "sin") == 0 || strcmp(token->str, "SIN") == 0) {
        token->kind = SIN_FUNCTION_TOKEN;
        st_func_flag = 0;
        return;
      } else if(strcmp(token->str, "cos") == 0 || strcmp(token->str, "COS") == 0) {
        token->kind = COS_FUNCTION_TOKEN;
        st_func_flag = 0;
        return;
      } else if(strcmp(token->str, "tan") == 0 || strcmp(token->str, "TAN") == 0) {
        token->kind = TAN_FUNCTION_TOKEN;
        st_func_flag = 0;
        return;
      } else if(strcmp(token->str, "arcsin") == 0 || strcmp(token->str, "ARCSIN") == 0) {
        token->kind = ARC_SIN_FUNCTION_TOKEN;
        st_func_flag = 0;
        return;
      } else if(strcmp(token->str, "arccos") == 0 || strcmp(token->str, "ARCCOS") == 0) {
        token->kind = ARC_COS_FUNCTION_TOKEN;
        st_func_flag = 0;
        return;
      } else if(strcmp(token->str, "arctan") == 0 || strcmp(token->str, "ARCTAN") == 0) {
        token->kind = ARC_TAN_FUNCTION_TOKEN;
        st_func_flag = 0;
        return;
      } else if(strcmp(token->str, "exp") == 0 || strcmp(token->str, "EXP") == 0) {
        token->kind = EXP_FUNCTION_TOKEN;
        st_func_flag = 0;
        return;
      } else if(strcmp(token->str, "sqrt") == 0 || strcmp(token->str, "SQRT") == 0) {
        token->kind = SQRT_FUNCTION_TOKEN;
        st_func_flag = 0;
        return;
      } else if(strcmp(token->str, "pi") == 0 || strcmp(token->str, "PI") == 0) {
        token->kind = NUMBER_TOKEN;
        token->value = M_PI;
        st_func_flag = 0;
        return;
      }
      continue;
    } else if(isdigit(current_char)) {
      if(status == INITIAL) {
        status = INTEGER_PART;
      } else if(status == DOT) {
        status = FRACTIONAL_PART;
      }
    } else if(current_char == '.') {
      if(status == INTEGER_PART) {
        status = DOT;
      } else {
        fprintf(stderr, "syntax error.\n");
        exit(1);
      }
    } else {
      fprintf(stderr, "bad character: %c\n", current_char);
      exit(1);
    }
    if(st_func_flag != 0) {
      fprintf(stderr, "syntax error.\n");
      exit(1);
    }
  }
}

void set_line(char *line) {
  st_line = line;
  st_line_pos = 0;
}

ソースコード(yacccalc.c)

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "token.h"

#define LINE_BUF_SIZE 1024

static int st_look_ahead_token_exists;
static Token st_look_ahead_token;

static int factorial_calc(int);
static void my_get_token(Token *);
static void unget_token(Token *);
static double parse_primary_expression();
static double parse_pow_term();
static double parse_term();
static double parse_expression();


static int factorial_calc(int number) {
  if(number == 1 || number == 0) {
    return 1;
  } else {
    return number * factorial_calc(number - 1);
  }
}

static void my_get_token(Token *token) {
  if(st_look_ahead_token_exists) {
    *token = st_look_ahead_token;
    st_look_ahead_token_exists = 0;
  } else {
    get_token(token);
  }
}

static void unget_token(Token *token) {
  st_look_ahead_token = *token;
  st_look_ahead_token_exists = 1;
}

static double parse_primary_expression() {
  int minus_flag = 0, function_flag = 0;
  Token token;
  double value;
  double base;
  my_get_token(&token);
  if(token.kind == SUB_OPERATOR_TOKEN) {
    minus_flag = 1;
    my_get_token(&token);
  }
  if(token.kind == LOG_NATURAL_FUNCTION_TOKEN) {
    function_flag = 1;
    my_get_token(&token);
  } else if(token.kind == SIN_FUNCTION_TOKEN) {
    function_flag = 2;
    my_get_token(&token);
  } else if(token.kind == COS_FUNCTION_TOKEN) {
    function_flag = 3;
    my_get_token(&token);
  } else if(token.kind == TAN_FUNCTION_TOKEN) {
    function_flag = 4;
    my_get_token(&token);
  } else if(token.kind == EXP_FUNCTION_TOKEN) {
    function_flag = 5;
    my_get_token(&token);
  } else if(token.kind == SQRT_FUNCTION_TOKEN) {
    function_flag = 6;
    my_get_token(&token);
  } else if(token.kind == ARC_SIN_FUNCTION_TOKEN) {
    function_flag = 7;
    my_get_token(&token);
  } else if(token.kind == ARC_COS_FUNCTION_TOKEN) {
    function_flag = 8;
    my_get_token(&token);
  } else if(token.kind == ARC_TAN_FUNCTION_TOKEN) {
    function_flag = 9;
    my_get_token(&token);
  } else if(token.kind == LOG_FUNCTION_TOKEN) {
    function_flag = 10;
    my_get_token(&token);
    if(token.kind == NUMBER_TOKEN) {
      base = token.value;
      my_get_token(&token);
    } else if(token.kind == LEFT_BRACKET_TOKEN) {
      base = 10;
    }
  }
  if(function_flag != 0) {
    if(token.kind == LEFT_BRACKET_TOKEN) {
      value = parse_expression();
      my_get_token(&token);
      if(token.kind != RIGHT_BRACKET_TOKEN) {
        fprintf(stderr, "cannot find brackets.\n");
        exit(1);
      }
      switch(function_flag) {
        case 1:
          value = log(value);
          break;
        case 2:
          value = sin(value);
          break;
        case 3:
          value = cos(value);
          break;
        case 4:
          value = tan(value);
          break;
        case 5:
          value = exp(value);
          break;
        case 6:
          value = sqrt(value);
          break;
        case 7:
          value = asin(value);
          break;
        case 8:
          value = acos(value);
          break;
        case 9:
          value = atan(value);
          break;
        case 10:
          value = log(value) / log(base);
      }
    } else {
      exit(1);
      return 0.0;
    }
  } else if(token.kind == NUMBER_TOKEN) {
    value = token.value;
  } else if(token.kind == LEFT_BRACKET_TOKEN) {
    value = parse_expression();
    my_get_token(&token);
    if(token.kind != RIGHT_BRACKET_TOKEN) {
      fprintf(stderr, "cannot find brackets.\n");
      exit(1);
    }
  } else {
    exit(1);
    return 0.0;
  }
  if(minus_flag) {
    value = -1.0 * value;
  }
  return value;
}

static double parse_factorial_term() {
  double r1;
  Token token;
  r1 = parse_primary_expression();
  my_get_token(&token);
  if(token.kind == FACTORIAL_FUNCTION_TOKEN) {
    r1 = factorial_calc(r1);
  } else {
    unget_token(&token);
  }
  return r1;
}

static double parse_pow_term() {
  double r1, r2;
  Token token;
  r1 = parse_factorial_term();
  my_get_token(&token);
  if(token.kind == POWER_FUNCTION_TOKEN) {
    r2 = parse_factorial_term();
    r1 = pow(r1, r2);
  } else {
    unget_token(&token);
  }
  return r1;
}

static double parse_term() {
  double r1, r2;
  Token token;
  r1 = parse_pow_term();
  while(1) {
    my_get_token(&token);
    if(token.kind != MUL_OPERATOR_TOKEN && token.kind != DIV_OPERATOR_TOKEN && token.kind != FACTORIAL_FUNCTION_TOKEN) {
      unget_token(&token);
      break;
    } else if(token.kind != FACTORIAL_FUNCTION_TOKEN) {
      r2 = parse_pow_term();
    }
    if(token.kind == MUL_OPERATOR_TOKEN) {
      r1 *= r2;
    } else if(token.kind == DIV_OPERATOR_TOKEN) {
      r1 /= r2;
    } else if(token.kind == FACTORIAL_FUNCTION_TOKEN) {
      r1 = factorial_calc((int)r1);
    }
  }
  return r1;
}

static double parse_expression() {
  double r1, r2;
  Token token;
  r1 = parse_term();
  while(1) {
    my_get_token(&token);
    if(token.kind != ADD_OPERATOR_TOKEN && token.kind != SUB_OPERATOR_TOKEN) {
      unget_token(&token);
      break;
    }
    r2 = parse_term();
    if(token.kind == ADD_OPERATOR_TOKEN) {
      r1 += r2;
    } else if(token.kind == SUB_OPERATOR_TOKEN) {
      r1 -= r2;
    }
  }
  return r1;
}

double parse_line() {
  double value;
  st_look_ahead_token_exists = 0;
  value = parse_expression();
  return value;
}

int main(int argc, char **argv) {
  char line[LINE_BUF_SIZE];
  double value;
  while(fgets(line, LINE_BUF_SIZE, stdin) != NULL) {
    set_line(line);
    value = parse_line();
    printf(">> %f\n", value);
  }
  return 0;
}

最後まで見ていただきありがとうございます。また別の記事でお会いできることを祈っております。