1. 導入:計算が合わない……?
プログラミングの世界では、当たり前だと思っていた算数が通用しないことがあります。 私が最近遭遇したのが、浮動小数点(double型)を使った計算です。「0.1を3回足せば0.3になる」という当然の期待が、Javaのコード上では裏切られる……。今回はその謎と、確実な計算を行うための解決策を探求します。
2. 遭遇した現象:doubleでは「0.3」にならない
まずは、実際に私が動かしてみて「えっ?」となったコードを見てください。
【検証コード:FloatingPointTest.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 ではありません!");
}
}
}
【実行結果】
0.1を3回足した結果: 0.30000000000000004
結果は 0.3 ではありません!
なんと、末尾に「4」という余計な数字が現れました。これが、コンピュータが内部で数値を「2進数」で扱っているために発生する**誤差(丸め誤差)**の正体です。人間には簡単な「0.1」も、コンピュータにとっては無限小数になってしまい、どこかで切り捨てが発生しているのです。
3. 解決策:BigDecimalによる厳密な演算
お金の計算など、1円のズレも許されないシステムでは、floatやdoubleを使ってはいけません。そこで登場するのが java.math.BigDecimal クラスです。
【修正コード:BigDecimalTest.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 件のコメント:
コメントを投稿