LoginSignup
shun03
@shun03

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

Javaの計算関係

解決したいこと

Javaで、以下のように6つの演算があり、
kadai.javaの引数に数値と演算子を入力して出力することによって
計算がされるコードを書いておりましたが、
乗算、除算、累乗が想定していた結果と違っております。
解決方法がありましたらお願いします。

発生している問題

// 乗算
現状:入力値が正しくありません
理想:11*7=77.0

//除算
現状:11/7=1.0
理想:11/7=1.5714285714285714

//累乗
現状:11/7=.0
理想:11/7=19487171.0

該当するソースコード

Calc.java

package com.lupinus_ltd;

public class Calc {

    private int total;
    public Calc () {
        init();
    }

        public void add(int operand1,int operand2) {
            total = operand1 + operand2;
        }   
        public void sub(int operand1,int operand2) {
            total = operand1 - operand2;
        }
        public void multi(int operand1,int operand2) {
            total = operand1 * operand2;
        }       
        public void div(int operand1,int operand2) {
            total = operand1 / operand2;
        }         
        public void mod(int operand1,int operand2) {
            total = operand1 % operand2;
        }       
        public void pow(int operand1,int operand2) {
            int count = 1;
            for (int i = 0; i < operand2; i++) {
                count =count * operand1;
            }
        }

        public int getTotal() {

            return total;
        }

        public void init() {
            total = 0;
        }

}

kadai.java

import java.math.BigDecimal;
import java.text.DecimalFormat;

import com.lupinus_ltd.Calc;

public class kadai4 {


    public static void main(String[]args) {

        Calc c = new Calc();
    try {

        int operand1 = Integer.parseInt(args[0]);
        String operator = args[1];
        int operand2 = Integer.parseInt(args[2]);

        switch(operator) {
        case "+":
            c.add(operand1,operand2);
            break;
        case "-":
            c.sub(operand1,operand2);
            break;
        case "*":
            c.multi(operand1,operand2);
            break;
        case "/":
            c.div(operand1,operand2);
            break;
        case "%":
            c.mod(operand1,operand2);
            break;  
        case "^":
            c.pow(operand1,operand2);
            break;
        }

        BigDecimal total = BigDecimal.valueOf(c.getTotal());
        if(total.scale() == 0) {
            DecimalFormat format = new DecimalFormat("#.0");
            System.out.println(operand1 + "" + operator + "" + operand2 + "=" + format.format(total));
        } else {
            System.out.println(operand1 + operator + operand2 + "=" + total.toPlainString());
        }

    }catch(NumberFormatException e) {
            System.out.println("入力値が正しくありません");
        }
    }
}



自分で試したこと

1、2時間ほど検索やコードを書き、乗算の演算子(*)をコピペしたり除算に(double)を入れてみたり
累乗計算するためのfor文を見直したりしましたが、現状から変わりませんでした。

0

2Answer

乗算のところで結構ややこしい事が起こっているので、除算と累乗は今は無視します。
このプログラムを実行するとき、コマンドライン上で次のように実行すると思います。

java kadai4 11 + 7
java kadai4 11 - 7
java kadai4 11 * 7
java kadai4 11 / 7
java kadai4 11 % 7
java kadai4 11 ^ 7

このとき、メインメソッドが受け取る文字列配列argsの内容を確認してみると、乗算以外は

args = ["11", "+", "7"]
args = ["11", "-", "7"]
以下略

こんな感じで、予想通りの値が入っています。
しかし、乗算のときだけおかしな事になります。

args = ["11", "kadai4.java", "kadai4.class", "その他ファイル名", "7"]

このように、乗算記号のアスタリスク(*)がファイル名になってしまいます。
これは、コマンドラインではアスタリスクが特殊な文字として扱われ、カレントディレクトリの全てのファイル名に展開されるからです。(詳しくはワイルドカードで調べてみてください)

これを防ぐには、次のように、実行時のコマンドでバックスラッシュやクォートで囲うなどしなければなりません。

java 11 \* 7
java 11 "*" 7
java 11 '*' 7

とりあえずこれで乗算のところのエラーはなくなると思います。

1

Comments

  1. @shun03

    Questioner
    ご回答ありがとうございます。
    「*」の特殊性について理解できました。
    教えていただいたことはバックアップを取って知識として頭に入れます。
    ありがとうございました。

* と ^ はコマンドプロンプト上では特殊な文字として扱われ、それ単体でコマンドとなるため
文字として認識させたいのであれば""で囲うなりエスケープする必要があります。

今回は乗算記号は*ではなくxで代用し、累乗記号の^は^^と入力してエスケープすることにしました。

そして計算結果を表すtotalは、面倒なのでint型ではなく小数を扱えるdouble型で宣言
ただしこれだけでは除算結果は小数点以下が切り捨てられます。

total = operand1 / operand2;

ここの処理ですが、operand1, operand2ともにint型です
仕様として、int型同士の計算結果はint型になります。そのため、もし小数を含む計算結果が欲しい場合は、doubleやfloat形に明示的型変換(キャスト)をしてあげる必要があります。

total = (double)operand1 / operand2;

そのため、今回はこのようにしました。
といっても

除算に(double)を入れてみたり

とあるところを見ると、このキャスト自体はすでに試していそうですね。
うまくいかなかった理由は、totalがint型のままだったからだと思います。
double型 → int型のような、データの大きな型から小さな型へ変換する場合、値の一部が欠如します。この例なら小数点以下が切り捨てられる感じですね

