SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

即戦力にならないといけない人のためのJava入門

Javaの基本的な演算と制御構文

即戦力にならないといけない人のためのJava入門 第3回

2.3. 文字列での+演算子

 また、「+」の演算子は今までのサンプルでよく見かけるように、数値だけでなく文字列を連結させる際に使うことができます。ただし、注意点として、文字列の後に「+」の演算子をつけると、それ以降の数値もすべて文字列として扱われることが挙げられます。そのため、次のような場合、

"1 + 1 = " + 1 + 1
↓
"1 + 1 = " + "1" + "1"

と見なされ、出力される結果は「1 + 1 = 11」という文字列です。もし、数値の計算をしつつ、文字列との連結をさせる必要がある場合は、次のように数値の部分を丸カッコ「()」を使うことによって、数値として計算させることができます。

"1 + 1 = " + (1 + 1)

 この場合、出力される結果は「1 + 1 = 2」です。

 また、文字連結の「+」演算子は左から計算されていくため、文字列の前に数値があると数値の計算として扱われます。例えば、

1 + 2 + "3"

は「123」ではなく文字列の前の数値としての「1 + 2」が評価され、結果として「33」と表示されます。ただし、業務アプリでの開発の場合、この記述だと、仮に後で変更があり、数値の前に文字列が来た場合にきちんと対処していないと意図した値にならないので、基本は文字列内の数値計算は「()」(丸カッコ)内で行うようにします。

 では、次のサンプルを実行してみましょう。

ArithmeticOperator3.java
package jp.codezine.java.sample03;

/**
 * 算術演算子を確認するクラス。
 */
public class ArithmeticOperator3 {

    /**
     * 算術演算を実行し、その内容を確認します。
     * 
     * @param args
     *            コマンドライン引数。今回は使われません。
     */
    public static void main(String[] args) {

        // [1] カッコなし
        System.out.println("[1] 1 + 1 = " + 1 + 1);

        // [2] カッコあり
        System.out.println("[2] 1 + 1 = " + (1 + 1));

        // [3] 数値が左
        System.out.println(1 + 2 + "3");
    }
}
実行結果:
[1] 1 + 1 = 11
[2] 1 + 1 = 2
33

2.4. 浮動小数点の数値を扱った算術演算の注意点

 今まで見てきたように、数値の計算を算術演算子で行うことは可能です。しかし、doubleやfloatなど浮動小数点の値で計算する場合は注意が必要です。それは浮動小数点の値を計算した場合、誤差が発生し意図した値にならないためです。まずは次のサンプルを実行してどのような結果になるのか見てみましょう。

ArithmeticOperator4.java
package jp.codezine.java.sample03;

/**
 * 算術演算子を確認するクラス。
 */
public class ArithmeticOperator4 {

    /**
     * 算術演算を実行し、その内容を確認します。
     * 
     * @param args
     *            コマンドライン引数。今回は使われません。
     */
    public static void main(String[] args) {

        // [1] 注意点(浮動小数点の計算)
        double resultDouble = 0.0;
        resultDouble = 0.7 + 0.1;
        System.out.println("[1] 0.7 + 0.1 = " + resultDouble);
    }
}

 これを実行すると次の結果が表示されます。

[1] 0.7 + 0.1 = 0.7999999999999999

 この誤差はJavaだけではなくほとんどのプログラミング言語にある問題で、原因はソースコード上で記述された10進数をJavaの実行環境内で2進数に変換して計算しているため、浮動小数点の数値を扱うと、この10進数と2進数との変換の際に誤差が生じてしまうためです。

 そのため、金額の計算など誤差の発生が許されず正確な数値を求められる場合、次のBigDecimalクラスを使って計算します。

