Javaでのプログラミング、特に数値を扱う際に「計算結果が微妙にずれる…」と悩んだ経験はありませんか。実は私も過去に、決済システムの開発でdouble型を使い、金額の計算で誤差が発生して冷や汗をかいたことがあります。
1円でもずれれば大問題になる金融の世界で、この失敗から数値計算の正確性がいかに重要かを学びました。
この記事は、そんな過去の私と同じように、Javaでの正確な数値計算に課題を感じている方に向けて書いています。
この記事を最後まで読めば、あなたは以下のことができるようになります。
BigDecimalの基本的な役割と必要性を理解できるdouble型との違いを明確に説明できるようになるBigDecimalの正しい生成方法、演算、比較方法を習得できる- 初心者が陥りがちな3つの落とし穴とその対策を学べる
- 通貨計算などの実践的なテクニックを身につけられる
Javaで数値を正確に扱いたいすべての方へ、BigDecimalを使いこなすための知識を、私の経験を交えながら分かりやすく解説していきます。
BigDecimalとは?今さら聞けない基本と魅力

まずはBigDecimalがどのようなもので、なぜ必要なのかを見ていきましょう。特にdouble型との比較を通して、その存在価値を明らかにします。
BigDecimalとは何か?概要とできること
BigDecimalは、Javaで非常に正確な10進数の計算を行うために用意されたクラスです。java.mathパッケージに属しており、小数点以下の数値を誤差なく表現できます。
私たちが普段使う数字は0から9の10個の数字を組み合わせる10進数です。一方、コンピュータの内部では0と1の2つの数字で表現する2進数が使われています。
doubleやfloatといった型は、この2進数で小数を扱おうとするため、0.1のような単純な小数でさえも、内部的には無限に続く循環小数になってしまい、誤差が生まれてしまいます。
BigDecimalは、この問題を解決するために作られました。内部的に数値を10進数のまま保持するため、私たちが期待する通りの正確な計算が可能です。特に、お金の計算や精密な科学技術計算など、わずかな誤差も許されない場面でその真価を発揮します。
なぜBigDecimalが必要なのか? doubleとの比較
BigDecimalの必要性を理解するために、double型で計算した場合の問題点を見てみましょう。例えば、0.1 + 0.2という簡単な足し算を考えます。答えはもちろん0.3になるはずです。
しかし、この計算をdouble型で行うと、驚くべき結果が返ってきます。
System.out.println(0.1 + 0.2)実行結果:
0.30000000000000004このように、ごくわずかながら誤差が生じてしまいます。これは、doubleが2進数で小数を近似的に表現していることに起因します。一度の計算では小さな誤差でも、計算を繰り返すうちに無視できない大きなずれになる可能性があります。
一方で、同じ計算をBigDecimalで行うとどうなるでしょうか。
import java.math.BigDecimal;
public class Main {
public static void main(String[] args) {
BigDecimal a = new BigDecimal("0.1");
BigDecimal b = new BigDecimal("0.2");
System.out.println(a.add(b));
}
}実行結果:
0.3期待通り、正確な0.3という結果が得られました。このように、計算の正確性を担保したい場合にBigDecimalは不可欠なのです。
BigDecimalを使うメリット・デメリット
BigDecimalの利用には、メリットとデメリットの両方があります。
メリット:
- 高い精度: 小数計算で誤差が発生しません。金融システムなど、正確性が最優先される場面で必須です。
- 柔軟な丸め処理: 四捨五入、切り捨て、切り上げなど、8種類の丸めモードを細かく指定できます。消費税の計算などで非常に便利です。
デメリット:
- パフォーマンス:
doubleやlongなどのプリミティブ型と比べて、オブジェクトを生成するため処理速度が遅く、メモリも多く消費します。 - 記述の煩雑さ: 四則演算が
+や-のような演算子ではなく、add()やsubtract()といったメソッド呼び出しになるため、コードが長くなりがちです。
すべての数値計算をBigDecimalで行う必要はありません。パフォーマンスが重視され、多少の誤差が許容されるグラフィック処理などではdoubleが適しています。用途に応じて適切に使い分けることが重要です。
BigDecimalの基礎:生成、演算、比較

