概要
消費税計算は金額を扱うシステムで必ず必要になる処理ですが、端数処理の方式(四捨五入、切り捨て、切り上げ)によって1円の差が生まれます。double や float では誤差が避けられないため、業務用途では BigDecimal を使うのが鉄則です。この記事では、税抜から税込への変換、税込から税抜への逆算、軽減税率(8%)と標準税率(10%)の使い分けを BigDecimal で実装します。RoundingMode の選び方や、明細と合計で端数処理のタイミングが異なる場合の対応も含めて整理します。
使いどころ
請求書の明細行ごとに消費税を計算し、合計金額と突き合わせる
POS レジで税込表示価格から税抜金額を逆算する
軽減税率対象商品と標準税率商品を混在して合計する
コード例
import java.math.BigDecimal;
import java.math.RoundingMode;
public class TaxCalculator {
private static final BigDecimal STANDARD_RATE =
new BigDecimal("0.10");
private static final BigDecimal REDUCED_RATE =
new BigDecimal("0.08");
public static BigDecimal withTax(
BigDecimal price, BigDecimal rate,
RoundingMode mode) {
var tax = price.multiply(rate)
.setScale(0, mode);
return price.add(tax);
}
public static BigDecimal withoutTax(
BigDecimal priceWithTax, BigDecimal rate,
RoundingMode mode) {
return priceWithTax.divide(
BigDecimal.ONE.add(rate), 0, mode);
}
public static void main(String[] args) {
var price = new BigDecimal("1980");
System.out.println("税抜: " + price);
System.out.println("税込(10%): " +
withTax(price, STANDARD_RATE,
RoundingMode.FLOOR));
System.out.println("税込(8%): " +
withTax(price, REDUCED_RATE,
RoundingMode.FLOOR));
}
}Version Coverage
switch 式で商品種別による税率の切り替えが簡潔になる。record で金額と税額をまとめると可読性が上がる。
// Java 17: switch 式で税率を切り替え
String itemType = "food";
BigDecimal rate = switch (itemType) {
case "food", "newspaper" -> REDUCED_RATE;
default -> STANDARD_RATE;
};
BigDecimal total = calcTaxIncluded(price, rate);Library Comparison
注意点
BigDecimal の生成には new BigDecimal('0.1') のように文字列を使うこと。double リテラルは誤差が入る。
端数処理のタイミング(明細ごとか合計か)は取引先との合意に依存する。
税率は法改正で変わる可能性があるため、定数管理を推奨する。
FAQ
double は2進浮動小数点のため、0.1 を正確に表現できず、金額計算で1円の誤差が生じます。
日本の商慣習では切り捨て(FLOOR/DOWN)が多いですが、契約や業種で異なるため要確認です。
明細行ごとに税率を判定し、税率別に小計を出してから合算する方法が安全です。