ModelにBigDecimalを使用する時は値の範囲に注意

はじめに

 後輩に「モデルの値が化けるのですけど」と突っ込まれ原因を調べたのでメモ。

環境

  • Wicket 1.3.6(1.3.x、1.4.x全般で発生すると思います)

事象

 ModelにBigDecimalを使用して、TextFieldから値を入力する際大きな値を入力するとModelの値が化けます。具体的にはlongで扱える範囲を超えた場合に化けることがあるという……。
 longで扱える範囲を超えることと言うのはあまりないので、これが問題になるケースは少ないとは思います。

原因

 端的に言うとjava.text.NumberFormat#parseObject(String, ParsePosition)の仕様のせいです。
 なぜそうなのかという説明のために、org.apache.wicket.util.convert.converters.BigDecimalConverter#convertToObject(String, Locale)のシーケンス図*1を以下に示します(拡大したい人は図をクリックしてください)。

 図に示したようにBigDecimalConverter#convertToObject(String, Locale)は内部で、java.text.NumberFormat#parseObject(String, ParsePosition)を使用しています。

 java.text.NumberFormat#parseObject(String, ParsePosition)のリファレンスを読むと、java.text.NumberFormat#parse(String, ParsePosition)のリファレンスを読むように書かれているのですが、そこに注目すべき仕様が記載されています。
 以下に引用します。

可能な場合 (たとえば、[Long.MIN_VALUE, Long.MAX_VALUE] の範囲で、小数部分がない場合) は Long を、そうでない場合は Double を返します。IntegerOnly が設定されていると、小数点で止まります (または、それと同等のもの、たとえば、分数 "1 2/3" では 1 の後で止まる)。例外はスローしません。オブジェクトが解析できない場合、インデックスは変わりません。

 簡単に言うと「longで扱える範囲を超えると、doubleで扱う」というわけです。
 よって、ModelにBigDecimalを使用していても、精度の問題に遭遇するわけです。

さいごに

 BigDecimalConverterなんて名前だったら、BigDecimalの精度で扱えると誤解してしまいますよね。JavaDocに注意書きがあったら親切なのに、なんて思わないでもないです。

*1:明らかなミスが1カ所あります。parseObject(String, ParsePosition)の戻り値がvoidとなってます。許しておいてください。