ここでは、BigDecimalを実際に使うための基本的な操作方法を解説します。オブジェクトの作り方から、四則演算、そして値の比べ方まで、サンプルコードを交えて見ていきましょう。
BigDecimalオブジェクトの生成方法
BigDecimalのオブジェクトを生成するには、いくつかの方法がありますが、文字列を引数に取るコンストラクタを使うのが最も安全で確実です。
// 推奨される方法: 文字列から生成
BigDecimal bd1 = new BigDecimal("10.5");
// doubleから生成(非推奨)
BigDecimal bd2 = new BigDecimal(0.1);
System.out.println("文字列から: " + bd1);
System.out.println("doubleから: " + bd2);実行結果:
文字列から: 10.5
doubleから: 0.1000000000000000055511151231257827021181583404541015625doubleを引数にすると、doubleが元々持っている誤差までBigDecimalオブジェクトに変換されてしまいます。これではBigDecimalを使う意味がありません。そのため、必ず文字列コンストラクタを使いましょう。
また、よく使う0や1、10については、定数が用意されています。
BigDecimal zero = BigDecimal.ZERO; // 0
BigDecimal one = BigDecimal.ONE; // 1
BigDecimal ten = BigDecimal.TEN; // 10valueOf()メソッドを使う方法もあります。valueOf(double val)は内部でDouble.toString(val)を呼び出すため、文字列コンストラクタと同じ結果になり安全です。
BigDecimal bd3 = BigDecimal.valueOf(10.5); // こちらも安全
System.out.println(bd3); // 10.5BigDecimalの四則演算:add, subtract, multiply, divide
BigDecimalの四則演算は、それぞれ専用のメソッドで行います。
- 加算:
add() - 減算:
subtract() - 乗算:
multiply() - 除算:
divide()
これらのメソッドは、計算結果を新しいBigDecimalオブジェクトとして返します(詳しくは後述の「不変性」で解説します)。
BigDecimal a = new BigDecimal("10");
BigDecimal b = new BigDecimal("3");
// 加算: 10 + 3
BigDecimal resultAdd = a.add(b);
System.out.println("加算: " + resultAdd); // 13
// 減算: 10 - 3
BigDecimal resultSubtract = a.subtract(b);
System.out.println("減算: " + resultSubtract); // 7
// 乗算: 10 * 3
BigDecimal resultMultiply = a.multiply(b);
System.out.println("乗算: " + resultMultiply); // 30
// 除算: 10 / 3
// このままだと割り切れないため例外が発生する
// BigDecimal resultDivide = a.divide(b);
// 正しくは、桁数と丸めモードを指定する
BigDecimal resultDivide = a.divide(b, 5, RoundingMode.HALF_UP);
System.out.println("除算: " + resultDivide); // 3.33333除算については、割り切れない場合にArithmeticExceptionという例外が発生する可能性があるため注意が必要です。この点も後ほど詳しく解説します。
BigDecimalの比較:equalsとcompareToの違い
BigDecimalの値を比較するにはequals()とcompareTo()の2つのメソッドがありますが、この2つは役割が異なります。
equals(): 数値とスケール(小数点以下の桁数)の両方が完全に一致する場合にtrueを返します。compareTo(): 数値の大小を比較します。スケールは無視されます。a.compareTo(b)の結果-1:aがbより小さい0:aとbが等しい1:aがbより大きい
具体的なコードで違いを見てみましょう。
BigDecimal bd1 = new BigDecimal("1.0");
BigDecimal bd2 = new BigDecimal("1.00");
// equals()での比較
System.out.println("equals: " + bd1.equals(bd2)); // false
// compareTo()での比較
System.out.println("compareTo: " + (bd1.compareTo(bd2) == 0)); // true1.0と1.00は、数値的には同じですが、スケール(小数点以下の桁数)がそれぞれ1と2で異なります。そのため、equals()はfalseを返します。
金額の比較など、純粋に数値として等しいかを確認したい場合はcompareTo()を使いましょう。 equals()は、スケールまで厳密に一致しているかを確認したい特殊なケースで利用します。
BigDecimalでハマりやすい落とし穴と対策

