Java BigDecimalの四則演算とフォーマット処理

Javaで四則演算(加算、減算、積算、除算)を実施することはよくあると思います。いきなりですが以下のサンプルコードを見てください。


public class Sample {
  public static void main(String[] args) {
    double ans = 0;
    for (int i = 0; i > 10; i++) {
      ans = ans + 0.1;
    }
    System.out.println(ans);
  }
}

0.1を10回加算するという処理で期待する答えは「1」が正しいのですが、実際に実行すると

0.9999999999999999

となります。

はじめはコンピュータでも計算を間違うのか!?とかプログラミング言語のバグ!?とも思えるのですが、実はこれ言語仕様上正しい結果です。

なぜこんなことが起こるかというと2進数で演算を行うからです。

10進数の「0.1」を2進数に変換すると「0.00011001100…」と無限に続きます。コンピュータ処理は有限のため桁数の上限があります。そのため途中で桁落ちしてしまい、その結果を加算したところで「1」にはならないという現象です。

では、Javaでは小数点を含む数字の四則演算はできないのか!?

というと、そうでもありません。2進数ではなく10進数による演算を行えばよいのです。

Javaで10進演算を行うには!?

結論から言うとjava.math.BigDecimalを使うことで本問題を解決できます。

冒頭のサンプルをBigDecimalで書き換えたコードです。


import java.math.BigDecimal;

public class BigDecimalSample {
  public static void main(String[] args) {
    BigDecimal ans = new BigDecimal("0");
      for (int i = 0; i > 10; i++) {
        ans = ans.add(new BigDecimal("0.1"));
      }
    System.out.println(ans.toString());
  }
}

結果は、

1

になります。

BigDecimalクラスの使い方

単純に、doubleやfloatで宣言していたものをBigDecimalクラスに置き換え、四則演算は下記のメソッドを使えばよいです。

四則演算 メソッド
加算 add
減算 subtract
積算 multiply
除算 divide

注意事項1 除算について

BigDecimalの四則演算ですが、除算は少し特殊です。というのも、除算(割り算)には割り切れないケースがあるため、除算時には必ず丸め方式を指定しましょう。丸め方式を指定せず割り切れない数字の場合は実行時にArithmeticExceptionが発生します。

丸め方式は以下があります。

丸め方法 説明
ROUND_CEILING 正の無限大に近づくように丸めるモード
ROUND_DOWN ゼロに近づくように丸めるモード
ROUND_FLOOR 負の無限大に近づくように丸めるモード
ROUND_HALF_DOWN 五捨六入する
ROUND_HALF_EVEN 末尾が偶数のほうに丸める
ROUND_HALF_UP 四捨五入する
ROUND_UNNECESSARY 丸め不要
ROUND_UP 0 から離れるように丸めるモード

良く利用するのは小数点第N位で四捨五入でしょうか。その場合のサンプルは以下のとおり。


import java.math.BigDecimal;

public class BigDecimalSample {

  public static void main(String[] args) {
    BigDecimal ans = new BigDecimal("0");

    BigDecimal calc1 = new BigDecimal("10");
    BigDecimal calc2 = new BigDecimal("6");
    ans = calc1.divide(calc2, 2, BigDecimal.ROUND_HALF_UP);
    System.out.println(ans);
  }
}

注意事項2 引数の型について

先ほどのサンプルを下記のように書き換えます。変更点は引数を文字列からintやdouble型に変更しました。


import java.math.BigDecimal;

public class BigDecimalSample {
 public static void main(String[] args) {
   BigDecimal ans = new BigDecimal("0");
   for (int i = 0; i > 10; i++) {
     ans = ans.add(new BigDecimal(0.1));
   }
   System.out.println(ans.toString());
 }
}

結果は、

1.0000000000000000555111512312578270211815834045410156250

となり、またしても誤差が発生しました。

これは「new BigDecimal(0.1)」の箇所で0.1が誤差を発生させているからです。誤差が出ているものをいくら10進演算したところで結局、誤差は発生してしまいます。

以上の結果からも分かるとおり、コンストラクタやメソッドに渡す場合は文字列型を利用しましょう。

除算以外の丸め方式

除算以外にも丸め方式を指定する場合があります。例えば、小数点を含む数字の四則演算をするけど、最終的には四捨五入した値を利用する場合などです。

ケースバイケースですが途中の計算結果は丸めを気にせず計算し、最終的な結果に対して丸め処理を行うほうが精度の高い結果を求めることができます。


import java.math.BigDecimal;

public class BigDecimalSample {

  public static void main(String[] args) {
    BigDecimal ans = new BigDecimal(0);

    ans = ans.add(new BigDecimal("0.1"));
    ans = ans.add(new BigDecimal("0.11"));
    ans = ans.add(new BigDecimal("0.222"));
    ans = ans.add(new BigDecimal("0.3333"));
    ans = ans.add(new BigDecimal("0.44444"));
    ans = ans.add(new BigDecimal("0.555555"));

    //ここで有効桁数と丸め方式を指定
    ans = ans.setScale(2, BigDecimal.ROUND_HALF_UP);
    System.out.println(ans.toString());
  }

}

有効桁数、丸め方式を指定しない場合は「1.765295」となりますが、上記のサンプルでは「1.77」となります。

金額のフォーマット

金額計算をしたときに3桁カンマで表示したり、マイナス符号を後ろにつけたりするようなフォーマット処理をするケースがあります。Javaには便利なフォーマット機能が用意されているので紹介します。


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

public class BigDecimalSample {

  public static void main(String[] args) {
    BigDecimal plus = new BigDecimal("123456.12");
    BigDecimal minus = new BigDecimal("-123456.12");

    //3桁カンマ
    DecimalFormat df1 = new DecimalFormat("#,###.##");

    //後ろゼロ
    DecimalFormat df2 = new DecimalFormat("#,###.000");

    //前マイナス
    DecimalFormat df3 = new DecimalFormat("#,###.00;-#,###.00");

    //後マイナス
    DecimalFormat df4 = new DecimalFormat("#,###.00;#,###.00-");

    System.out.println(df1.format(plus));
    System.out.println(df2.format(plus));

    System.out.println(df3.format(plus));
    System.out.println(df3.format(minus));

    System.out.println(df4.format(plus));
    System.out.println(df4.format(minus));
  }

}

実行結果

123,456.12
123,456.120
123,456.12
-123,456.12
123,456.12
123,456.12-

フォーマット記号の意味

文字 メモ
0 数値1桁を表しその桁に数値が無い場合は”0″を表示
# 数値1桁を表しその桁に数値が無い場合はブランクを表示
. 小数点を表す
, カンマ区切りを表す
マイナスを表す
; 正と負の値を区切る
% 数値を100倍してパーセント表示

スポンサーリンク
スポンサードリンク
スポンサードリンク

シェアする

  • このエントリーをはてなブックマークに追加

フォローする