Java | Java 標準ライブラリ:BigDecimal の必要性

Java Java
スポンサーリンク

なぜ BigDecimal が「わざわざ」必要になるのか(全体像)

BigDecimal は、ざっくり言うと

お金や桁数が重要な数値を、ちゃんと正確に扱うためのクラス

です。

初心者が最初にハマるポイントはここです。

double でいいじゃん。double って小数も扱えるんでしょ?」

たしかに double は小数を扱えます。でも、
double“だいたいの値” を高速に扱うための型であって、
“きっちりした値” を扱うには向いていません。

お金の計算、税計算、集計処理、金融・会計系などでは、
「0.1 の誤差」すらバグになります。
この「誤差問題」を避けるために登場するのが BigDecimal です。


double(浮動小数点)の「誤差問題」を体で感じる

一見ちゃんと計算できているように見えるが…

とりあえず、次のコードを見てください。

public class DoubleSample {
    public static void main(String[] args) {
        double a = 0.1;
        double b = 0.2;
        double c = a + b;

        System.out.println("0.1 + 0.2 = " + c);
    }
}
Java

実行すると、環境によりますが多くの場合こうなります。

0.1 + 0.2 = 0.30000000000000004

「0.3」じゃない。
「0.30000000000000004」という、微妙にズレた値が出ます。

なぜこんなことが起きるのか(直感的な説明)

double は「2進数ベース」の仕組みで小数を表します。
ところが「0.1」「0.2」のような 10 進数の小数は、
2 進数ではきれいに表現できないものがたくさんあります。

たとえば、10 進数で

1/3 = 0.3333…

と「無限に続く小数」になってしまうのと似ています。
2 進数の世界では、0.1 や 0.2 が「無限に続く」感じになってしまうので、
コンピュータ内部では「近いところで丸めた近似値」を使います。

その結果、

0.1 も 0.2 も「本当の値」ではなく「近似値」
近似値同士を足すから、結果も「ちょっとずれた値」になる

という現象が起こります。

double は、「だいたい合っていればいい」
グラフ描画、物理シミュレーション、統計などには便利ですが、
「1 円でも狂ったらダメ」な世界には向きません。


お金の計算に double を使うと何がマズいか

税計算・合計でズレていく

たとえば、1 個 0.1 円の商品を 10 個買ったとします(極端な例ですが)。

double price = 0.1;
double total = 0.0;
for (int i = 0; i < 10; i++) {
    total += price;
}
System.out.println("合計 = " + total);
Java

理屈では 1.0 になるはずですが、実際には

合計 = 0.9999999999999999

のような結果になることがあります。

これをそのまま「金額」として表示したり、
さらに税計算・割引計算・合計繰り返し…とやっていくと、
本来あるはずの 1 円がどこかで行方不明になったり、
逆に 1 円多くなったりします。

現場でこれをやると、「会計システムとしてアウト」です。


BigDecimal の役割:10 進数を「桁どおり」に正確に扱う

10 進数として「そのままの桁」で持つ

BigDecimal は、「10 進数としての桁」をそのまま持ちます。

  • double → 2 進数の近似表現(中身はだいたい)
  • BigDecimal → 10 進数の正確な表現(中身はきっちり)

という違いです。

たとえば、さっきの 0.1 + 0.2 を BigDecimal でやってみましょう。

import java.math.BigDecimal;

public class BigDecimalSample {
    public static void main(String[] args) {
        BigDecimal a = new BigDecimal("0.1");
        BigDecimal b = new BigDecimal("0.2");
        BigDecimal c = a.add(b);

        System.out.println("0.1 + 0.2 = " + c.toString());
    }
}
Java

結果はこうなります。

0.1 + 0.2 = 0.3

ぴったり 0.3。
10 進数の桁をそのまま扱っているので、「0.1」も「0.2」も誤差なしに表現できます。

文字列コンストラクタを使うのが重要(ここはかなり大事)

BigDecimal を作るときに、うっかりこう書くと危険です。

BigDecimal a = new BigDecimal(0.1);  // これが実は良くない
Java

0.1 の時点で、もう double として誤差を含んだ値になってしまっているので、
その誤差付きの値を元に BigDecimal が作られてしまいます。

正解は「文字列で渡す」です。

BigDecimal a = new BigDecimal("0.1");  // これなら 0.1 を 10 進数として正確に扱える
Java

実務でお金を扱うときは、ここはほぼ「必須ルール」と言っていいです。


BigDecimal でお金計算をするとどう変わるか

さっきの 0.1 × 10 の例を BigDecimal で

先ほどの「0.1 円 × 10 個」を BigDecimal でやってみます。

BigDecimal price = new BigDecimal("0.1");
BigDecimal total = BigDecimal.ZERO;

for (int i = 0; i < 10; i++) {
    total = total.add(price);
}

System.out.println("合計 = " + total.toString());
Java

結果はきちんと

合計 = 1.0

になります。

「足していったら 0.9999… になった」みたいなことは起きません。
お金の世界で必要なのはまさにこういう「一切の誤差がない計算」です。

小数桁数をしっかり管理できる(scale の概念)

BigDecimal は、「小数点以下が何桁か(scale)」を意識して扱えます。
お金なら「小数第2位(0.01 円単位)まで」と決めることが多いです。

例えば、税込価格の計算。

BigDecimal price = new BigDecimal("100");       // 100 円
BigDecimal taxRate = new BigDecimal("0.1");     // 10%

BigDecimal tax = price.multiply(taxRate);       // 10.0
BigDecimal total = price.add(tax);              // 110.0
System.out.println(total);                      // 110.0
Java

さらに、四捨五入や切り捨てをしっかり制御したい場合には、
setScale(桁数, 丸め方) で「何桁でどう丸めるか」を指定できます。


「じゃあ全部 BigDecimal にすればいいの?」に対する答え

何でもかんでも BigDecimal は逆にしんどい

BigDecimal はとても正確ですが、その代わりに

  • 計算が遅い(内部で複雑な処理をしている)
  • 記法が長い(+ 演算子が使えず、add, subtract, multiply, divide を呼ぶ必要がある)

というデメリットもあります。

例えば、

BigDecimal a = new BigDecimal("1.23");
BigDecimal b = new BigDecimal("4.56");
BigDecimal c = a.add(b).multiply(new BigDecimal("2"));
Java

のように、全部メソッド呼びになります。
double でやるよりはるかにコードが重くなるし、読みづらくもなります。

使い分けの大雑把な基準

初心者向けにざっくり線を引くと、

お金・料金・税額など、「1円も狂ってはいけない」計算
BigDecimal を積極的に使う

グラフ、統計、ゲーム内の座標、センサー値など、「多少の誤差はどうでもいい」計算
double で十分

というイメージで OK です。

金融・会計系、請求書、売上集計のようなコードに double が出てきたら、
「本当にこれでいいの?」と一度立ち止まるクセをつけておくと安全です。


まとめ:BigDecimal の「必要性」をあなたの中でどう位置づけるか

ここまでを、初心者向けに一言でまとめるとこうです。

double は「速くて便利だけど、誤差を含む小数」
BigDecimal は「遅くて面倒だけど、誤差なしで扱える小数」

だから、

誤差が許されない(特にお金)ところでは BigDecimal が「必要」になる
誤差があってもいいところでまで BigDecimal を使うと、コードもパフォーマンスも重くなる

特に重要なのは、

  • 0.1 + 0.2 のような計算が double では正確でないことを、自分の目で一度確認しておく
  • BigDecimal を使うなら、new BigDecimal("0.1") のように文字列で初期化するクセをつける

この2点です。

タイトルとURLをコピーしました