お金の計算など正確にJava で計算をするうえで欠かせないBigDecimal ですが、
一部JDK バージョンで挙動に変更が入っていました。
この改修により問題に直面してしまったため備忘録がてら挙動をまとめることにしました。
まず、本題に入る前にBigDecimal はどのように値を保持しているかを見てみましょう。
BigDecimal は以下の要素を保持しています。
intCompact
intVal
precision
scale
では実際に見てみましょう。
BigDecimal bigDecimal1 = new BigDecimal("3.14e+25" );
BigDecimal bigDecimal2 = new BigDecimal("31400000000000000000000000" );
BigDecimal の内部値例
bigDecimal1は指数表現を指定し作成されているためintCompact
には314、precision
には3、scale
には-23が格納されています。
また、bigDecimal1は指数表記なのでintVal
に値は保持されていません。
bigDecimal2は通常の数値表記を指定し作成されているためintCompact
にはLong最低値、intVal
にはスケーリングされていない値、precision
には26が格納されています。
このようにBigDecimal は同じ数値でありながらも内部の値が異なることがあるというパターンは存在します。
それでは本題です。
先ほどの例で見た通りBigDecimal の値保持には2つのパターンがありました。
これらのtoStringすると以下のようになります。
bigDecimal1 : 3.14E+25
bigDecimal2 : 31400000000000000000000000
当然BigDecimal 作成時に指定したものになりますね。
ここで例えば、この文字列を画面表示に使用したい場合について考えてみます。
3.14E+25
のように表示しても数値自体は誤りではないですが、
金額の表示などであれば非常にわかりづらいですよね。
ではbigDecimal2は良いとしてもbigDecimal1はどうすればよいでしょうか。
誤った表記変換方法
指数表記になってしまったBigDecimal を通常の数値表記の戻す方法として誤っている例を挙げてみます。
何を使用するかというとBigDecimal のメソッドのmovePointRight(0)
です。
movePointRight
は小数点を右に指定した分だけずらすというメソッドです。
つまり右に小数点を0個ずらすということです。
BigDecimal bigDecimal1 = new BigDecimal("3.14e+25" ).movePointRight(0 );
BigDecimal bigDecimal2 = new BigDecimal("31400000000000000000000000" ).movePointRight(0 );
JDK のバージョンごとの結果は以下の通りになりました。
JDK バージョン
bigDecimal1
bigDecimal2
8
31400000000000000000000000
31400000000000000000000000
11
31400000000000000000000000
31400000000000000000000000
17
3.14E+25
31400000000000000000000000
21
31400000000000000000000000
31400000000000000000000000
なんとJDK17だけ bigDecimal1が指数表記のままとなってしまっています。
※以下は使用したJDK のディストリビューション とバージョンです。
正しい文字列を取得する方法
movePointRight(0)
を使用したBigDecimal をtoStringで表示させた際にJDK17のときのみ
指数表記になってしまうということがわかりました。
では正しい値を取り出す方法はあるのでしょうか。
もちろん存在します。
BigDecimal のtoPlainStringメソッドです。
これを使用すると保持している内部データが異なっていても通常の数値表現(文字列)を返してくれます。
このメソッドはJDK 5から実装されているので問題になっていないJDK バージョンでも積極的に使用したいですね。
まとめ
JDK バージョンによってBigDecimal が動作が一部異なるという現象に遭遇し今回詳しく調べて見ました。
JDK17でのみ挙動が異なっており、最新のLTSであるJDK21では発生していないので
今後修正される可能性もあるかもしれません。
しかし、Java 側で通常の数値表記を取り出すメソッドが用意されているので今後はそれを使用したほうが良さそうです。
BigDecimal を使用する場面というのは間違うことが許されないような数値を扱う場面が殆どかと思います。
今後の更新も要チェックですね。