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));
除算
import java.math.RoundingMode;
//・・・
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
四捨五入・切り捨て・切り上げ
import java.math.RoundingMode;
//・・・
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
とか使っているページばかり出てきて絶望しています。