つかびーの技術日記

情報系修士卒のWeb系技術日記です。現在のフォーカス分野はアドテクです。

浮動小数点数(float, double)演算の丸め誤差と対策(Javaの例)

   

最近浮動小数点数演算に起因する調査を行う機会があったので、復習しました。

浮動小数点数(Javaで言うとfloatとかdouble)を使って演算するときは丸め誤差によって意図した値にならないことがあります。これが発生するケースや対策について書いてみました。

しっかり書こうと思ったけど、当然ながらこれについて書いている記事は他にも色々あるし、自分自身他のblogで書いたことがあったので、結構省略していきます。

ちなみに以下のサイトとか分かりやすくて良いですよ。

[Java] 小数点の計算をやるからといってすぐにfloatやdoubleを使ってはいけない

現象

さて、このプログラムの出力結果は何でしょうか。

普通に考えば1.0ですが、答えは・・・

上記の計算に使った小数はどれも浮動小数点数であり、循環小数です。そのため、コンピュータの内部では丸め誤差によって意図した値になっていないことがあります。

今回の計算は1.0より僅かに小さい値になりましたが、計算によっては僅かに大きい値になることもあります。

対策

さて、ではどうすれば良いのでしょうか。

セオリー通りにやるならば、BCDを使う方法かと思います。JavaではBCDはBigDecimalとして提供されています。ただし、これは性能が悪いのでシーンによっては四捨五入でも良いかもしれません。

BigDecimalの性能

以下が参考になります。

JavaSE BigDecimalの演算速度 – @//メモ – kagyuu

自分でも実行してみました。

100回doubleまたはBigDecimalを計算する処理を用意して、1000回実行した実行時間の平均を出力します。

結果はこんな感じ。

doubleだと0.007msで、BigDecimalは0.096ms、doubleとBigDecimalで13倍の開きがありました。

BigDecimalが遅いという人は結構いるかと思いますが・・・本当に使えないほど遅いかどうかはコンテキスト次第のはずです。仮にWebアプリケーションがリクエストに応じて数値データを返す、みたいな処理があり、これが大体100msだとしましょう。ここに100回double演算を行う処理を足すと100.007msになり、BigDecimalなら100.096msになるはずです。このようなケースであれば自分ならとにかくBigDecimalを使って安全に計算する方が良いと思います。もし100msでは問題があり、高速化が必要になるのであればBigDecimal云々ではなく、パレートの法則に従って他の部分を見直します。

まとめ

  • 言語によっては小数演算で誤差が生じる
  • 実際の例はコードの通り
  • 対策はBigDecimalなどのBCD方式を使うか、許容できるのであれば四捨五入

 - Java , , ,