累乗計算自体の処理は問題ないのですが

public void pow(int operand1,int operand2) {
    int count = 1;
    for (int i = 0; i < operand2; i++) {
        count =count * operand1;
    }
}

このままでは、計算結果はcountに入ったままとなるためc.getTotal()では0の入ったtotalしか取得できません。
別変数を宣言する必要性を感じなかったため、totalを流用しました。

public void pow(int operand1,int operand2) {
    total = 1;
    for (int i = 0; i < operand2; i++) {
        total = total * operand1;
    }
}

また、今後このようなメソッドを作る場合

public double pow(int operand1,int operand2) {
    total = 1;
    for (int i = 0; i < operand2; i++) {
        total = total * operand1;
    }
    return total
}

のように、戻り値をvoidにしてフィールドをgetするメソッドを準備するより、これら6つのメソッドが結果を戻す形にしたほうが楽だと思います。
構造を見るにprivate修飾子とゲッターを利用する試みなのでしょうが...

そしてKadai4.javaですが

BigDecimal total = BigDecimal.valueOf(c.getTotal());
if(total.scale() == 0) {
    DecimalFormat format = new DecimalFormat("#.0");
    System.out.println(operand1 + "" + operator + "" + operand2 + "=" + format.format(total));
} else {
    System.out.println(operand1 + operator + operand2 + "=" + total.toPlainString());
}

計算結果の表示に関わるここの処理の必要性が全く分からなかったためがっつり割愛しました。

というわけで自分なりに修正してみました。
ディレクトリ構成の再現までは面倒だったため意図的にpackage宣言とimport宣言は外しています。

Calc.java
public class Calc {
    private double total;
    public Calc () {
        init();
    }
    public void add(int operand1,int operand2) {
        total = operand1 + operand2;
    }   
    public void sub(int operand1,int operand2) {
        total = operand1 - operand2;
    }
    public void multi(int operand1,int operand2) {
        total = operand1 * operand2;
    }       
    public void div(int operand1,int operand2) {
        total = (double)operand1 / operand2;
        System.out.println(total);
    }         
    public void mod(int operand1,int operand2) {
        total = operand1 % operand2;
    }       
    public void pow(int operand1,int operand2) {
        total = 1;
        for (int i = 0; i < operand2; i++) {
            total = total * operand1;
        }
    }
    public double getTotal() {
        return total;
    }
    public void init() {
        total = 0;
    }
}
Kadai4.java
public class Kadai4 {
    public static void main(String[]args) {
        Calc c = new Calc();
        try {
            int operand1 = Integer.parseInt(args[0]);
            String operator = args[1];
            int operand2 = Integer.parseInt(args[2]);

            switch(operator) {
            case "+":
                c.add(operand1,operand2);
                break;
            case "-":
                c.sub(operand1,operand2);
                break;
            case "x":
                c.multi(operand1,operand2);
                break;
            case "/":
                c.div(operand1,operand2);
                break;
            case "%":
                c.mod(operand1,operand2);
                break;  
            case "^":
                c.pow(operand1,operand2);
                break;
            }
            System.out.println(operand1 + " " + operator + " " + operand2 + " = " + c.getTotal());
        }catch(NumberFormatException e) {
            System.out.println("入力値が正しくありません");
        }
    }
}

実行結果
>> java Kadai4 13 + 3
13 + 3 = 16.0

>> java Kadai4 13 - 3
13 - 3 = 10.0

>> java Kadai4 13 x 3
13 x 3 = 39.0

>> java Kadai4 13 / 3
13 / 3 = 4.333333333333333

>> java Kadai4 13 % 3
13 % 3 = 1.0

>> java Kadai4 13 ^^ 3
13 ^ 3 = 2197.0

特に実行結果の型を気にしないのであれば、これで要件は満たせてるかと思います。

1

Comments

  1. @shun03

    Questioner
    ご回答ありがとうございます。
    全ての計算ができるようになりました。

    また、
    BigDecimal total = BigDecimal.valueOf(c.getTotal());
    if(total.scale() == 0) {
    DecimalFormat format = new DecimalFormat("#.0");
    System.out.println(operand1 + "" + operator + "" + operand2 + "=" + format.format(total));
    } else {
    System.out.println(operand1 + operator + operand2 + "=" + total.toPlainString());
    }

    に関しましては、上記コードで計算をしなさいという条件がつけられていたため、書いていました。

    教えていただいたことはバックアップを取って知識として頭に入れます。
    ありがとうございました。
  2. なるほど、より実用的に誤差を少なくするためにはintやdoubleでは役不足なのでBigDecimalが必要みたいですね

    BigDecimal total = BigDecimal.valueOf(c.getTotal());
    if(total.scale() == 0) {
    DecimalFormat format = new DecimalFormat("#.0");
    System.out.println(operand1 + "" + operator + "" + operand2 + "=" + format.format(total));
    } else {
    System.out.println(operand1 + operator + operand2 + "=" + total.toPlainString());
    }

    ちなみにこの一連の処理が一つ一つどのような働きをしているかは説明されたりしていますか?
    分からない場合は、BigDecimalクラスのvalueOf(), scale(), toPlainString()メソッド、DecimalFormatクラスのコンストラクタやformat()メソッドの働きを
    https://docs.oracle.com/javase/jp/8/docs/api/java/math/BigDecimal.html
    こういったところで自力で参照しながら解読するほうが力が付きます。

Your answer might help someone💌