BigDecimalは非常に便利なクラスですが、使い方を誤ると予期せぬエラーやバグの原因になります。ここでは、初心者が特に陥りやすい3つのポイントとその対策を解説します。
スケールと精度:丸め処理の重要性
BigDecimalでは、スケール(scale)、つまり小数点以下の桁数を意識することが非常に重要です。特に、計算結果の桁数を調整するsetScale()メソッドを使う際には注意が必要です。
例えば、1.235という数値を小数点以下2桁に丸めたいとします。
import java.math.BigDecimal;
import java.math.RoundingMode;
public class Main {
public static void main(String[] args) {
BigDecimal num = new BigDecimal("1.235");
// 小数点以下2桁に、四捨五入で丸める
BigDecimal rounded = num.setScale(2, RoundingMode.HALF_UP);
System.out.println(rounded); // 1.24
// 丸めモードを指定しないと例外が発生する可能性がある
try {
BigDecimal error = new BigDecimal("1.235").setScale(2);
} catch (ArithmeticException e) {
System.err.println("エラー: " + e.getMessage());
}
}
}実行結果:
1.24
エラー: Rounding necessarysetScale()で桁数を減らす場合、1.235を1.23にするには端数の5をどう処理するか決めなければなりません。この「どう処理するか」を指定するのが丸めモード(RoundingMode)です。丸めモードを指定しないと、「丸めが必要なのにモードが指定されていない」という理由でArithmeticExceptionが発生します。
計算の最終結果を特定の桁数に揃える場合は、必ずsetScale()とRoundingModeをセットで使いましょう。
除算時のArithmeticException:例外処理
BigDecimalの落とし穴として最も有名なのが、除算時のArithmeticExceptionです。これは、割り算の結果が無限小数になる(割り切れない)場合に発生します。
例えば、10 ÷ 3は3.333...となり、割り切れません。
BigDecimal a = new BigDecimal("10");
BigDecimal b = new BigDecimal("3");
// 割り切れないため ArithmeticException が発生する
// a.divide(b); この例外を回避するには、divide()メソッドのオーバーロードを使い、スケール(小数点以下の桁数)と丸めモードを明示的に指定します。
import java.math.BigDecimal;
import java.math.RoundingMode;
public class Main {
public static void main(String[] args) {
BigDecimal a = new BigDecimal("10");
BigDecimal b = new BigDecimal("3");
// 小数点以下5桁まで求め、それ以降は四捨五入する
BigDecimal result = a.divide(b, 5, RoundingMode.HALF_UP);
System.out.println(result); // 3.33333
}
}これにより、指定した桁数で計算が打ち切られ、例外の発生を防げます。特にユーザーの入力値など、どのような数値が来るか分からない計算では、必ず桁数と丸めモードを指定する習慣をつけましょう。
BigDecimalの不変性:注意点とコピー
BigDecimalオブジェクトは不変(Immutable)です。これは、一度作成したオブジェクトの内部状態(値)が決して変わらないという性質を意味します。
add()やsetScale()などのメソッドを呼び出しても、元のオブジェクトの値は変化しません。これらのメソッドは、計算結果を持つ新しいBigDecimalオブジェクトを生成して返すのです。
初心者がやりがちな間違いが、この戻り値を受け取らないことです。
// 間違った例
BigDecimal price = new BigDecimal("1000");
BigDecimal taxRate = new BigDecimal("0.1");
// price.add(price.multiply(taxRate)); // 計算結果を捨ててしまっている
// System.out.println(price); // 1000 のまま
// 正しい例
BigDecimal tax = price.multiply(taxRate);
BigDecimal totalPrice = price.add(tax); // 戻り値を受け取る
System.out.println(totalPrice); // 1100.0price.add(tax)を実行しても、price自体の値は1000のままです。計算結果である1100は、新しく作られたBigDecimalオブジェクトとして返されるので、それをtotalPriceという別の変数で受け取る必要があります。
この不変性という性質は、複数の場所から同じオブジェクトを参照しても値が書き換わる心配がないため、安全なプログラミングにつながります。BigDecimalの演算を行う際は、必ず戻り値を変数に代入することを忘れないでください。
BigDecimalの実践:応用テクニック

