BigDecimal
クラスRevised: Sep./01st/2004; Since: May/7th/2002
Java では小数は IEEE 754 規格の浮動小数点数で扱います。この場合、2進数の特徴として、期待していた値が厳密には求まらない場合があります。
例えば、 "0.1" という数値は IEEE 754 の 2進数では次のようにあらわされます:
1001100110011001100110011001100110011001100110011010
10進数の分数で表現すると、次のような無限級数になります:
1/10 = 1/16 + 1/32 + 1/256 + 1/512 + 1/4096 + 1/8192 + ...
つまり、0.1 は浮動小数点数に対応する値が存在せず、厳密に正しい値では表現できないのです。この例のように、途中で計算を打ち切ることで生じる、厳密な値との誤差を打切り誤差と呼びます。一般に、算術計算では切り捨て/四捨五入/切り上げが必要になることが殆どで、そのために生じる誤差を丸め誤差と呼びます。精密な値が必要な場合は、丸め誤差モードを何にするのか、有効数字として何桁必要なのかといったことに自覚的である必要があります。
このような問題を解決するために、小数に 10 を乗じて整数として計算し、結果を 10 で割るとなどのテクニックがよく使われます。
BigDecimal
クラスはそのような問題を解決するために、10 進数値を文字列として扱います。通常の基本データ型の計算に比べてパフォーマンスが落ちるのですが、お金の計算など、厳密に正しい数値が欲しい場合に好んで使われます
java.lang.Object | +--java.lang.Number | +--java.math.BigDecimal
API 仕様書では次のように説明されています:
変更が不可能な、任意精度の符号付き 10 進数です。BigDecimal は、任意精度の「スケールなしの整数値」と、小数点以下の桁数を表す負でない 32 ビット整数の「スケール」で構成されます。BigDecimal で表される数値は (unscaledValue/10scale) です。BigDecimal は、基本算術、スケール操作、比較、ハッシング、および書式変換の演算を提供します。
BigDecimal(BigInteger val) | BigInteger を BigDecimal に変換します。 |
---|---|
BigDecimal(BigInteger unscaledVal, int scale) | BigInteger のスケールなしの値と int スケールを BigDecimal に変換します。 |
BigDecimal(double val) | double を BigDecimal に変換します。 |
BigDecimal(String val) | BigDecmal の String 表現を BigDecimal に変換します。 |
メソッドは数が多いのですべては紹介しません。主として、数値計算用のメソッド、基本データ型への変換のメソッドが用意されています。
修飾子 | 戻り値 | メソッド名 | 概要 | public | BigDecimal | add(BigDecimal val) | この BigDecimal に val を加算 |
---|---|---|---|
public | BigDecimal | multiply(BigDecimal val) | この BigDecimal に val を乗算 |
public | BigDecimal | divide(BigDecimal val, | この BigDecimal に val を除算。round にはこのクラスで定義されているフィールドを指定して、丸めモードを指定する。 |
最初に、基本データ型を使ったアプリケーションを示して、問題を発生させます:
FpcDemo1.java
:
// (cents, 個数) ペア用のレコード型
class Rec {
double cents;
int count;
// 小数は基本データ型で保持
Rec(double cents, int count) {
this.cents = cents;
this.count = count;
}
}
class FpcDemo1 {
// レコードのセット
static Rec values[] = {
new Rec(0.01, 1),
new Rec(0.05, 3),
new Rec(0.10, 7),
new Rec(0.25, 3),
new Rec(0.50, 4)
};
public static void main(String args[]) {
double sum = 0.0;
// Rec 型のフィールド cents*count の加算
for (int i = 0; i < values.length; i++) {
Rec r = values[i];
sum += r.cents * r.count;
}
// 合計を表示
System.out.println("sum = " + sum);
System.out.println("sum - 3.61 = " + (sum - 3.61));
}
}
C:\Java>javac FpcDemo1.java C:\Java>java FpcDemo1 sum = 3.6100000000000003 sum - 3.61 = 4.440892098500626E-16 C:\Java>
簡単な計算であり、期待値としては 3.61 であって欲しいのですが、わずかですが異なっています。 IEEE 756 規格浮動小数点数で表現できる限界で誤差が発生しています。
FpcDemo.java
:
import java.math.BigDecimal; // (cents, 個数) ペア用のレコード型 class Rec { String cents; int count; // 小数は文字列で保持 Rec(String cents, int count) { this.cents = cents; this.count = count; } } class FpcDemo { // レコードのセット static Rec values[] = { new Rec("0.01", 1), // 小数を文字列でセット new Rec("0.05", 3), new Rec("0.10", 7), new Rec("0.25", 3), new Rec("0.50", 4) }; public static void main(String args[]) { // 加算結果も BigDecimal で保持 // 桁数を小数点以下2桁に設定: "0.00" BigDecimal sum = new BigDecimal("0.00"); // BigDecimal を使用して、合計値を計算 for (int i = 0; i < values.length; i++) { Rec r = values[i]; // cents を BigDecimal 型に BigDecimal cents = new BigDecimal(r.cents); // 個数を BigDecimal 型に BigDecimal count = new BigDecimal(r.count); // BigDecimal 型の計算 // sum = sum + cents*count sum = sum.add(cents.multiply(count)); } // 合計値を表示 System.out.println("sum = " + sum); } }
C:\Java>javac FpcDemo.java C:\Java>java FpcDemo sum = 3.61 C:\Java>
期待したとおりの値が出力されたほかに、不要な0が表示されていない事に注目してください。 BigDecimal 型オブジェクト sum
を初期化したときに "0.00"
を指定したので、小数点以下2桁だけが表示されているのです。 "0.000"
で初期化すれば、小数点以下第3位まで表示されるようになりますので試してみると良いでしょう。