KotlinのBigDecimalについて

採用はこちら

Kotlinで数値計算を行う際、金額・税率・利率など「誤差が許されない値」を扱うなら BigDecimal は避けて通れません。

一方で、BigDecimalは使い方を誤ると 例外・比較バグ・意図しない丸めを招きやすいクラスでもあります。

この記事では、

  • なぜBigDecimalが必要なのか
  • 正しい生成方法
  • 演算・割り算・丸めの安全な書き方
  • equals と compareTo の使い分け
  • Kotlinらしい実務パターン

誤解が起きない形で体系的に解説します。

目次

なぜDoubleやFloatではダメなのか

Kotlin(JVM)の Double / Float2進数の浮動小数点数です。

そのため、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で重要なのは

  1. 生成方法を間違えない
  2. 割り算と丸めを明示する
  3. equals と compareTo を使い分ける

この3点です。

ここを押さえれば、BigDecimalは「扱いづらいクラス」ではなく最も信頼できる数値型になります。

以上、KotlinのBigDecimalについてでした。

最後までお読みいただき、ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次