//学籍番号XXXXXXX //XXXX import java.io.*; class Kadai1102{ static double InputNumber() throws IOException{ BufferedReader d=new BufferedReader(new InputStreamReader(System.in)); double num; while(true){ String a; a=d.readLine(); try{ num=Double.parseDouble(a); } catch(java.lang.NumberFormatException e){ System.out.println("次は数字を入力してください."); continue; } break; } return num; } static int InputCul() throws IOException{ BufferedReader d=new BufferedReader(new InputStreamReader(System.in)); String b; int c; while(true){ b=d.readLine(); if(b.equals("+")){ c=0; break; } if(b.equals("-")){ c=1; break; } if(b.equals("*")){ c=2; break; } if(b.equals("/")){ c=3; break; } if(b.equals("=")){ c=4; break; } else{ System.out.println("次は演算子を入力してください."); continue; } } return c; } static double Culculate(double r,double n,int i){ if(i==0){ r=r+n; } if(i==1){ r=r-n; } if(i==2){ r=r*n; } if(i==3){ r=r/n; } System.out.println(r); return r; } public static void main(String[] args) throws IOException{ double result=0.0;//計算結果を入れる変数 double number=0.0;//計算する数字を入れる変数 int cul=0;//入力した演算子を判定し、種類により整数値にして入れる変数 while(true){ if(cul!=4) number=InputNumber(); result=Culculate(result,number,cul); cul=InputCul(); if(cul==4) break; } } }
最初に演算子として「+」を入れて値としては「0」を入れておくというテクニッ クで最初の1回を特別扱いしないというテクニックを用いた解答が多くありました.
/* 名前:XXX 学生証番号:XXXXX プログラムの説明:数字が要求される場面で他の文字列が入力されたときはtry文 を使って例外処理をした。演算子の要求される入力ではqualsメソッドを用いて一 つずつ判定した。同時に次の計算での分岐も行ってしまうことにして、switch文の 為のcase定数cを定義して用いた。演算子入力のループから抜けるのにcを判断基準 に用いた点などを工夫した。 */ import java.io.*; class Kadai1102{ public static void main(String[] args) throws IOException{ BufferedReader d=new BufferedReader(new InputStreamReader(System.in)); double a=0,n; int c=1; all: for(;;){ try{n=Double.parseDouble(d.readLine());} catch(java.lang.NumberFormatException e){ System.out.println("次は数字を入力してください。"); continue; } switch(c){ case 1:a=a+n;break; case 2:a=a-n;break; case 3:a=a*n;break; case 4:a=a/n;break; } System.out.println(a); for(c=0;c==0;){String k=d.readLine(); if(k.equals("+")) c=1; else if(k.equals("-")) c=2; else if(k.equals("*")) c=3; else if(k.equals("/")) c=4; else if(k.equals("=")) break all; else System.out.println("次は演算子を入力してください。"); } } } }短い解答だったので紹介しました.内側のfor文でcの値によって脱出を1カ所に したのは,あまりお勧めできません.
for(;;){ String k=d.readLine(); if(k.equals("+")) {c=1; break;} else if(k.equals("-")) {c=2; break;} else if(k.equals("*")) {c=3; break;} else if(k.equals("/")) {c=4; break;} else if(k.equals("=")) break all; else System.out.println("次は演算子を入力してください。"); }のほうが良いでしょうし,あるいはメソッドにした方が,よりはっきりします.
// XXXX // XXXX //説明 //最初に入れた文字が数字でないと continue によって toploop の最初に戻る (あ) //数字であれば(1)の if-else 文に進む(最初は i = 0 + num ) (い) //その後(2)の for 文に進む (う) //入力された文字が +,-,*,/ なら(2)の for 文終了、toploop の最初に戻る (え) //入力された文字が = なら(2)の for 文、さらには toploop 終了 (お) //入力された文字がそれら以外なら(2)の for 文を繰り返す (か) //以上(あ)〜(か)を(お)で終了するまで繰り返す // 入力に関するクラスを使う時は必要 import java.io.*; class Kadai1102{ // throws IOException で内部で入出力エラーが起きる可能性があることを示す public static void main(String[] args) throws IOException{ // 入力をするためには,System.inからBufferedReaderを作らなくてはいけない BufferedReader d=new BufferedReader(new InputStreamReader(System.in)); double i=0; double num; String token; token="+"; toploop:for(;;){ try{ //この中でエラーが起きる可能性がある // BufferedReader dから読み込んだ数をnumに入れる. num=Double.parseDouble(d.readLine()); } // java.lang.NumberFormatExceptionが起きた時はここに飛び警告する catch(java.lang.NumberFormatException e){ System.out.println("次は数字を入力してください"); continue; } if (token.equals("+")) //(1) i+=num; else if (token.equals("-")) i-=num; else if (token.equals("*")) i*=num; else if (token.equals("/")) i/=num; System.out.println(i); for(;;){ // BufferedReader dから読み込んだ演算子をtokenに入れる. token=d.readLine(); if (token.equals("+")||token.equals("-")||token.equals("*")||token.equals("/")) break; else if (token.equals("=")) break toploop; else System.out.println("次は演算子を入力してください"); } } } }
if (token.equals("+")||token.equals("-")||token.equals("*")||token.equals("/"))は
if (token.length()==1 && "+-*/".indexOf(token)>=0)とも書けますが,上の方が素直でしょう.
import java.io.*; public class Kadai1102{ static int NUM = 1; static int SIGN = 2; public static void main(String[] args) throws Exception{ StreamTokenizer stk = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in))); int need = NUM; char op='+'; boolean isMemo = false; double num=0,result=0; System.out.println("Simple Calculator"); while(stk.nextToken()!=StreamTokenizer.TT_EOF){ if(need == NUM ){ if(stk.ttype != StreamTokenizer.TT_NUMBER){ System.out.println("Please type a number!"); continue; }else{ need = SIGN; if(!isMemo){ result = stk.nval; } else{ num = stk.nval; switch(op){ case '+': result += num; break; case '-': result -= num; break; case '*': result *= num; break; case '/': if(num!=0) result /= num; else{ System.out.println("Error: Div by 0"); need = NUM; } break; default: System.out.println("Please type an operator! "); need = SIGN; break; } System.out.println(result); } } }else{ if(stk.ttype==StreamTokenizer.TT_NUMBER||stk.ttype==StreamTokenizer.TT_WORD){ System.out.println("Please type an operator!"); continue; }else{ op = (char)stk.ttype; if(op=='='){ System.out.println("Result: "+result); return; } need = NUM; isMemo = true; } } } } }
// Encoding UTF-8 // 氏名:XXXX // 学生証番号:XXXXX /* 繰り返しを用い、iが奇数のときに数字、iが偶数のときに演算子を読み込むことにすれば、エラーメッセージを表示するための判定がしやすいと考えた。配列を使う必要はなかったかもしれないが、計算記録を残しておいて最後に表示するようにした。 */ import java.io.*; // 入力に関するクラスを使うために必要。 class Kadai1102{ public static void main(String[] args) throws IOException{ // 入出力エラーの可能性を考慮。 BufferedReader d=new BufferedReader(new InputStreamReader(System.in)); // 入力を行うためにSystem.inからBufferedReaderの作成が必要。 int n=2^32-1; // int型で使える最大の整数をnとする。 String[] cal=new String[n]; // 入力を読み込む配列を作成。 double[] num=new double[n]; // 入力された数字をオブジェクト型から実数に変換するための配列を作成。 double[] ans=new double[n]; // 答えを保存する配列を作成。 // 以下、繰り返し。 for(int i=1;i< n;i++){ cal[i]=d.readLine(); // 配列calに、入力された数字または演算子を代入。 // 以下、iが偶数、演算子が代入されるべきとき。 if(i%2==0){ if(cal[i].equals("+")||cal[i].equals("-")||cal[i].equals("*")||cal[i].equals("/")||cal[i].equals("=")){ if(cal[i].equals("=")){ System.out.println(ans[i-1]); break; } // =が入力されたら、それまでの答えを表示して、プログラムを終了する。 else continue; // "+-*/"ならそのまま計算を続ける。 } else{ System.out.println("演算子を入力してください。"); i=i-1; continue; } // 演算子以外が代入されたときはエラーメッセージを表示し、iの値を元に戻して、繰り返す。 } // 以下、iが奇数、数字が代入されるべきとき。 if(i%2==1){ try{ num[i]=Double.parseDouble(cal[i]); } catch(java.lang.NumberFormatException e){ System.out.println("数字を入力してください。"); i=i-1; continue; } // 例外処理を用いた。入力されたものを実数に変換できない場合は、エラーメッセージを表示し、iの値を元に戻して、繰り返す。 if(cal[i-1]==null) ans[i]=num[i]; // 最初に入力されたときは計算せずに表示。cal[0]がnullなので、それを目印にする。 else if(cal[i-1].equals("+")) ans[i]=ans[i-2]+num[i]; // 加法 else if(cal[i-1].equals("-")) ans[i]=ans[i-2]-num[i]; // 減法 else if(cal[i-1].equals("*")) ans[i]=ans[i-2]*num[i]; // 乗法 else if(cal[i-1].equals("/")) ans[i]=ans[i-2]/num[i]; // 除法 } System.out.println(ans[i]); // 毎回計算結果を表示。 } // 繰り返しの終了。 System.out.println("全計算過程は、"); for(int i=3;cal[i+1]!=null;i=i+2){ System.out.println(ans[i-2]+" "+cal[i-1]+" "+num[i]+" = "+ans[i]); } // 計算過程の表示。 } }
int n=2^32-1; // int型で使える最大の整数をnとする。は232-1を計算したつもりのだと思いますが,Java言語の「^」という 演算子は排他的論理和(xor)でかつ,優先順位が低いため,2 xor 31 = 29が入り ます.
なお,本当に232-1を計算したいなら,
double f=Math.pow(2,32)-1.0; System.out.println(f);でできますが,こんな大きさの配列を取ろうとしても,メモリが足りなくなって作れないのが普通です.
//////////////////////////////////////////////////////////////////////////////// // プログラム名 : Option1102 // 作成者 : gXXXXXX XXXX // プログラムの説明 : 電卓プログラム //////////////////////////////////////////////////////////////////////////////// import java.io.*;//IO import java.util.*;//Vector,Stock,StringTokenizer class Option1102{ public static void main(String[] args) throws IOException{ BufferedReader br=new BufferedReader(new InputStreamReader(System.in)); String s=null;//入力を読むための文字列変数 Vector theStrings=new Vector();//計算式をトークンに分けたものを保存するStringの可変長配列 double result=0.0;//計算結果 Variables variable=new Variables();//変数クラス while(true){ theStrings.clear();//計算式初期化 System.out.println("計算式を入力してください(help:ヘルプ,quit:終了)."); System.out.print("> "); System.out.flush(); //ここから計算式入力処理 //標準入力から1行読み込んでトークンに分けて保存 s=br.readLine(); StringTokenizer st=new StringTokenizer(s," */+-()^",true); String temp; int if_open_kakko=1;//直前の文字が開きかっこかどうか(負の数の判断に使用) while(st.hasMoreTokens()){ temp=st.nextToken(); if(temp.equals(" ")){ continue;}//空白は読み飛ばす if(if_open_kakko==1 && temp.equals("-")){//開きかっこの直後にマイナスがきたら theStrings.add("0");//負の数を表すようなので,0-*の形に無理やり変換 } if(temp.equals("(")){if_open_kakko=1;} else{if_open_kakko=0;}//開きかっこかどうかを記録 theStrings.add(temp); }//入力処理完了 //何も入力していなかったらやり直し if(theStrings.size()==0){ continue; } //quitが入力されたらwhile文を抜けてプログラム終了 if((theStrings.get(0)).equals("quit") || (theStrings.get(0)).equals("exit")){ break; } //helpが入力されたらヘルプ表示 if((theStrings.get(0)).equals("help")){ System.out.println(); System.out.println("式の入力:"); System.out.println(" 1行に全ての式を入力します.使える演算子は"); System.out.println(" + [加算], - [減算], * [乗算], / [除算], ^ [累乗], ( ) [かっこ]"); System.out.println(" です."); System.out.println(); System.out.println("計算結果:"); System.out.println(" 計算結果が,NaN(非数),Infinity(無限大値)となることがあります."); System.out.println(" あきらめてくださいw"); System.out.println(); System.out.println("変数・定数:"); System.out.println(" aからzまでの変数が使用できます.変数に数値を代入するには,"); System.out.println(" set [変数名] [数値]"); System.out.println(" などとします.数値を省略すると,前の演算の結果が代入されます."); System.out.println(" 円周率(PI)と自然対数の底(E)は定数として使用できます."); System.out.println(); System.out.println("関数:"); System.out.println(" 関数として,次のものが使用できます."); System.out.println(" sin(),cos(),tan() : 三角関数の値を返します(引数の単位ラジアン)."); System.out.println(" log() : 自然対数値を返します."); System.out.println(" sqrt() : 正の平方根を返します( ^(1/2) と同等)."); System.out.println(" abs() : 絶対値を返します."); System.out.println(); continue; } //setが入力されたら前の計算結果を指定された変数に代入する if((theStrings.get(0)).equals("set")){ int i; for(i=0;i < 26;i++){ //2番目のトークンがa-zかどうか if((theStrings.get(1)).equals(String.valueOf((char)('a'+i)))){ try{//set [変数] [数値] 構文かどうか試す //3番目以降のトークンのみにする theStrings.remove(0); theStrings.remove(0); //定数・変数置き換え処理 variable.doTrans(theStrings); //3番目以降をを計算してみてresultに代入 result=Calculator.doCalc(theStrings); } catch(java.lang.NumberFormatException e){//失敗したら何もしない } catch(java.util.EmptyStackException e){ } catch(java.lang.ArrayIndexOutOfBoundsException e){ } //変数に代入 variable.set(i,result); System.out.println((char)('a'+i)+"に"+result+"を代入しました."); break; } } if(i==26){//a-zではなかった System.out.println("多分,入力が不正です."); } continue; } //変数・定数置き換え処理 variable.doTrans(theStrings); //計算を実行して, try{ result= Calculator.doCalc(theStrings); } //なんらかのエラーが出たら入力が不正であると決め付けるw catch(java.lang.NumberFormatException e){ System.out.println("多分,入力が不正です."); continue; } catch(java.util.EmptyStackException e){ System.out.println("多分,入力が不正です."); continue; } catch(java.lang.ArrayIndexOutOfBoundsException e){ System.out.println("多分,入力が不正です."); continue; } System.out.println(result); } } } //////////////////////////////////////////////////////////////////////////////// //変数管理クラス Variables // //doTrans関数:変数・定数を処理する //引数:Vector 計算式をトークンに分けたもの(Stringの可変長配列) //返り値:なし // //set関数:変数に値を入れる //引数:int 変数の番号0-25(a-z) // double 値 // //////////////////////////////////////////////////////////////////////////////// class Variables{ private double variable[]=new double[26];//変数a-z public void doTrans(Vector input){ for(int j=0;j < input.size();j++){ if((input.get(j)).equals("PI")){ input.set(j,Double.toString(Math.PI)); } if((input.get(j)).equals("E")){ input.set(j,Double.toString(Math.E)); } else { for(int i=0;i < 26;i++){ if((input.get(j)).equals(String.valueOf((char)('a'+i)))){ input.set(j,Double.toString(variable[i])); } } } } } public void set(int i,double number){ variable[i]=number; } } //////////////////////////////////////////////////////////////////////////////// //計算機クラス Calculator // //doCalc関数: //引数:Vector 計算式をトークンに分けたもの(Stringの可変長配列) //返り値:double 計算結果を返す // //check関数:doCalcの補助,式の形をチェック //引数:Vector 計算式をトークンに分けたもの(Stringの可変長配列) //返り値:なし //////////////////////////////////////////////////////////////////////////////// class Calculator{ private static void check(Vector input){ //中値記法の形をしているかどうかをチェック(数値が連続していないか) int if_number=0; for(int i=0;i < input.size();i++){ try{ Double.parseDouble((String)input.get(i));//数値に変換できるか if(if_number==1){//その直前も数字だったらエラー throw new java.lang.ArrayIndexOutOfBoundsException();//なんでもいいから例外を投げる } if_number=1; } catch(java.lang.NumberFormatException e){//数値ではなかった if(!(input.get(i)).equals("(") && !(input.get(i)).equals(")")){//[数値] [かっこ] [数値]もダメ if_number=0; } } } } public static double doCalc(Vector input){ input=Funcs.doTrans(input);//関数の処理 check(input);//中置記法の形をしているかどうかをチェック input=InToPost.doTrans(input);//中置記法->後置記法の変換 return ParsePost.doParse(input);//後置記法の式を評価 } } //////////////////////////////////////////////////////////////////////////////// //関数処理クラス Funcs //doTrans関数: //引数:Vector 計算式をトークンに分けたもの(Stringの可変長配列) //返り値:Vector 計算式をトークンに分けたもの,関数の部分を数値に変換済み //////////////////////////////////////////////////////////////////////////////// class Funcs{ public static Vector doTrans(Vector input){ Vector output=new Vector();//最終的な出力 Vector temp= new Vector();//一時用 Stack theStack=new Stack();//スタック,()の処理用 double tempResult=0.0;//一時用 for(int j=0;j < input.size();j++){ if(input.size()-j < 4){//処理すべき部分のトークン数が4未満のとき //関数があるはずがないので,そのまま出力する output.add((String)input.get(j)); continue; } String s1=(String)input.get(j); String s2=(String)input.get(j+1); String s3=(String)input.get(j+2); String s4=(String)input.get(j+3); //関数検出開始 if(s1.equals("sin")){//関数名sinを検出 if(!s2.equals("(")){//次が(でなかったら //入力がおかしいのでとりあえず,なんでもいいから例外を投げる throw new java.lang.NumberFormatException(); } else if(s4.equals(")")){//3つ先が)だったら //()の中身=s3は数値のはず output.add(Double.toString(Math.sin(Double.parseDouble(s3)))); j+=3; continue; } } else if(s1.equals("cos")){//関数名cosを検出 if(!s2.equals("(")){ throw new NumberFormatException(); } else if(s4.equals(")")){ output.add(Double.toString(Math.cos(Double.parseDouble(s3)))); j+=3; continue; } } else if(s1.equals("tan")){//関数名tanを検出 if(!s2.equals("(")){ throw new NumberFormatException(); } else if(s4.equals(")")){ output.add(Double.toString(Math.tan(Double.parseDouble(s3)))); j+=3; continue; } } else if(s1.equals("log")){//関数名logを検出 if(!s2.equals("(")){ throw new NumberFormatException(); } else if(s4.equals(")")){ output.add(Double.toString(Math.log(Double.parseDouble(s3)))); j+=3; continue; } } else if(s1.equals("sqrt")){//関数名sqrtを検出 if(!s2.equals("(")){ throw new NumberFormatException(); } else if(s4.equals(")")){ output.add(Double.toString(Math.sqrt(Double.parseDouble(s3)))); j+=3; continue; } } else if(s1.equals("abs")){//関数名absを検出 if(!s2.equals("(")){ throw new NumberFormatException(); } else if(s4.equals(")")){ output.add(Double.toString(Math.abs(Double.parseDouble(s3)))); j+=3; continue; } } else {//関数ではなかった //そのまま出力 output.add(s1); continue; } //以下,関数を検出したが,その中身が数値ではない=数式だった場合 //スタックを使ってかっこの対応を調べる(使わなくてもいいけど..) //かっこの中身の数式をtempに入れる j++; theStack.push("("); while(true){ j++; String s=(String)input.get(j); if(s.equals("(")){ theStack.push("("); } else if(s.equals(")")){ theStack.pop(); if(theStack.empty()){//最後のかっこを検出したら break;//終了 } } temp.add(s); } //tempの数式を計算してtempResultにいれる tempResult=Calculator.doCalc(temp); //関数ごとに値を求めて出力 if(s1.equals("sin")){ output.add(Double.toString(Math.sin(tempResult))); } else if(s1.equals("cos")){ output.add(Double.toString(Math.cos(tempResult))); } else if(s1.equals("tan")){ output.add(Double.toString(Math.tan(tempResult))); } else if(s1.equals("log")){ output.add(Double.toString(Math.log(tempResult))); } else if(s1.equals("sqrt")){ output.add(Double.toString(Math.sqrt(tempResult))); } else if(s1.equals("abs")){ output.add(Double.toString(Math.abs(tempResult))); } } return output;//出力を返す } } //////////////////////////////////////////////////////////////////////////////// //中置記法処理クラス InToPost // //doTrans関数 //引数:Vector 計算式をトークンに分けたもの(Stringの可変長配列),中置記法 //返り値:Vector 計算式をトークンに分けたもの(Stringの可変長配列),後置記法 // //gotOper関数:doTrans関数の補助 +-*/^を受け取ったときの処理 //gotParen関数:doTrans関数の補助 閉じかっこを受け取ったときの処理 //////////////////////////////////////////////////////////////////////////////// class InToPost { private static Stack theStack;//スタック private static Vector output;//最終的な出力 public static Vector doTrans(Vector input){ //初期化 theStack=new Stack(); output=new Vector(); for(int j=0;j < input.size();j++){ String s=(String)input.get(j); if(s.equals("+") || s.equals("-")){ gotOper(s,1);//演算子をポップする(優先度1) } else if(s.equals("*") || s.equals("/")){ gotOper(s,2);//演算子をポップする(優先度2) } else if(s.equals("^")){ gotOper(s,3);//演算子をポップする(優先度3) } else if(s.equals("(")){ theStack.push(s);//"("をプッシュする } else if(s.equals(")")){ gotParen(s);//演算子をポップする } else{//数値だった output.add(s);//そのまま出力 } } while(!theStack.empty()){//のこりの演算子をポップする output.add(theStack.pop()); } return output;//出力を返す } private static void gotOper(String opThis,int prec1){ while(!theStack.empty()){ String opTop=(String)theStack.pop(); if(opTop.equals("(")){//開きかっこのときは元に戻す theStack.push(opTop); break; } else{ int prec2;//演算子なら if(opTop.equals("+") || opTop.equals("-")){//優先度を判断して prec2=1; } else{ prec2=2; } if(prec2 < prec1){//この演算子の優先度が前のより低ければ theStack.push(opTop);//プッシュする break; } else{//そうでなければ output.add(opTop);//出力 } } } theStack.push(opThis);//あらたな演算子をプッシュする } private static void gotParen(String s){ while(!theStack.empty()){ String sx=(String)theStack.pop(); if(sx.equals("(")){//開きかっこをポップしたら break;//終わり } else{//演算子をポップしたら output.add(sx);//それを出力する } } } } //////////////////////////////////////////////////////////////////////////////// //後置記法評価クラス ParsePost // //doParse関数 //引数:Vector 計算式をトークンに分けたもの(Stringの可変長配列),後置記法 //返り値:double 計算結果 // //////////////////////////////////////////////////////////////////////////////// class ParsePost{ private static Stack theStack;//スタック public static double doParse(Vector input){ theStack=new Stack(); String s; int j; double num1,num2,interAns; for(j=0;j < input.size();j++){ s=(String)input.get(j); try{//数値に変換してみる,変換できたらプッシュ theStack.push(Double.toString(Double.parseDouble(s))); } //数値でなかったとき catch(java.lang.NumberFormatException e){ //数値をポップ num2=Double.parseDouble((String)theStack.pop()); num1=Double.parseDouble((String)theStack.pop()); //それぞれ演算実行 if(s.equals("+")){ interAns=num1+num2; } else if(s.equals("-")){ interAns=num1-num2; } else if(s.equals("*")){ interAns=num1*num2; } else if(s.equals("/")){ interAns=num1/num2; } else if(s.equals("^")){ interAns=Math.pow(num1,num2); } else{//入力エラー throw e; } theStack.push(Double.toString(interAns));//計算結果をプッシュ } } interAns=Double.parseDouble((String)theStack.pop());//最終結果をポップ if(!theStack.empty()){//まだスタックが空でなかったらエラー throw new java.lang.NumberFormatException();//何でもいいから例外を投げる } return interAns;//結果を返す } }
/* 計算機プログラミングI 火曜五限(田中先生) 11/02 課題 - オプション課題 XXXXXX/ XXXXXX XXXXXX /XXXXXX gXXXXXX@mail.ecc.u-tokyo.ac.jp 2004/11/08(Mon) 中置記法で書かれた数式を後置記法に変換して計算し、結果を返します。 種々の演算子及び数学関数・定数、変数の代入をサポートします。 */ import java.util.*; import java.io.*; // 例外(変数が定義されていない;文法が間違っている) class NotDefinedException extends Exception {} class SyntaxErrorException extends Exception {} public class Option1102 { // enum の代わりとなるものを知らないのでこんなことに.. static final int VALUE = 0; static final int UNARY_OP = 1; static final int BINARY_OP = 2; static final int PAREN_L = 3; static final int PAREN_R = 4; static final int NON = 5; static final int FUNC = 6; static final int CONSTANT = 7; static final int VAR = 8; // 変数 static VarList var_list; // 中置記法を後置記法へと変換する。 // 入力、出力ともに Token の Vector public static Vector convertToPN(Vector tks) throws SyntaxErrorException { Token now; Stack st_op = new Stack(); Vector vec_ret = new Vector(); int index = 0; boolean prev_num = false; // - の処理のために使う。 // 優先度最小の仮想演算子を追加する。 tks.add(new Token("N")); st_op.push(new Token("N")); for(;;) { if (index >= tks.size()) break; // トークンを読み込む now = (Token)tks.elementAt(index++); // 値ならばそのまま素通りさせて並べる。 if (now.attr == VALUE || now.attr == CONSTANT || now.attr == VAR) { vec_ret.add(now); prev_num = true; continue; } // "(" ならば、優先順位を気にせずに積む。 // ")" ならば、"(" までの演算子を書き出す。 if (now.attr == PAREN_L) { st_op.push(now); continue; } else if (now.attr == PAREN_R) { for(Token t ;;) { try { t = (Token)st_op.pop(); } catch (Exception e) { throw new SyntaxErrorException(); } if (t.attr == PAREN_L) { break; } else { vec_ret.add(t); } } continue; } else if (now.attr == NON) { // 終端まで来たら、今までのを全部書き出す for(Token t;;) { if (!st_op.empty()) { t = (Token)st_op.pop(); } else { break; } vec_ret.add(t); } return vec_ret; } // - については単項演算子か二項演算子かの区別が必要。 // 前が数字ならば二項演算子、そうでないなら単項演算子。 if (now.elem.equals("-")) { now.attr = prev_num ? BINARY_OP : UNARY_OP; } // 演算子なら、スタック最上位のものと優先順位を比較する // 現在の演算子のほうが優先順位が大きれば、そのまま積む。 Token foo = (Token)st_op.pop(); if (foo.Priority() < now.Priority()) { st_op.push(foo); st_op.push(now); } else if (foo.Priority() == now.Priority()) { vec_ret.add(now); st_op.push(foo); } else { // 現在の演算子よりも小さいときは、最上位の方を並べる。 vec_ret.add(foo); // さらに比較する for(;;) { Token bar = (Token)st_op.pop(); if (bar.Priority() == now.Priority()) { // 同じならば、積まれていた方を並べる(つまり左結合) vec_ret.add(bar); } else { // 自分のほうが高ければ積まれる(低いことはありえない) st_op.push(bar); st_op.push(now); break; } } } prev_num = false; } return vec_ret; } // 後置記法で記述されたトークン列を計算する。 public static double calculate(Vector ts) throws NotDefinedException, SyntaxErrorException { double result = 0; int index = 0; Stack s = new Stack(); for(;;) { // まずトークンを読む Token t = (Token)ts.elementAt(index++); // 読み終えたときに値が一つだけならよろしい if (t.attr == NON) { try { double d = ((Double)s.pop()).doubleValue(); if (s.empty()) return d; else throw new SyntaxErrorException(); } catch (Exception e) { throw new SyntaxErrorException(); } } // 数字ならスタックに積んでおく if (t.attr == VALUE) { s.push(new Double(Double.parseDouble(t.elem))); continue; } else if (t.attr == CONSTANT) { if (t.elem.equalsIgnoreCase("PI")) { s.push(new Double(Math.PI)); } else if (t.elem.equalsIgnoreCase("E")) { s.push(new Double(Math.E)); } continue; } // 変数の場合は登録されているかどうか調べる if (t.attr == VAR) { try { s.push(new Double(var_list.getVar(t.elem))); continue; } catch (NotDefinedException e) { System.out.println("変数 " + t.elem + " は定義されていません。"); throw e; } } // 二項演算子だった if (t.attr == BINARY_OP) { double a, b; try { a = ((Double)s.pop()).doubleValue(); b = ((Double)s.pop()).doubleValue(); } catch (EmptyStackException e) { throw new SyntaxErrorException(); } s.push(new Double(t.op.operate(a, b))); } else if (t.attr == UNARY_OP || t.attr == FUNC) { // 単項演算子もしくは数学関数だった double a; try { a = ((Double)s.pop()).doubleValue(); } catch (EmptyStackException e) { throw new SyntaxErrorException(); } s.push(new Double(t.op.operate(a))); } } } // 使い方を表示(ファイルから読んだほうがいいのかもしれない) public static void showHelp() { System.out.println("Option1102 - A simple calculator"); System.out.println("Usage:"); System.out.println("[使用可能な演算子]"); System.out.println(" +, -, *, /, %(余り), ^(累乗), =(等しければ 1、そうでなければ 0)"); System.out.println("[使用可能な関数]"); System.out.println(" sin, cos, tan, log(常用対数), ln(自然対数), lg(底2), sqrt(平方根), exp"); System.out.println("[定数]"); System.out.println(" PI = 3.141592653589793"); System.out.println(" E = 2.718281828459045"); System.out.println(""); System.out.println("(変数名) = (式) で変数への代入ができます(大文字小文字の区別はありません)。"); System.out.println("例: theta = PI/4"); System.out.println(" height = 3 * sin(theta)"); System.out.println(""); } public static void main(String args[]) throws IOException, ArrayIndexOutOfBoundsException, NotDefinedException, SyntaxErrorException { // キーボードから読み込むためのBufferedReaderの作成 BufferedReader r = new BufferedReader(new InputStreamReader(System.in)); String s = null; var_list = new VarList(); for(;;) { System.out.println("(help:使い方を表示, quit:終了, var:変数一覧を表示)"); System.out.print("> "); // 1行読み込み if ((s = r.readLine()) == null) break; if (s.length() == 0) continue; // ヘルプ表示と終了 if (s.equalsIgnoreCase("quit")) break; if (s.equalsIgnoreCase("help")) { showHelp(); continue; } if (s.equalsIgnoreCase("var")) { var_list.showVars(); continue;} // 記号を切れ目にして字句(token)を読むtokenizerを作成 StringTokenizer st = new StringTokenizer(s,"*%/+-()= ",true); Vector tokens = new Vector(); while(st.hasMoreTokens()) { String ss; ss = st.nextToken(); // 空白は読み飛ばす。 if (!ss.equals(" ")) tokens.add(new Token(ss)); } // 代入命令 if (tokens.size() > 2 && ((Token)tokens.elementAt(1)).elem.equals("=")) { String key = ((Token)tokens.elementAt(0)).elem; tokens.remove(0); tokens.remove(0); double val; try { val = calculate(convertToPN(tokens)); } catch (Exception e) { System.out.println("Syntax Error"); continue; } var_list.addVar(key, val); System.out.println("val " + key + " := " + val); continue; } // 計算部分 try { /* 後置記法に直したの表示させてみる Vector hoge = convertToPN(tokens); for(int i=0; i < hoge.size(); i++) System.out.println(((Token)hoge.elementAt(i)).elem); */ System.out.println(calculate(convertToPN(tokens))); } catch (NotDefinedException e) { continue; } catch (SyntaxErrorException e) { System.out.println("Syntax Error"); continue; } } } } // 二項演算子 class Operator { Operator(){} public int priority = 0; public double operate(double a, double b){ return a; } public double operate(double a){ return a; }; } class Plus extends Operator { Plus() { priority = 8; } public double operate(double a, double b) { return a + b; } public double operate(double a) {return a;} } class Minus extends Operator { Minus() { priority = 8; } public double operate(double a, double b) { return a - b; } public double operate(double a) { return -a; } } class Multiple extends Operator { Multiple() { priority = 9; } public double operate(double a, double b) { return a*b; } } class Divide extends Operator { Divide() { priority = 9; } public double operate(double a, double b) { return b/a; } } class Power extends Operator { Power() { priority = 9; } public double operate(double a, double b) { return Math.pow(b, a); } } class Mod extends Operator { Mod() { priority = 9; } public double operate(double a, double b) { return b % a; } } class Edge extends Operator { Edge() { priority = 0; } } class Paren_L extends Operator { Paren_L() { priority = 1; } } class Equal extends Operator { Equal() { priority = 10; } public double operate(double a, double b) { return a == b ? 1.0 : 0.0; } } // 数学関数は単項演算子として扱う class Sine extends Operator { Sine() { priority = 11; } public double operate(double a) { return Math.sin(a); } } class Cosine extends Operator { Cosine() {priority = 11;} public double operate(double a) { return Math.cos(a); } } class Tangent extends Operator { Tangent() {priority = 11;} public double operate(double a) { return Math.tan(a); } } class Exp extends Operator { Exp() { priority = 11; } public double operate(double a) { return Math.exp(a); } } class Sqrt extends Operator { Sqrt() { priority = 11; } public double operate(double a) { return Math.sqrt(a); } } // 対数関数 // 底として -1 を与えると自然対数の底とみなす。 class Log extends Operator { double base = 10; Log(double b) { priority = 11; base = b; } public double operate(double a) { if (base == -1) { return Math.log(a); } else { return Math.log(a) / Math.log(base); } } } // 変数リスト // 文字列をキーとして線形に探索する class VarList { // 内部クラス Var class Var { public String key; public Double value; Var(String k, double d) { key = k; value = new Double(d); } } Vector vars; public VarList() { vars = new Vector(); } // 変数を追加 public void addVar(String key, double d) { for(int i = 0; i < vars.size(); i++) { if (((Var)vars.elementAt(i)).key.equalsIgnoreCase(key)) { ((Var)vars.elementAt(i)).value = new Double(d); return ; } } vars.add(new Var(key, d)); } // キーから値を検索(でもハッシュではなく単なる線形探索) public double getVar(String key) throws NotDefinedException { for(int i = 0; i < vars.size(); i++) { if (((Var)vars.elementAt(i)).key.equalsIgnoreCase(key)) { return ((Var)vars.elementAt(i)).value.doubleValue(); } } throw new NotDefinedException(); } // 変数一覧を表示(これは VarList クラスにやらせる仕事か?という疑問が残る。) public void showVars() { for(int i = 0; i < vars.size(); i++) { System.out.println(" " + ((Var)vars.elementAt(i)).key + " = " + ((Var)vars.elementAt(i)).value); } } } // 今回の主役となるトークン class Token { static final int VALUE = 0; static final int UNARY_OP = 1; static final int BINARY_OP = 2; static final int PAREN_L = 3; static final int PAREN_R = 4; static final int NON = 5; static final int FUNC = 6; static final int CONSTANT = 7; static final int VAR = 8; public Token(){} public Token(String e) { elem = e; // 二項演算子たち if (e.equals("*")) { op = new Multiple(); } else if (e.equals("/")) { op = new Divide(); } else if (e.equals("+")) { op = new Plus(); } else if (e.equals("-")) { op = new Minus(); } else if (e.equals("^")) { op = new Power(); } else if (e.equals("%")) { op = new Mod(); } else if (e.equals("=")) { op = new Equal(); } // ここまでで引っ掛かれば二項演算子 if (op != null) { attr = BINARY_OP; return ; } // 終端記号(優先順位の最も低い仮想的な演算子) if (e.equals("N")) { op = new Edge(); attr = NON; return ; } // 単項演算子及び数学関数 if (e.equalsIgnoreCase("sin")) { op = new Sine(); } else if (e.equalsIgnoreCase("cos")) { op = new Cosine(); } else if (e.equalsIgnoreCase("tan")) { op = new Tangent(); } else if (e.equalsIgnoreCase("log")) { op = new Log(10); } else if (e.equalsIgnoreCase("ln")) { op = new Log(-1); } else if (e.equalsIgnoreCase("lg")) { op = new Log(2); } else if (e.equalsIgnoreCase("exp")) { op = new Exp(); } else if (e.equalsIgnoreCase("sqrt")) { op = new Sqrt(); } // ここまでで引っ掛かれば関数 if (op != null) { attr = FUNC; return ; } // 定数 if (e.equalsIgnoreCase("PI") || e.equalsIgnoreCase("E")) { attr = CONSTANT; return ; } // 括弧 if (e.equals("(")) { op = new Paren_L(); attr = PAREN_L; return ; } else if (e.equals(")")) { attr = PAREN_R; return ; } // 数値 if (isnum(e)) { attr = VALUE; return ; } // どれでもなければ、変数 // (つまり 12. や 2.2.2 なども変数になりうる) attr = VAR; } // parseDouble で変換できるようなものか public static boolean isnum(String s) { boolean period = false; for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); if ( !('0' <= c && c <= '9') ) { if (c == '.') { // ピリオドが登場した if (period) return false; // 二個以上.があれば数値ではない else if (i == s.length()-1) { return false; // ピリオドで終わるのも(12. とかでも)数値ではない。 } else { period = true; } } else { return false; // ヨーロッパでは , が小数点記号なのだがそれは気にしない。 } } } return true; } // 優先順位を得る(数値の場合は意味をなさない) public int Priority() { if (attr == VALUE) return -1; else return op.priority; } Operator op = null; int attr = VALUE; int priority = 0; public String elem; }