概要
単位変換のコードで怖いのは、変換式の誤りよりも「どの単位で値を持っているか」の認識ズレです。DB にはグラムで入っているのに画面はキログラム前提だった、海外仕様書の温度が華氏だったのに摂氏として計算していた――このたぐいの不具合はコンパイルも通り、テストも一見通ってしまうため、発見が遅れがちです。実装面では、N 種類の単位を相互変換しようとして N×N 通りの変換式を書き始めると保守が破綻します。この記事では、enum に基準単位への係数を持たせて「基準単位を経由して変換する」設計パターンを軸に、BigDecimal での係数管理、温度のようにオフセットが絡む単位系の扱い、単位を追加するときの拡張手順を整理します。
使いどころ
海外倉庫とのデータ連携で、lb 単位で届く重量を kg に揃えて在庫システムへ取り込む
図面管理システムで inch 表記の寸法を mm に変換し、公差計算の基準を統一する
設備モニタリングで華氏で送られてくるセンサー値を摂氏に変換して閾値判定にかける
コード例
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
public class UnitConverter {
enum LengthUnit {
METER(BigDecimal.ONE),
CENTIMETER(new BigDecimal("0.01")),
MILLIMETER(new BigDecimal("0.001")),
KILOMETER(new BigDecimal("1000")),
INCH(new BigDecimal("0.0254")),
FOOT(new BigDecimal("0.3048"));
final BigDecimal toMeter;
LengthUnit(BigDecimal toMeter) {
this.toMeter = toMeter;
}
}
private static final MathContext MC =
new MathContext(10, RoundingMode.HALF_UP);
public static BigDecimal convert(
BigDecimal value,
LengthUnit from,
LengthUnit to) {
var meters = value.multiply(from.toMeter);
return meters.divide(to.toMeter, MC);
}
public static void main(String[] args) {
var feet = new BigDecimal("6");
var cm = convert(feet,
LengthUnit.FOOT, LengthUnit.CENTIMETER);
System.out.println(feet + " ft = "
+ cm.setScale(2, RoundingMode.HALF_UP) + " cm");
// → 6 ft = 182.88 cm
// 逆変換して元の値に戻ることを確認(往復テストの基本形)
var back = convert(cm,
LengthUnit.CENTIMETER, LengthUnit.FOOT);
System.out.println("往復: "
+ back.setScale(2, RoundingMode.HALF_UP) + " ft");
// → 6.00 ft
// 桁の離れた変換も基準単位(メートル)経由なら崩れない
var mm = new BigDecimal("1500000");
System.out.println(mm + " mm = "
+ convert(mm, LengthUnit.MILLIMETER, LengthUnit.KILOMETER)
.setScale(1, RoundingMode.HALF_UP) + " km");
// → 1500000 mm = 1.5 km
}
}Version Coverage
record + switch 式で変換ロジックを簡潔に書ける。var による型推論で記述量も減る。
// Java 17: record + switch 式で変換ロジック
record Conversion(BigDecimal value, String unit) {}
var result = switch (to) {
case METER -> meters;
case CENTIMETER -> meters.divide(
CENTIMETER.toMeter, MC);
case INCH -> meters.divide(
INCH.toMeter, MC);
};
var output = new Conversion(result, to.name());Library Comparison
注意点
温度変換は単純な係数乗算ではないため、別途ロジックが必要。
BigDecimal で係数を管理しないと浮動小数点誤差が入る。
単位の追加時は enum にフィールドを追加するだけで拡張できる設計にする。
現場では単位の取り違えが原因の不具合が意外と多い。DB や外部 API とのデータ授受では単位を明示するコメントや定数名をつける習慣をつけること。グラムとキログラムの混在は特に見逃しやすい。
FAQ
単位を増やすときの修正が「基準単位への係数を1つ追加する」だけで済みます。相互変換式を個別に書く方式では、単位数の2乗のペースで式が増えていきます。
摂氏・華氏はオフセット(32度)の加減算が入るため、係数の乗除だけでは表現できません。温度だけは専用の変換メソッドに分離するのが素直な設計です。
0.0254 のような係数は2進数で誤差を含むため、乗除を繰り返すと下位桁がずれます。検査成績書や請求に関わる値なら BigDecimal で管理してください。