第14夜 Java SE 6 の 数値丸めの新機能

たまにはプログラミングの話題でも。パラメータいじるのが好きな人にとってDQ9はシリーズ最高傑作すぎる。


今回はDecimalFormatのJDK 6での新機能について。地味ながらも便利な丸めのお話。派手じゃないわりと誰もが知っている機能のほうが受けがいいらしいので。数値のフォーマットはよく使われる機能だと思いますしね。



通常数値を表示するのにDecimalFormatを使うことは多いです。カンマ区切りで表示などもよく使われる機能です。では丸めはどうするのでしょうか。小数点1桁だけ表示とかよくやりますよね。単純にやると以下のようなコードになります。

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;

public class Format {

    public static void main(String[] args) {
        DecimalFormat df = new DecimalFormat(",##0.0");

        System.out.println( df.format(new BigDecimal("1234.55")) );
    }

}

結果は以下のようになります。

1,234.6

一見四捨五入されているように見えますが、実は違います。以下のコードを実行すると

        System.out.println( df.format(new BigDecimal("1234.45")) );
        System.out.println( df.format(new BigDecimal("1234.55")) );
        System.out.println( df.format(new BigDecimal("1234.65")) );

結果は以下のようになります。

1,234.4
1,234.6
1,234.6

デフォルトでの端数の処理はHALF_EVENモードで行われます。これは最も誤差を減らす丸めモードなのですが、計算中にこれを採用することはあっても表示する場合に使うことはまずないと思います。そもそも切り上げで表示したい場合、切捨てで表示したい場合、四捨五入で表示したい場合と同一の画面や帳票ですら丸めの計算方法が違う場合は珍しくなく、パラメータとして渡す側がそれを意識するのはつらい。事実上ファサードとなるメソッドが必要になります。



J2SE 5.0までは丸めモードがDecimalFormatになかったのでBigDecimal等引数側であらかじめ計算しておく必要がありました。その前の1.4では丸め処理用命令が用意されていませんでした。5.0で用意はされましたがぱっと見でわかるようには思えません。そもそも表示する項目ごとにわざわざ計算を記述しないといけないのはつらいものがありました。

たとえばJ2SE 5.0での小数点2桁目を四捨五入する場合以下のようになります。斜め読みで理解するのは難しいですよね。でもみんなこんなコードを日常的に書いていますよね。

        BigDecimal num = new BigDecimal("1234.55");
        MathContext mathContext = new MathContext(num.precision() - num.scale() + 1, RoundingMode.HALF_UP);
        System.out.println(num.round(mathContext) );

数値の表示なんてのはよくある機能です。そのよくある機能がこれだけ煩雑なのはちょっとつらいものがありました。



ではJavaSE 6でDecimalFormatはどう変わったのでしょうか。たとえば最初のコードで四捨五入表示が可能です。

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;

public class Format {

    public static void main(String[] args) {
        DecimalFormat df = new DecimalFormat(",##0.0");
        df.setRoundingMode(RoundingMode.HALF_UP);//★ここが追加されただけ!

        System.out.println( df.format(new BigDecimal("1234.45")) );
        System.out.println( df.format(new BigDecimal("1234.55")) );
        System.out.println( df.format(new BigDecimal("1234.65")) );
    }

}

setRoundingModeメソッドが追加された部分です。実行すると以下のようになります。

1,234.5
1,234.6
1,234.7

非常に簡単ですね。パラメータとして渡す数値をあらかじめ丸めの計算をしなくてよいのでらくちんです。

ほかに切り捨てにしたい場合は

        df.setRoundingMode(RoundingMode.DOWN);|

逆に切り上げにしたい場合は

        df.setRoundingMode(RoundingMode.UP);|

とするだけでいいです。表示の際にはこの3種類があれば十分でしょう。お客さんが指定してくる書式もこの3種であることがほとんどなはずです。


この機能は正確にはNumberFormatで持っていますが、まるめを意識するような桁を指定された書式で印字など細かく扱うときにはDecimalFormatを使わざるを得ないでしょうからDecimalFormatと表記しました。


ちなみに上で軽く説明しましたがBigDecimalは1.4から5.0で大幅に機能が増えていて使い勝手が別物になってます。1.4時代のBigDecimalは頻繁に使う機能すらなく、まじできつい。便利な定数も5.0から追加されました。


これでもまだJ2SE 5.0なんですか?1.4はさらに地獄ですよね?みんな地獄が好きなんですか?