Java9でBigDecimal#ROUND_~系がやっと@Deprecatedになりましたね。
にもかかわらず、未だにBigDecimalを解説するブログやエントリで、BigDecimal#setScale・BigDecimal#divideなどを説明するときにBigDecimal#ROUND_~系を利用して説明されるのはなんなんですか?それ全部、Java9で@Deprecatedですよ?
と、最近モヤモヤするので記事書くことにしました。
まずは基本的なことから。
生成・Factoryメソッド
基本
基本的に以下の方法で生成する。
BigDecimal value = BigDecimal.valueOf(1234.567);// 数値系からの生成
BigDecimal value = new BigDecimal("1234.567");// 文字列から生成
0,1,10は定義済なのでこれらを使う。
BigDecimal.ZERO
BigDecimal.ONE
BigDecimal.TEN
文字列からの生成に関する注意
文字列からの生成はscaleも文字列で指定した通りになる。
System.out.println(new BigDecimal("1234.567").scale()); // → 3
System.out.println(new BigDecimal("1234.56700").scale());// → 5
数値からの生成のNGパターン
数値(double)からの生成はBigDecimal#valueOfを使ってください。newで生成するととんでもないことになります。
BigDecimal value = new BigDecimal(1234.567);// NG!!数値系からの生成
System.out.println(value);// → 1234.5670000000000072759576141834259033203125
ResultSet#getBigDecimal
ResultSetから取得する場合はResultSet#getBigDecimalがあるのでこれを使う。
BigDecimal value = rs.getBigDecimal("COL");
以下は無駄なのでNG
BigDecimal value = BigDecimal.valueOf(rs.getDouble("COL"));
BigDecimal value = new BigDecimal(rs.getString("COL"));
四則演算
加算
BigDecimal a = BigDecimal.valueOf(123);
BigDecimal b = BigDecimal.valueOf(456);
// value = a + b
BigDecimal value = a.add(b);
System.out.println(value);// 579
減算
BigDecimal a = BigDecimal.valueOf(123);
BigDecimal b = BigDecimal.valueOf(456);
// value = a - b
BigDecimal value = a.subtract(b);
System.out.println(value);// -333
乗算
BigDecimal a = BigDecimal.valueOf(123);
BigDecimal b = BigDecimal.valueOf(456);
// value = a * b
BigDecimal value = a.multiply(b);
System.out.println(value);// 56088
10倍・100倍等
BigDecimal#scaleByPowerOfTenを使うといい。
BigDecimal src = BigDecimal.valueOf(0.123);
BigDecimal value = src.scaleByPowerOfTen(2);// 100倍(10の2乗倍)
System.out.println(value);// 12.3
src.multiply(BigDecimal.valueOf(100))よりだいぶスッキリするし、100のインスタンス作らなくていい。
マイナス化
BigDecimal#negateを使うといい。
BigDecimal src = BigDecimal.valueOf(123);
BigDecimal value = src.negate();
System.out.println(value);// -123
以下のような書き方はやめてほしい。
BigDecimal value = src.multiply(BigDecimal.valueOf(-1));
除算
BigDecimal a = BigDecimal.valueOf(123);
BigDecimal b = BigDecimal.valueOf(456);
// value = a / b
BigDecimal value = a.divide(b, 3 /* ← scale */, RoundingMode.HALF_UP /* ← 四捨五入 */);
System.out.println(value);// 0.270
RoundingModeに置き換え可能なメソッドは利用NG
以下のような書き方はNG
BigDecimal value = a.divide(b, 3, BigDecimal.ROUND_HALF_UP);
RoundingModeについては下のほうでに詳細を書くので確認してください。
scaleを指定する
scaleを指定しないBigDecimal#divideメソッドもありますが、
割り切れない時ArithmeticExceptionが発生するのでscale指定版を使うのがいい。
1/10・1/100等
BigDecimal#scaleByPowerOfTenを使うのがいい。
BigDecimal src = BigDecimal.valueOf(8);
BigDecimal value = src.scaleByPowerOfTen(-2);// 1/100(10の-2乗倍)
System.out.println(value);// 0.08
四捨五入・切り捨て・切り上げ
BigDecimal src = BigDecimal.valueOf(123.456);
//四捨五入
BigDecimal value = src.setScale(2, RoundingMode.HALF_UP);
System.out.println(value); // → 123.46
//切り上げ
BigDecimal value = src.setScale(2, RoundingMode.UP);
//切り捨て
BigDecimal value = src.setScale(2, RoundingMode.DOWN);
RoundingModeに置き換え可能なメソッドは利用NG
以下のような書き方はNG
BigDecimal value = src.setScale(2, BigDecimal.ROUND_HALF_UP);
RoundingModeについては下のほうでに詳細を書くので確認してください。
文字列化
BigDecimal#toPlainString
BigDecimal#toStringは指数表記になる可能性があるので、BigDecimal#toPlainStringを使うほうが良いと思う。
BigDecimal value = new BigDecimal("0.0000001");
System.out.println(value.toString()); // → 1E-7
System.out.println(value.toPlainString());// → 0.0000001
jacksonにもtoPlainStringを使うオプションがあったりする。
JsonGenerator.Feature.html#WRITE_BIGDECIMAL_AS_PLAIN
小数の末尾の0を削除
BigDecimal#toStringもBigDecimal#toPlainStringも、文字列にはscaleが考慮され、小数の末尾に0が続いても残される。
これを消したい場合は、BigDecimal#stripTrailingZerosを使う。
正規表現で頑張る必要はない。
BigDecimal value = new BigDecimal("1.000000");
System.out.println(value); // → 1.000000
System.out.println(value.stripTrailingZeros());// → 1
※ただし、BigDecimal#stripTrailingZerosはjava7まではバグがあるので注意
JDK-6480539 : BigDecimal.stripTrailingZeros() has no effect on zero itself ("0.0")
int・long・double化
BigDecimal value = BigDecimal.valueOf(12345);
int i = value.intValue();
long l = value.longValue();
double d = value.doubleValue();
float・BigIntegerもあります。
許容のチェック版
整数への変換には、許容範囲外で失われる情報があるような場合に例外を投げる版がある。
逆にいうと通常版は情報が失われても無視される。
int i = value.intValueExact();
long l = value.longValueExact();
-ValueExact系メソッドにはbyteとshortとBigInteger版もあります。
一致比較
通常の比較
BigDecimal#equalsはscaleも一致しないとfalseを返すので、
scaleとか興味ないシステムを書いているなら、compareToの結果が0と一致するかで判定したほうが良い。
BigDecimal value1 = new BigDecimal("123.0"); // scale=1
BigDecimal value2 = new BigDecimal("123.00");// scale=2
System.out.println(value1.equals(value2)); // false
System.out.println(value1.compareTo(value2) == 0);// true
HashMapのキーに利用
BigDecimal#equals・BigDecimal#hashCodeはscaleも考慮されるので、
scaleとか興味ないシステムを書いていると、痛い目見るかもしれない。
BigDecimal value1 = new BigDecimal("123.0"); // scale=1
BigDecimal value2 = new BigDecimal("123.00");// scale=2
Map<BigDecimal, String> map = new HashMap<>();
map.put(value1, "data");
System.out.println(map.get(value2));// null
こういう場合はBigDecimal#stripTrailingZerosでscaleを安定させるといいかもしれない。
BigDecimal value1 = new BigDecimal("123.0"); // scale=1
BigDecimal value2 = new BigDecimal("123.00");// scale=2
Map<BigDecimal, String> map = new HashMap<>();
map.put(value1.stripTrailingZeros(), "data");
System.out.println(map.get(value2.stripTrailingZeros()));// data
※ただし、BigDecimal#stripTrailingZerosはjava7まではバグがあるので注意
JDK-6480539 : BigDecimal.stripTrailingZeros() has no effect on zero itself ("0.0")
RoundingMode
RoundingModeはenumで定義されていて、「四捨五入」以外にも「切り上げ」「切り捨て」「正の無限大への丸め」「負の無限大への丸め」などいろいろあるのでjavadocを一度確認してみるといいと思います。
以下はRoundingModeのjavadocの転記。
CEILING
正の無限大に近づくように丸めるモードです。
DOWN
0に近づくように丸めるモードです。
FLOOR
負の無限大に近づくように丸めるモードです。
HALF_DOWN
「もっとも近い数字」に丸める丸めモードです(両隣りの数字が等距離の場合は切り捨てます)。
HALF_EVEN
「もっとも近い数字」に丸める丸めモードです(ただし、両隣りの数字が等距離の場合は偶数側に丸めます)。
HALF_UP
「もっとも近い数字」に丸める丸めモードです(ただし、両隣りの数字が等距離の場合は切り上げます)。
UNNECESSARY
要求される演算の結果が正確であり、丸めが必要でないことを表す丸めモードです。
UP
0から離れるように丸めるモードです。
RoundingModeに置き換え可能なメソッドは利用NG
BigDecimal#ROUND_~系とこれらの利用を前提にしたメソッドは利用しないこと。(Java9で@Deprecated)
それぞれ、RoundingModeを利用する対になるメソッドがあるはずなのでこれらを利用しましょう。
9で@Deprecatedになったメソッドは1.4時代の遺産です。
ただ、普通に「BigDecimal 割り算」とかでググると、BigDecimal.ROUND_HALF_UPとか使っているページばかり出てきて絶望しています。
違いがわからない、、、
HALF_DOWN
「もっとも近い数字」に丸めるモードで、近い数字が2つ等距離ある場合は切り捨てます。
HALF_EVEN
「もっとも近い数字」に丸めるモードで、近い数字が2つ等距離ある場合は偶数にします。
HALF_UP
「もっとも近い数字」に丸めるモードで、近い数字が2つ等距離ある場合は切り上げます。
コメントありがとうございます。
ですね。。。
ちゃんと読んでませんでした。
引用元の選択を間違えましたね。
https://docs.oracle.com/javase/jp/6/api/java/math/RoundingMode.html
あと、ちゃんと探したら8の日本語版ありましたね。
https://docs.oracle.com/javase/jp/8/docs/api/java/math/RoundingMode.html
引用元を変更します。
指摘ありがとうございます。