BigDecimalでの小数
javaで小数計算する場合はBigDecimalがデファクトスタンダードです。
double型やfloat型で計算しようものならあっと言う間に誤差が出てひどいことになります。
しかも、このBigDecimalは性質の悪いことに新人が一番使ってしまいそうな
以下のようなコードを書くと結局誤差が出てしまいます。
double型で宣言した時点で誤差が出ているので、
それを基にBigDecimalを作っても誤差がそのまま保持されてしまうからです。
新人の頃、先輩から下のように書けと散々叩き込まれたものです。
BigDecimal bad = new BigDecimal(1.3);
//内部的に1.3000000000000000444089209850062616169452667236328125
BigDecimal good = new BigDecimal("1.3");
BigDecimal.valueOf(double)
というわけで、BigDecimalで小数を扱う時は引数は文字列だと完全に体に染み込んでいるのですが、
最近ちょっと検索してみると、BigDecimal.valueOf(double)でdouble型は問題なく扱えるという風に書いてある解説ページが結構ありました。
しかし、new BigDecimal(double)
がdouble型の時点で正確な10進数の小数を保持できないために誤差が出てしまうということを考えると、
BigDecimal.valueOf(double)
も誤差が出てしまうはずです。
しかし、意外なことに確かに大抵のケースでBigDecimal.valueOf(double)
は正しく動きます。
BigDecimal fromValueOf = BigDecimal.valueOf(1.3);
//これはnew BigDecimal("1.3") と同じ値になる
これはどういうことでしょうか。
BigDecimal.valueOfの実装
BigDecimal.valueOf(double)
の実装を見てみると、以下の通りでした。
(OpenJdk より引用)
public static BigDecimal valueOf(double val) {
//コメント部分は省略
return new BigDecimal(Double.toString(val));
}
意外と実装はシンプルで、Double.toString(double)
でdouble型を文字列化した値を使って文字列からBigDecimalを生成しています。
よって、Double.toString(double)がそのdouble値の元となったリテラルの値(またはそれに対応する数値表現)をきちんと返してくれる限り、意図した通りのBigDecimalを返してくれそうです。
Double.toString()はきちんと値を復元してくれるのか?
ほとんどの場合大丈夫ですが、非常に小数点以下の桁数の多い数の場合完璧には戻らないようです。
BigDecimal.valueOf(1.0000000003000003)
//1.0000000003000002
とはいっても、狙って出さないと基本的にずれませんし、
これだけ細かい桁数を使うことはほぼないと思われます。
まとめ
- BigDecimal.valueOf(double)は一度文字列変換しているので信頼性が高い
- 滅多にない誤差まで気になるのなら文字列で宣言できるところは文字列で宣言を
余談
ちなみに、これはリテラルで宣言したdoubleをそのまま使った場合の話です。
double型で演算した後の値を使うとうまく行きません。
BigDecimal.valueOf(1.3 + 1.5 + 1.4)
///4.199999999999999
演算はBigDecimalにしてからですね。