基本を押さえたところで、次はより実践的なBigDecimalの使い方を見ていきましょう。丸めモードの使い分けや、通貨・パーセントの表示形式、コレクションとの連携など、実務で役立つテクニックを紹介します。
RoundingMode:様々な丸めモードを使いこなす
BigDecimalの強力な機能の一つが、豊富な丸めモードです。java.math.RoundingModeというenumで定義されており、全部で8種類あります。ここでは、特によく使われるものを紹介します。
| RoundingMode | 説明 | 例 (5.5) | 例 (2.5) | 例 (-2.5) |
HALF_UP | いわゆる四捨五入。端数が0.5以上なら切り上げる。 | 6 | 3 | -3 |
DOWN | 切り捨て。0から遠ざからないように丸める。 | 5 | 2 | -2 |
UP | 切り上げ。0から遠ざかるように丸める。 | 6 | 3 | -3 |
CEILING | 正の無限大方向に丸める(天井)。 | 6 | 3 | -2 |
FLOOR | 負の無限大方向に丸める(床)。 | 5 | 2 | -3 |
例えば、日本の消費税計算では、端数は「切り捨て」が一般的です。このような場合にRoundingMode.DOWNが役立ちます。
import java.math.BigDecimal;
import java.math.RoundingMode;
public class Main {
public static void main(String[] args) {
BigDecimal price = new BigDecimal("198");
BigDecimal taxRate = new BigDecimal("0.1");
// 消費税を計算し、小数点以下を切り捨てる
BigDecimal tax = price.multiply(taxRate).setScale(0, RoundingMode.DOWN);
System.out.println("消費税: " + tax); // 19
BigDecimal totalPrice = price.add(tax);
System.out.println("税込価格: " + totalPrice); // 217
}
}業務要件に応じて適切な丸めモードを選択することが、正確なシステムを構築する上で非常に重要です。
NumberFormat:通貨やパーセント表示
BigDecimalで計算した結果を、ユーザーに分かりやすく表示する際にはNumberFormatクラスが便利です。例えば、数値を「¥1,234」のような通貨形式や、「50%」のようなパーセント形式に整形できます。
import java.math.BigDecimal;
import java.text.NumberFormat;
import java.util.Locale;
public class Main {
public static void main(String[] args) {
BigDecimal amount = new BigDecimal("12345.67");
// 日本の通貨形式にフォーマット
NumberFormat currencyFormatter = NumberFormat.getCurrencyInstance(Locale.JAPAN);
System.out.println(currencyFormatter.format(amount)); // ¥12,346
BigDecimal ratio = new BigDecimal("0.55");
// パーセント形式にフォーマット
NumberFormat percentFormatter = NumberFormat.getPercentInstance();
System.out.println(percentFormatter.format(ratio)); // 55%
}
}NumberFormatは内部で自動的に四捨五入などの丸め処理を行います。getCurrencyInstanceにLocaleを指定することで、様々な国の通貨形式に対応できます。計算はBigDecimalで正確に行い、表示の整形はNumberFormatに任せる、という役割分担が理想的です。
BigDecimalとコレクション:合計値の計算
Listなどのコレクションに格納された複数のBigDecimalオブジェクトの合計値を計算する場面もよくあります。これは、ループ処理やJava 8で導入されたStream APIを使うことで簡単に実現できます。
for-eachループを使った方法:
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<BigDecimal> prices = Arrays.asList(
new BigDecimal("198.5"),
new BigDecimal("2050"),
new BigDecimal("378.99")
);
BigDecimal sum = BigDecimal.ZERO;
for (BigDecimal price : prices) {
sum = sum.add(price);
}
System.out.println("合計金額 (for-each): " + sum); // 2627.49
}
}Stream APIを使った方法:
Stream APIのreduceメソッドを使うと、より簡潔に記述できます。
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<BigDecimal> prices = Arrays.asList(
new BigDecimal("198.5"),
new BigDecimal("2050"),
new BigDecimal("378.99")
);
BigDecimal streamSum = prices.stream()
.reduce(BigDecimal.ZERO, BigDecimal::add);
System.out.println("合計金額 (Stream): " + streamSum); // 2627.49
}
}どちらの方法でも正しい結果が得られますが、Stream APIを使うとコードがすっきりとします。プロジェクトのコーディング規約やJavaのバージョンに合わせて使い分けると良いでしょう。
BigDecimalマスターへの道:まとめと学習のヒント
最後に、これまでの内容をまとめ、BigDecimalをさらに深く理解するためのヒントを提供します。
BigDecimalを使うべきケース、使わない方が良いケース
BigDecimalは万能ではありません。特性を理解し、適切な場面で使うことが重要です。
使うべきケース:
- 金融・会計システム: お金の計算では1円の誤差も許されません。必須です。
- ECサイトの料金計算: 税金や送料、割引など、正確な金額の算出が求められます。
- 精密な科学技術計算: 誤差が結果に大きく影響する物理シミュレーションなどで利用します。
- 法制度に関わる計算: 法律で計算方法や端数処理が厳密に定められている場合。
使わない方が良いケース(doubleなどを検討するケース):
- パフォーマンスが最優先される計算: 3Dグラフィックスの座標計算や大量の統計データ処理など、速度が重要で、わずかな誤差は許容できる場合。
- 単純なカウンターやループ変数: 整数で事足りる場面で
BigDecimalを使う必要はありません。intやlongを使いましょう。
「正確性が何よりも重要か?」を一つの判断基準にすると、使い分けがしやすくなります。
BigDecimalに関する参考文献・リンク集
さらに学習を深めたい方向けに、公式のドキュメントを紹介します。一次情報にあたることで、より正確で深い知識を得られます。
- Java SE Platform ドキュメント (BigDecimal):https://docs.oracle.com/javase/jp/8/docs/api/java/math/BigDecimal.html
メソッドの詳細な仕様や、すべてのRoundingModeの説明などが記載されています。困ったときには、まず公式ドキュメントを参照する習慣をつけることをお勧めします。
BigDecimalのパフォーマンスチューニング
BigDecimalはdoubleに比べて低速ですが、使い方を工夫することでパフォーマンスへの影響を最小限に抑えられます。
valueOf()を積極的に使う:BigDecimal.valueOf(10)のように、long型の引数を取るvalueOfメソッドは、0から10までのよく使われる値をキャッシュしています。new BigDecimal("10")よりも高速になる場合があります。- ループ内でのオブジェクト生成を避ける: ループの中で何度も同じ値の
BigDecimalをnewしている場合、ループの外で一度だけ生成して使い回すことで、不要なオブジェクト生成を減らせます。 - 不要な精度で計算しない: 計算途中のスケールが不必要に大きくなると、パフォーマンスが低下します。中間結果に対しても適切に
setScale()を適用し、桁数を管理することが有効です。
BigDecimalは、Javaで正確な数値を扱うための強力な武器です。doubleとの違いを正しく理解し、生成・演算・比較の基本をマスターすれば、もう数値計算の誤差に悩まされることはありません。特に、new BigDecimal(double)を使わない、除算では丸めモードを指定する、不変性を理解するという3つのポイントは、バグを未然に防ぐために必ず押さえておきましょう。
この記事が、あなたのJavaプログラミングの一助となれば幸いです。