floatをもとにBigDecimalオブジェクトを作成する。

doubleからBigDecimalオブジェクトを作成する場合、

  BigDecimal bd = new BigDecimal(doubleVal);

としてしまうと、浮動小数点で正確に表せない数値の場合、期待結果とズレる場合があります。
JavadocBigDecimal(double) コンストラクタから引用↓

Oracle Technology Network for Java Developers | Oracle Technology Network | Oracle
# 一方、String コンストラクタは完全に予測可能である。new BigDecimal("0.1") と記述すると、「正確に」 0.1 と等しい BigDecimal が作成される。そのため、通常は、これの代わりに String コンストラクタを使用することが推奨されている
# BigDecimal のソースとして double を使用する必要がある場合、このコンストラクタは正確な変換を行うことに注意する必要がある。このコンストラクタでは、Double.toString(double) メソッドと BigDecimal(String) コンストラクタを使用して double を String に変換したときと同じ結果にはならない。同じ結果を得るには、static valueOf(double) メソッドを使用する

こんなかんじですね↓

  BigDecimal bd = BigDecimal.valueOf(doubleVal);
  // または
  BigDecimal bd = new BigDecimal(Double.toString(doubleVal));


さて、floatの場合はどうでしょう。doubleと同じように、BigDecimal#valueOf() を使ってみます。

  BigDecimal bd = BigDecimal.valueOf(floatVal);

試しに floatValに 0.1f や 2.35f を設定してみてください。おそらくbdは0.1 や 2.35 を表してくれないでしょう。


原因は、そもそもBigDecimal.valueOf(float)というメソッドはないからです。
コンパイルエラーになればいいんですが、floatからdoubleに暗黙キャストされてしまうので、コンパイル可能です。しかしfloatとdoubleでは表現できる精度に差があるため、暗黙キャストの段階で誤差が発生してしまいます。


それを踏まえたうえでfloatからBigDecimalオブジェクトを作成するなら、

  BigDecimal bd = new BigDecimal(Float.toString(floatVal));

として文字列表現から作ればうまくいきます。


今回の例では、「floatを引数とするメソッドがない」「doubleの引数にfloatを渡しても、暗黙キャストされるためコンパイルエラーにならない」という点が問題になりました。
BigDecimalにかぎらず、floatからdoubleへの暗黙キャストには注意する必要がありますね。


まぁこんな対応する以前に「誤差があって欲しくない場所では初めから浮動小数点数を使わない」という根本を直せよ、って話です。




と、浮動小数点数の基礎的な部分で罠にかかった日でした。