2.5. BigDecimalでの計算

 Javaでは、浮動小数点の数値に対して演算子を使って計算した場合に誤差が発生し、意図した値になりません。その誤差を回避するために、Javaではjava.math.BigDecimalというクラスが用意されています。そして正確な値が必要な計算の場合、このBigDecimalクラスを使って計算が行われます。

 BigDecimalでの計算を行う際は、まずBigDecimalのコンストラクタの引数に数値を文字列にしたものを渡して、それぞれの数値を持つBigDecimalのインスタンスを生成します。そしてBigDecimalが持つメソッドを使って、計算結果の値を持つBigDecimalを結果として生成します。BigDecimalでは算術演算子と同様に次のメソッドが用意されています。

メソッド名 同等の演算子 概要
add + 足し算(加算)
subtract - 引き算(減算)
multiply * 掛け算(乗算)
divide / 割り算(除算)
remainder % 割り算の余り(剰余算)

 ここで注意すべき点は、BigDecimalを使って割り算を計算する場合、割り切れない数値を計算するとjava.lang.ArithmeticExceptionの例外が発生します。そのため、割り算する場合は、計算する小数点の桁数と桁からはみ出ている端数を切り捨てるのか切り上げるのかなどの処理を設定することが基本です。この端数処理(丸め処理)は、すでにJavaで用意されているものがありjava.math.RoundingModeで定義されているものを使う場合がほとんどです。よく使われるRoundingModeは次のものになります。

RoundingModeの定数 概要
HALF_UP 四捨五入
UP 切り上げ。0から離れるように切り上げます。負の値の場合、例えば-5.5の小数点以下第1位を丸める場合、-6になります。
DOWN 切り捨て。0に近づくよう切り捨てます。負の値の場合、例えば-5.5の小数点以下第1位を丸める場合、-5になります。

 また、切り詰めの際に、負の値の考え方にもよるのですが、次のものも業務アプリでは見かけることが多いです。

RoundingModeの定数 概要
CEILING 切り上げ。正の無限大に近づくように切り上げます。正の値の場合はUPと同じです。しかし負の値の場合、例えば-5.5の小数点以下第1位を丸める場合、-5になります。
FLOOR 切り捨て。負の無限大に近づくよう切り捨てます。正の値の場合はDOWNと同じです。しかし負の値の場合、例えば-5.5の小数点以下第1位を丸める場合、-6になります。

 それではBigDecimalがどのように使われるのか見てみましょう。次のサンプルを作成します。

package jp.codezine.java.sample03;

import java.math.BigDecimal;
import java.math.RoundingMode;

/**
 * BigDecimalのサンプルを実行しその内容を確認するクラスです。
 */
public class BigDecimalSample1 {

    /**
     * BigDecimalでの計算を実行し、その内容を確認します。
     * 
     * @param args
     *            コマンドライン引数。今回は使われません。
     */
    public static void main(String[] args) {
        // [1] 足し算
        BigDecimal value1 = new BigDecimal("0.7");
        BigDecimal value2 = new BigDecimal("0.1");
        BigDecimal result = value1.add(value2);
        System.out.println("[1] 0.7 + 0.1 = " + result);

        // [2] 引き算
        result = value1.subtract(value2);
        System.out.println("[2] 0.7 - 0.1 = " + result);

        // [3] 掛け算
        result = value1.multiply(value2);
        System.out.println("[3] 0.7 × 0.1 = " + result);

        // [4] 割り算
        value1 = new BigDecimal("7.0");
        value2 = new BigDecimal("3.0");
        result = value1.divide(value2, 0, RoundingMode.DOWN); // 小数点未満を切り捨て
        System.out.println("[4] 7.0 ÷ 3.0 = " + result);

        // [5] 余り
        value1 = new BigDecimal("7.0");
        value2 = new BigDecimal("3.0");
        result = value1.remainder(value2);
        System.out.println("[5] 7.0 % 3.0 = " + result);
    }
}

 これを実行すると次の結果が表示されます。

