Aritalab:Lecture/Programming/Java/Calculator

From Metabolomics.JP
Jump to: navigation, search

逆ポーランド記号 (postfix) か通常の記法 (infix) で数式を記述できる計算機です。 四則演算のほかに log, sqrt をサポートしています。

Calculator.java
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Stack;
import java.util.StringTokenizer;

/**
 * @author masanori arita
 * 逆ポーランド記号電卓、および普通の記法の電卓
 */
public class Calculator
  {
    static final int NUMBER = 0;

    /**
     * 与えられた文字が数字なのか、オペレータなのかを判定する関数
     * オペレータの場合は優先度を返し、数字の場合はNUMBER (優先度は一番低い)
     * 
     * @param op
     *          トークン(数かオペレータ)
     * @return トークンの優先度
     */
    int getPriority(String op)
      {
        final String[] functions = { "+", "-", "/", "*",
            "log", "sqrt" };
        final int[] priority = { 1, 1, 2, 2, 3, 3 };
        for (int i = 0; i < functions.length; i++)
          if (op.equals(functions[i]))
            return priority[i];
        return NUMBER;
      }

    /**
     * tokenStackの先頭からnextPrioより優先度が高い部分を popして計算
     * 
     * @param nextPrio
     *          次に来ているトークンの優先度
     * @param stackPrio
     *          スタックの先頭にあるオペレータの優先度
     * @param tokenStack
     *          トークンが入っているスタック
     * @throws Exception
     */
    void reduce(int nextPrio, int stackPrio,
        Stack<String> tokenStack) throws Exception
      {
        while ((tokenStack.size() > 1)
            && (nextPrio <= stackPrio))
          {
            // 1つめのtoken
            double num1 = Double.parseDouble(tokenStack
                .pop());
            // 2つめのtoken
            String op = tokenStack.pop();

            /** ()の対応用 */
            if (op.equals("("))
              { // 左カッコを発見したら強制的に数式の終わりとみなす
                // 左カッコはここで捨てられる
                tokenStack.push("" + num1);
                return;
              }

            switch (getPriority(op)) {
            case 0:
              throw new Exception("オペレータが足りません");
            case 3: // 関数適用
              if (op.equals("log"))
                num1 = Math.log(num1);
              else if (op.equals("sqrt"))
                num1 = Math.sqrt(num1);
              break;
            case 1:
            case 2: // 四則演算
              if (tokenStack.size() == 0)
                throw new Exception("オペランドが足りません");
              double num2 = Double.parseDouble(tokenStack
                  .pop());
              if (op.equals("/"))
                {
                  if (num1 == 0)
                    throw new Exception("0で割り算しています");
                  num1 = num2 / num1;
                }
              else if (op.equals("*"))
                num1 = num2 * num1;
              else if (op.equals("+"))
                num1 = num2 + num1;
              else if (op.equals("-"))
                num1 = num2 - num1;
              break;
            }
            tokenStack.push("" + num1);
          }
      }

    /**
     * 入力された数式を読んで計算する関数
     * @param st トークンの入力列
     * @return 計算結果
     * @throws Exception
     */
    double parse(StringTokenizer st) throws Exception
      {
        // トークンをためるスタックを初期化
        Stack<String> tokenStack = new Stack<String>();
        int stackTopPrio = 0;
        while (st.hasMoreTokens())
          {
            // 次に処理するトークンを先読み
            String token = st.nextToken();

            /** ()の対応用 */
            if (token.equals(")"))
              // 右カッコが出てきたら、入力文字列の最後とみなす
              break;
            else if (token.equals("("))
              // 左カッコが出てきたら、新しい数式が始まったとみなす
              // その結果を1つの数字とみなす
              tokenStack.push("" + parse(st));
            else

              // 演算か数式
              {
                int nextPrio = getPriority(token);
                // もし先読みしたトークンよりスタックに積んである式のほうが
                // 優先度が高い場合、スタックの中身を先に処理
                if ((nextPrio != NUMBER)
                    && (nextPrio <= stackTopPrio))
                  reduce(nextPrio, stackTopPrio, tokenStack);
                tokenStack.push(token);
                if (nextPrio != NUMBER)
                  stackTopPrio = nextPrio;
              }
          }
        // 入力文字列の最後にきたので、スタックに残っている式を処理
        reduce(0, stackTopPrio, tokenStack);

        if (tokenStack.size() != 1)
          throw new Exception("オペレータが足りません");
        return Double.parseDouble(tokenStack.pop());
      }

    public double parseRPN(StringTokenizer st)
    throws Exception
      {
        Stack<String> tokenStack = new Stack<String>();
        while (st.hasMoreTokens())
          {
            String nextToken = st.nextToken();
            if (nextToken.equals("+"))
              {
                if (tokenStack.size() < 2)
                  throw new Exception("オペランドが足りません");
                double d1 = Double.parseDouble(tokenStack.pop());
                double d2 = Double.parseDouble(tokenStack.pop());
                tokenStack.push("" + (d2 + d1));
              }
            else if (nextToken.equals("-"))
              {
                if (tokenStack.size() < 2)
                  throw new Exception("オペランドが足りません");
                double d1 = Double.parseDouble(tokenStack.pop());
                double d2 = Double.parseDouble(tokenStack.pop());
                tokenStack.push("" + (d2 - d1));
              }
            else if (nextToken.equals("log"))
              {
                if (tokenStack.size() < 1)
                  throw new Exception("オペランドが足りません");
                double d1 = Double.parseDouble(tokenStack.pop());
                tokenStack.push("" + Math.log(d1));
              }
            else 
              tokenStack.push(nextToken);
          }
        //最後にスタックには数字が一つ残っているはず
        if (tokenStack.size() != 1)
          throw new Exception("オペレータが足りません");
        return Double.parseDouble(tokenStack.pop());
      }
    
    public static void main(String[] args)
      {
        try
          {
            BufferedReader br = new BufferedReader(
                new InputStreamReader(System.in));
            while (true)
              {
                System.out.print(">");
                String line = br.readLine();
                if (line == null)
                  break;
                StringTokenizer st = new StringTokenizer(
                    line);
                try
                  {
                    Calculator C = new Calculator();
                    //System.out.println(C.parseRPN(st)); // 逆ポーランド電卓 
                    System.out.println(C.parse(st));    // 普通の電卓
                  }
                catch (Exception e)
                  {
                    System.out.println("Error = " + e);
                  }
              }
          }
        catch (Exception exp)
          {
            exp.printStackTrace();
          }
      }
  }
Personal tools
Namespaces

Variants
Actions
Navigation
metabolites
Toolbox