2026年1月18日日曜日

【Javaの罠】doubleで計算してはいけない?実体験から学んだ「BigDecimal」の必要性

1. 導入:計算が合わない……?

プログラミングの世界では、当たり前だと思っていた算数が通用しないことがあります。 私が最近遭遇したのが、浮動小数点(double型)を使った計算です。「0.1を3回足せば0.3になる」という当然の期待が、Javaのコード上では裏切られる……。今回はその謎と、確実な計算を行うための解決策を探求します。


2. 遭遇した現象:doubleでは「0.3」にならない

まずは、実際に私が動かしてみて「えっ?」となったコードを見てください。

【検証コード:FloatingPointTest.java】

Java
public class FloatingPointTest {
    public static void main(String[] args) {
        double val = 0.0;
        for (int i = 0; i < 3; i++) {
            val += 0.1;
        }
        System.out.println("0.1を3回足した結果: " + val);
        
        // 期待は 0.3 だが、実際は...
        if (val == 0.3) {
            System.out.println("結果は 0.3 です");
        } else {
            System.out.println("結果は 0.3 ではありません!");
        }
    }
}

【実行結果】

Plaintext
0.1を3回足した結果: 0.30000000000000004
結果は 0.3 ではありません!

なんと、末尾に「4」という余計な数字が現れました。これが、コンピュータが内部で数値を「2進数」で扱っているために発生する**誤差(丸め誤差)**の正体です。人間には簡単な「0.1」も、コンピュータにとっては無限小数になってしまい、どこかで切り捨てが発生しているのです。


3. 解決策:BigDecimalによる厳密な演算

お金の計算など、1円のズレも許されないシステムでは、floatやdoubleを使ってはいけません。そこで登場するのが java.math.BigDecimal クラスです。

【修正コード:BigDecimalTest.java】

Java
import java.math.BigDecimal;

public class BigDecimalTest {
    public static void main(String[] args) {
        // 【重要】文字列として数値を渡すのがプロの鉄則
        BigDecimal val = new BigDecimal("0.0");
        BigDecimal addVal = new BigDecimal("0.1");

        for (int i = 0; i < 3; i++) {
            // BigDecimalは不変オブジェクトなので、結果を再代入する必要があります
            val = val.add(addVal);
        }

        System.out.println("BigDecimalでの計算結果: " + val);

        // 値の比較は compareTo を使用
        if (val.compareTo(new BigDecimal("0.3")) == 0) {
            System.out.println("結果は正確に 0.3 です");
        }
    }
}


4. 納得ポイント:実際に触ってわかった「プロのTips」

自分で実装してみて「なるほど、これはハマるな」と思った注意点が2つあります。

  • コンストラクタには必ず「文字列」を渡すこと new BigDecimal(0.1) と数値を直接渡すと、その引数の時点でdoubleの誤差が含まれてしまいます。new BigDecimal("0.1") と文字列で渡すことで、初めて厳密な数値として扱われます。

  • 計算結果は必ず「戻り値」を受け取ること val.add(addVal); と書くだけでは val の中身は変わりません。BigDecimalは書き換えができない(イミュータブルな)設計なので、必ず val = val.add(...) のように結果を受け取る必要があります。


5. まとめ:適材適所の道具選び

今回の探求を通じて、計算一つとっても「なぜ誤差が出るのか」を知り、適切な道具(クラス)を選ぶことの重要性を学びました。

「とりあえず動く」で済ませず、データの性質を理解して、それに適した型を選ぶ。この小さな納得の積み重ねが、バグのない堅牢なシステム作りには不可欠なのだと実感しています。




0 件のコメント:

コメントを投稿

【Java】「あれ、時間が違う?」の謎を解く。OSとJavaが時刻を判断する仕組みとモダンな修正術

1. 導入:9時間のズレが教えてくれたこと ローカルのPCでは正しく動いていたのに、サーバー(Linux)に持っていった途端、時刻が9時間ズレて表示される……。 そんな経験はありませんか?私はあります。この「9時間」という数字は、日本標準時(JST)と協定世界時(UTC)の差。 ...