[1] 0.7 + 0.1 = 0.8
[2] 0.7 - 0.1 = 0.6
[3] 0.7 × 0.1 = 0.07
[4] 7.0 ÷ 3.0 = 2
[5] 7.0 % 3.0 = 1.0

 この結果を見ると分かるように、BigDecimalを使って計算を行うと、算術演算子を使った場合に発生していた誤差はなくなって意図した値になっています。このように浮動小数点の誤差が許されない計算ではBigDecimalを使った計算が基本になります。

 ただし、BigDecimalを使って計算する場合も注意点があります。BigDecimalのコンストラクタの中には、引数が浮動小数点の数値を受けとるものがあります。ここで、BigDecimalのコンストラクタの引数に浮動小数点の数値を渡した場合、2進数で扱われているdouble値をBigDecimalが受け取った際に、内部で持つ10進数への変換で誤差が発生するためです。そのため、算術演算子の場合と同様、処理結果に誤差が発生してしまいます。そういう理由でBigDecimalのコンストラクタの引数に浮動小数点の数値を渡さないようにしてください。次のサンプルを実行すると、処理結果が意図したものにならないことが分かります。

BigDecimalSample2.java
package jp.codezine.java.sample03;

import java.math.BigDecimal;

/**
 * BigDecimalのサンプルを実行しその内容を確認するクラスです。
 */
public class BigDecimalSample2 {

    /**
     * BigDecimalでの計算を実行し、その内容を確認します。
     * 
     * @param args
     *            コマンドライン引数。今回は使われません。
     */
    public static void main(String[] args) {
        // [1] コンストラクタに浮動小数点を渡して計算
        BigDecimal value1 = new BigDecimal(0.7);
        BigDecimal value2 = new BigDecimal(0.1);
        BigDecimal result = value1.add(value2);

        System.out.println("value1= " + value1);
        System.out.println("value2= " + value2);
        System.out.println("0.7 + 0.1 = " + result);
    }
}
実行結果:
value1= 0.6999999999999999555910790149937383830547332763671875
value2= 0.1000000000000000055511151231257827021181583404541015625
0.7 + 0.1 = 0.7999999999999999611421941381195210851728916168212890625

 この問題の解決策として、BigDecimalにdouble値を引数にとるvalueOfメソッドでBigDecimalのインスタンスを生成する方法があります。この方法でBigDecimalのインスタンスを生成すると、コンストラクタの引数に文字列を入れた場合と同様のインスタンスを生成します。

★★ここまでCHK★★

 しかし、BigDecimalのvalueOfメソッドの引数にfloat値を入れた場合、結果はまたしても意図したものになりません。これはBigDecimalのvalueOfメソッドの引数にfloat値のものが用意されておらず、Javaがfloat値をdouble値に自動で変換して処理を行っているためです。しかし、その際にfloatが持つ精度とdoubleが持つ精度では差があるため、この変換の時点で誤差が発生してしまいます。そのため、floatが暗黙的にdoubleに変換された際は、その精度の違いにより意図したdoubleの値ではなくなってしまいます。それでは次のサンプルを見てください。

BigDecimalSample3.java
package jp.codezine.java.sample03;

import java.math.BigDecimal;

/**
 * BigDecimalのサンプルを実行しその内容を確認するクラスです。
 */
public class BigDecimalSample3 {

