四捨五入(round)の罠

ブログを立ち上げてグダグダやってる間にForce.com AdventCalendarの順番が回ってきてしまった……。

ということで、しょっぱいネタですがapexで四捨五入にはどうすれば良いのかちょっと書いてみたり。すでに日本語で詳しく解説してあるところがすでに存在してたりしますし常識かもですが、こんなのあったなあと頭の片隅にでも置いてもらえれば幸いです。

 

例えばjavaでdoubleやfloatを四捨五入する場合、下記のコードが一般的だと思います。

 

  1. System.out.println(Math.round(11.4));
  2. System.out.println(Math.round(11.5));


上記の結果は1が11、2は12が返ってきます。順当な結果ですね。

apexのMathクラスにもroundがあります。これを使って同じ事をしてみましょう。

 

  1. System.debug(Math.round(11.4));
  2. System.debug(Math.round(11.5));

この結果も1が11、2は12が返ってきます。これで四捨五入は完成! と思い込んでしまうと、潜在バグの完成です……。

今度は引数の値を変えてやってみます。

 

  1. System.debug(Math.round(10.4));
  2. System.debug(Math.round(10.5));

1の結果は10。これは問題無いですね。しかし2の結果を見ると、11になってほしいはずが10が返ってきます。日本の四捨五入は基本的に4以下は切り捨て、5以上は切り上げなので、大抵これは期待していた値ではありません。

さて、なぜこんな値になってしまうかというと、デフォルトの丸めモードが両言語で違うからです。

javaの丸めモードはHALF_UPといい、日本で言う四捨五入と同一の動きをします。

対してapexの丸めモードはHALF_EVEN。これは基本的に四捨五入と同じ動きをするのですが、結果をなるべく偶数に寄らせるという特性をもっています。

先ほどの例では、10.5を四捨五入すると11になるのですが、切り上げでは奇数になるので、切り捨てにして偶数の10に寄ってしまったために起こってしまったのです。

さて、では単純に四捨五入するにはどうすればよいかというと、以下のコードでHALF_UPが指定できます。

  1. Decimal rnd = 10.5;
  2. System.debug(rnd.setScale(0, RoundingMode.HALF_UP));

上記を実行すると、期待した値の11が返ってきます。

 

HALF_EVENの厄介なところは、テストデータによって一見正常に動作しているように見えるところです。自分も最初はこれで良いと思い込んでいて、ギリギリになって気付いてヒヤッとした覚えがあります……。四捨五入が必要な部分は金額が多いので、そこが狂ってしまうと大問題になりかねません。HALF_EVENで問題なければ良いのですが。

さて、次はどうするかな。本気でネタがないので小説でも書いてみるかもw