【Java】BigDecimalをちゃんと使う~2018~

Java9で`BigDecimal#ROUND_~`系がやっと`@Deprecated`になりましたね。
にもかかわらず、未だに`BigDecimal`を解説するブログやエントリで、`BigDecimal#setScale`・`BigDecimal#divide`などを説明するときに`BigDecimal#ROUND_~`系を利用して説明されるのはなんなんですか？それ全部、Java9で`@Deprecated`ですよ？
と、最近モヤモヤするので記事書くことにしました。

まずは基本的なことから。

# 生成・Factoryメソッド

## 基本
基本的に以下の方法で生成する。

```java
BigDecimal value = BigDecimal.valueOf(1234.567);// 数値系からの生成
BigDecimal value = new BigDecimal("1234.567");// 文字列から生成
```

`0`,`1`,`10`は定義済なのでこれらを使う。

```java
BigDecimal.ZERO
BigDecimal.ONE
BigDecimal.TEN
```

## 文字列からの生成に関する注意
文字列からの生成は`scale`も文字列で指定した通りになる。

```java
System.out.println(new BigDecimal("1234.567").scale());  // → 3
System.out.println(new BigDecimal("1234.56700").scale());// → 5
```

## 数値からの生成のNGパターン

数値（double）からの生成は`BigDecimal#valueOf`を使ってください。`new`で生成するととんでもないことになります。

```java
BigDecimal value = new BigDecimal(1234.567);// NG!!数値系からの生成
System.out.println(value);// → 1234.5670000000000072759576141834259033203125
```

## `ResultSet#getBigDecimal`
`ResultSet`から取得する場合は`ResultSet#getBigDecimal`があるのでこれを使う。

```java
BigDecimal value = rs.getBigDecimal("COL");
```

以下は無駄なのでNG

```java
BigDecimal value = BigDecimal.valueOf(rs.getDouble("COL"));
BigDecimal value = new BigDecimal(rs.getString("COL"));
```

# 四則演算

## 加算

```java
BigDecimal a = BigDecimal.valueOf(123);
BigDecimal b = BigDecimal.valueOf(456);

// value = a + b
BigDecimal value = a.add(b);

System.out.println(value);// 579
```

## 減算

```java
BigDecimal a = BigDecimal.valueOf(123);
BigDecimal b = BigDecimal.valueOf(456);

// value = a - b
BigDecimal value = a.subtract(b);

System.out.println(value);// -333
```

## 乗算

```java
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`を使うといい。

```java
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`を使うといい。

```java
BigDecimal src = BigDecimal.valueOf(123);
BigDecimal value = src.negate();
System.out.println(value);// -123
```

以下のような書き方はやめてほしい。

```java
BigDecimal value = src.multiply(BigDecimal.valueOf(-1));
```

## 除算

```java
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

```java
BigDecimal value = a.divide(b, 3, BigDecimal.ROUND_HALF_UP);
```

`RoundingMode`については[下のほうで](#roundingmode)に詳細を書くので確認してください。

### `scale`を指定する

`scale`を指定しない`BigDecimal#divide`メソッドもありますが、
割り切れない時`ArithmeticException`が発生するので`scale`指定版を使うのがいい。

### 1/10・1/100等
`BigDecimal#scaleByPowerOfTen`を使うのがいい。

```java
BigDecimal src = BigDecimal.valueOf(8);
BigDecimal value = src.scaleByPowerOfTen(-2);// 1/100(10の-2乗倍)
System.out.println(value);// 0.08
```

# 四捨五入・切り捨て・切り上げ

```java
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

```java
BigDecimal value = src.setScale(2, BigDecimal.ROUND_HALF_UP);
```

`RoundingMode`については[下のほうで](#roundingmode)に詳細を書くので確認してください。

# 文字列化

## `BigDecimal#toPlainString`
`BigDecimal#toString`は指数表記になる可能性があるので、`BigDecimal#toPlainString`を使うほうが良いと思う。

```java
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](http://static.javadoc.io/com.fasterxml.jackson.core/jackson-core/2.9.0/com/fasterxml/jackson/core/JsonGenerator.Feature.html#WRITE_BIGDECIMAL_AS_PLAIN)

## 小数の末尾の`0`を削除

`BigDecimal#toString`も`BigDecimal#toPlainString`も、文字列には`scale`が考慮され、小数の末尾に`0`が続いても残される。
これを消したい場合は、`BigDecimal#stripTrailingZeros`を使う。
正規表現で頑張る必要はない。


```java
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")](https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6480539)

# `int`・`long`・`double`化

```java
BigDecimal value = BigDecimal.valueOf(12345);
int i = value.intValue();
long l = value.longValue();
double d = value.doubleValue();
```
`float`・`BigInteger`もあります。

## 許容のチェック版

整数への変換には、許容範囲外で失われる情報があるような場合に例外を投げる版がある。
逆にいうと通常版は情報が失われても無視される。

```java
int i = value.intValueExact();
long l = value.longValueExact();
```
`-ValueExact`系メソッドには`byte`と`short`と`BigInteger`版もあります。


# 一致比較

## 通常の比較

`BigDecimal#equals`は`scale`も一致しないと`false`を返すので、
`scale`とか興味ないシステムを書いているなら、`compareTo`の結果が`0`と一致するかで判定したほうが良い。


```java
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`とか興味ないシステムを書いていると、痛い目見るかもしれない。

```java
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`を安定させるといいかもしれない。

```java
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")](https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6480539)

# `RoundingMode`

`RoundingMode`は`enum`で定義されていて、「四捨五入」以外にも「切り上げ」「切り捨て」「正の無限大への丸め」「負の無限大への丸め」などいろいろあるのでjavadocを一度確認してみるといいと思います。

以下は[RoundingModeのjavadoc](https://docs.oracle.com/javase/jp/8/docs/api/java/math/RoundingMode.html)の転記。

>**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`とか使っているページばかり出てきて絶望しています。