2007-04-25
■Joel on Software から

間違ったコードは間違って見えるようにする - The Joel on Software Translation Project
「コードに語らせる」技術を磨くことが大切だと思わされました。
ハンガリアン記法についても、本来の用途である「アプリケーションハンガリアン」を知らなかったので勉強になりました。システムハンガリアンはいらないと思う人間ですが、アプリケーションハンガリアンは使えそうです。
■floatをもとにBigDecimalオブジェクトを作成する。

doubleからBigDecimalオブジェクトを作成する場合、
BigDecimal bd = new BigDecimal(doubleVal);
としてしまうと、浮動小数点で正確に表せない数値の場合、期待結果とズレる場合があります。
Javadocの BigDecimal(double) コンストラクタから引用↓
BigDecimal (Java 2 Platform SE 5.0)
# 一方、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への暗黙キャストには注意する必要がありますね。
まぁこんな対応する以前に「誤差があって欲しくない場所では初めから浮動小数点数を使わない」という根本を直せよ、って話です。
と、浮動小数点数の基礎的な部分で罠にかかった日でした。
?double/float双方において、そのままのプリミティブ型からBigDecimalを作成すると、精度に問題があるのだから、要点は「小数点のBigDecimalを作成する際は、String型を引数にして作成しましょう」じゃないのかな?
?JavaDocの対応バージョンは1.4?それとも5.0?かなり仕様が変わっているクラスもあるので、その辺は明記しとくべき。一例として、BigDecimalも小数の表現方法が変更されたはず。