    /**
     * BigDecimalでの計算を実行し、その内容を確認します。
     * 
     * @param args
     *            コマンドライン引数。今回は使われません。
     */
    public static void main(String[] args) {
        // [1] BigDecimal.valueOfメソッドを使った場合
        BigDecimal value1 = BigDecimal.valueOf(0.7);
        BigDecimal value2 = BigDecimal.valueOf(0.1);
        BigDecimal result = value1.add(value2);
        System.out.println("[1] 0.7 + 0.1 = " + result);

        // [2] floatの場合
        value1 = BigDecimal.valueOf(0.7f);
        value2 = BigDecimal.valueOf(0.1f);
        result = value1.add(value2);
        System.out.println("[2] 0.7 + 0.1 = " + result);

        // [3] floatがdoubleに自動変換された際の誤差
        double convertedValue1 = 0.7f;
        double convertedValue2 = 0.1f;
        System.out.println("[3] 0.7f を double に変換 → " + convertedValue1);
        System.out.println("[3] 0.1f を double に変換 → " + convertedValue2);
    }
}
実行結果:
[1] 0.7 + 0.1 = 0.8
[2] 0.7 + 0.1 = 0.79999998956918712
[3] 0.7f を double に変換 → 0.699999988079071
[3] 0.1f を double に変換 → 0.10000000149011612

 そのため、float値を扱う場合は注意が必要です。どうしてもfloatを使って正確な計算を行わないといけない場合、double値に自動的に変換される前にFloat.toStringメソッドで文字列に変換し、BigDecimalを生成するようにしましょう。

 また、BigDecimalのコンストラクタの引数に文字列を渡して計算した場合、引数の小数点以下の桁数が多いほうに結果の小数点の桁数をあわせるようになっています。そのため、小数点以下の桁数が決まっている場合はBigDecimalのsetSacaleメソッドを使って桁数を調節するようにしてください。

BigDecimalSample4.java
package jp.codezine.java.sample03;

import java.math.BigDecimal;
import java.math.RoundingMode;

/**
 * BigDecimalのサンプルを実行しその内容を確認するクラスです。
 */
public class BigDecimalSample4 {

    /**
     * BigDecimalでの計算を実行し、その内容を確認します。
     * 
     * @param args
     *            コマンドライン引数。今回は使われません。
     */
    public static void main(String[] args) {
        // [1] 小数点の桁数が違う場合の計算
        BigDecimal value1 = new BigDecimal("0.7");
        BigDecimal value2 = new BigDecimal("0.10");
        BigDecimal result = value1.add(value2);
        System.out.println("[1] 0.7 + 0.10 = " + result);

        // [2] setScaleメソッドを使って小数点の桁数を調整
        value1 = new BigDecimal("0.7");
        value2 = new BigDecimal("0.10");
        result = value1.add(value2);
        result = result.setScale(1, RoundingMode.HALF_UP); // 小数点2位以下を四捨五入
        System.out.println("[2] 0.7 + 0.10 = " + result);

    }
}]
実行結果:
[1] 0.7 + 0.10 = 0.80
[2] 0.7 + 0.10 = 0.8

MathクラスとStrictMathクラス

 正確な計算結果を求められる業務アプリの開発では、あまり使われることはありませんが、Javaでは基本的な四則計算のほかに平方根や三角関数など、さまざまな計算を行うためのメソッドを用意したjava.langMathクラスとjava.lang.StrictMathクラスがあります。Mathクラスは実行環境が持つ算出処理を利用して算出結果を返すのに対し、StrictMathクラスはすべてのJava環境で同じ結果を返すようにしています。そのため、環境によってはMathクラスとStrictMathクラスの間で実行結果の差異がでることがあります。

 またMathクラスのほうは実行環境の算出処理を利用しているため、StrictMathよりも早く算出できるものとして理論上考えられています。

次のページ
3. 比較演算

この記事は参考になりましたか?

即戦力にならないといけない人のためのJava入門連載記事一覧

もっと読む

この記事の著者

長谷川 智之(株式会社ビーブレイクシステムズ)(ハセガワ トモユキ)

株式会社ビーブレイクシステムズ 開発部所属。社内サークルの執筆チームに在籍しています。主な執筆は下記になります。・@IT連載『Javaの常識を変えるPlay framework入門』※複数著者での連載 ・日経ソフトウェア連載『コツコツ学ぶAndroidネイティブアプリ開発教室』※複数著者での連載 ・@I...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/7840 2015/07/27 14:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー