BigDecimalをDecimalFormatでフォーマット

久しぶりに浮動小数点等の問題にハマりました。BigDecimalに保持している数値をDecimalFormatでフォーマットして出力するだけなのですが、あんまり莫大な桁数の計算をJavaで考えたことがなくBigDecimalを使用して計算を行えばよいぐらいしか思っていなかったので新たな発見です。
例えば、入力が、"22517998136852477"であるBigDecimalをDecimalFormat("#.##0")でフォーマットすると、"22,517,998,136,852,476"になります。見てのとおり結果が"1"違ってしまいます!
なぜでしょうか・・・。
理由は簡単で、DecimalFormat内で、BigDecimalをフォーマット対象として渡すと、doubleに置き換えてからフォーマットされるからです。NumberFormatには、longまたはdoubleを引数とするformatメソッドが用意されておりそれを呼び出すためです。つまり、BigDecimalをフォーマットする時は、double型の仕様に準拠してしまうのです。double型の仕様と言えば、IEEE754なので、仮数52bitということで"2251799813685247"まで正しくフォーマットされることになります。それ以上は、DecimalFormatのJavaDocに記述されているように、BigDecimal.ROUND_HALF_EVENの丸めが行われます。
さて、J2SE1.4で試していたのですが、J2SE1.5でふと同じことをしてみました。結果が違います。上記数値が丸められないのです。ソースをチェックしてみると、BigDecimalに対応するようなメソッドが追加されてるじゃないですか!いつのまに直してるんだ(#゜Д゜)ゴルァ!!


[J2SE1.4.2_02]<>
public final StringBuffer format(Object number, StringBuffer toAppendTo, FieldPosition pos) {
if (number instanceof Long || (number instanceof BigInteger && ( (BigInteger) number).bitLength() < 64)) {
return format(((Number)number).longValue(), toAppendTo, pos);
} else if (number instanceof Number) {
return format(((Number)number).doubleValue(), toAppendTo, pos);
} else {
throw new IllegalArgumentException("Cannot format given Object as a Number");
}
}

[J2SE1.5.0]<>
public StringBuffer format(Object number, StringBuffer toAppendTo, FieldPosition pos) {
if (number instanceof Long ||
number instanceof Integer ||
number instanceof Short ||
number instanceof Byte ||
(number instanceof BigInteger && ( (BigInteger) number).bitLength() < 64)) {
return format(((Number)number).longValue(), toAppendTo, pos);
} else if (number instanceof Number) {
return format(((Number)number).doubleValue(), toAppendTo, pos);
} else {
throw new IllegalArgumentException("Cannot format given Object as a Number");
}
}<>
public final StringBuffer format(Object number, StringBuffer toAppendTo, FieldPosition pos) {
if (number instanceof Long ||
number instanceof Integer ||
number instanceof Short ||
number instanceof Byte ||
(number instanceof BigInteger && ( (BigInteger) number).bitLength() < 64)) {
return format(((Number)number).longValue(), toAppendTo, pos);
} else if (number instanceof BigDecimal) {
return format((BigDecimal)number, toAppendTo, pos);
} else if (number instanceof BigInteger) {
return format((BigInteger)number, toAppendTo, pos);
} else if (number instanceof Number) {
return format(((Number)number).doubleValue(), toAppendTo, pos);
} else {
throw new IllegalArgumentException("Cannot format given Object as a Number");
}
}
J2SE1.5でどのぐらい変わったかは詳しく検証していないのですが、いつのまにか結果数値が変わるというのはちょっといただけない気がしました。Readmeに載っているのかしら・・・