Kotlinで数値計算を行う際、金額・税率・利率など「誤差が許されない値」を扱うなら BigDecimal は避けて通れません。
一方で、BigDecimalは使い方を誤ると 例外・比較バグ・意図しない丸めを招きやすいクラスでもあります。
この記事では、
- なぜBigDecimalが必要なのか
- 正しい生成方法
- 演算・割り算・丸めの安全な書き方
- equals と compareTo の使い分け
- Kotlinらしい実務パターン
を 誤解が起きない形で体系的に解説します。
なぜDoubleやFloatではダメなのか
Kotlin(JVM)の Double / Float は 2進数の浮動小数点数です。
そのため、10進数を正確に表現できません。
println(0.1 + 0.2)
// 0.30000000000000004
これはバグではなく仕様です。
金額計算・税計算などでは、この誤差がそのまま不具合になります。
BigDecimalとは何か
BigDecimal は
- 10進数を 任意精度で保持
- 丸め方法を 明示的に指定
- 金額・会計・金融向け
という特徴を持つクラスです。
Kotlinでは独自実装ではなく、Javaの java.math.BigDecimal をそのまま使用します。
import java.math.BigDecimal
BigDecimalの正しい生成方法
避けるべき生成方法
BigDecimal(0.1)
理由0.1 がすでに Double として誤差を含んでいるため、その誤差を正確に保存してしまうからです。
推奨①:文字列から生成(最も予測可能)
val price = BigDecimal("0.1")
- 値がコード上で明確
- 金額・税率・固定値に最適
- 実務ではこの方法が基本
推奨②:valueOf を使う
val price = BigDecimal.valueOf(0.1)
valueOf は内部で Double.toString() を使うため、new BigDecimal(double) より安全です。
Kotlinらしい方法:toBigDecimal()
val price = 0.1.toBigDecimal()
Kotlin標準ライブラリの拡張関数で、実質的に 文字列表現を経由する安全側の変換になっています。
実務ルールの目安
- 金額・税率・定数 → 文字列 or toBigDecimal
- 計算途中の値 → BigDecimalのまま保持
- Double ⇄ BigDecimal を行き来しない
四則演算の基本
BigDecimalは 不変(immutable) です。
演算結果は必ず新しいインスタンスになります。
val a = BigDecimal("10.5")
val b = BigDecimal("2.3")
val sum = a.add(b) // 12.8
val diff = a.subtract(b) // 8.2
val prod = a.multiply(b) // 24.15
割り算(divide)は必ず注意する
例外が発生するケース
BigDecimal("1").divide(BigDecimal("3"))
// ArithmeticException
理由:
結果が有限小数で表せない(非終端小数)場合、丸め指定がない divide は例外を投げます。
※ 1 / 2 = 0.5 のような 有限小数の場合は例外になりません。
安全な割り算の書き方
import java.math.RoundingMode
val result = BigDecimal("1").divide(
BigDecimal("3"),
2,
RoundingMode.HALF_UP
)
// 0.33
実務では「割り算=scaleとRoundingModeを指定」
これを原則にすると事故を防げます。
丸め(setScale)の正しい考え方
val value = BigDecimal("123.456")
val rounded = value.setScale(2, RoundingMode.HALF_UP)
// 123.46
注意点
- 桁を減らす場合、丸め指定がないと例外になることがある
- 桁を増やすだけなら例外にならない場合もある
実務的な結論
- 表示・保存・外部連携前には
setScale(scale, roundingMode)を明示的に使う - 「必須」というより 事故防止の標準手順
丸めモードの選び方
| モード | 説明 | 用途例 |
|---|---|---|
| HALF_UP | 四捨五入 | 一般的な金額表示 |
| HALF_EVEN | 銀行丸め | 金融・会計 |
| FLOOR | 切り捨て | ポイント・数量 |
| CEILING | 切り上げ | 利息・罰金 |
どれが正しいかは業務要件次第
税計算・請求計算では「仕様で決める」ことが重要です。
比較の正しい使い分け
equals(==)の挙動
BigDecimal("1.0") == BigDecimal("1.00") // false
- 数値 + scale(小数桁) を含めて比較
- 表現まで一致させたい場合に使う
数値として比較したい場合(推奨)
BigDecimal("1.0").compareTo(BigDecimal("1.00")) == 0 // true
Kotlinの比較演算子
a > b
a < b
これらは内部的に compareTo が使われます。
BigDecimalでも安全に使用可能です。
比較の使い分け指針
- 値として同じか? →
compareTo == 0 - 桁・精度まで同一か? →
==(equals)
Kotlinらしい拡張関数パターン
安全な割り算
fun BigDecimal.divideSafe(
divisor: BigDecimal,
scale: Int = 2,
mode: RoundingMode = RoundingMode.HALF_UP
): BigDecimal =
this.divide(divisor, scale, mode)
金額用ユーティリティ
fun BigDecimal.toYen(): BigDecimal =
this.setScale(0, RoundingMode.HALF_UP)
実務例:消費税計算(要件依存を前提に)
val price = BigDecimal("1000")
val taxRate = BigDecimal("0.1")
val tax = price
.multiply(taxRate)
.setScale(0, RoundingMode.HALF_UP)
val total = price.add(tax)
※ 端数処理のタイミング(明細単位か合計単位か)は必ず業務仕様に従うこと。
BigDecimalを使うべき/使わなくていいケース
使うべき
- 金額・税率・利率
- 精度が業務要件に含まれる計算
使わなくていい
- 座標・物理計算
- グラフィック
- 概算で十分な統計処理
まとめ
KotlinのBigDecimalで重要なのは
- 生成方法を間違えない
- 割り算と丸めを明示する
- equals と compareTo を使い分ける
この3点です。
ここを押さえれば、BigDecimalは「扱いづらいクラス」ではなく最も信頼できる数値型になります。
以上、KotlinのBigDecimalについてでした。
最後までお読みいただき、ありがとうございました。










