Javaでは小数点以下の数値をdoubleやfloatのまま計算(四則演算)を行うと2進数で処理されるため、2進数で扱いきれない数値は丸め誤差が発生してしまいます。金額など誤差が許されない計算をJavaで実施する場合はBigDecimalを使用するべきです。
doubleやfloatで誤差が出る例
- 以下の様な計算をdoubleやfloatで実施すると丸め誤差が発生して正確な結果が得られません。
123456public static void main(String[] args) {double d = 1.3 + 2.1;float f = 1.3f + 2.1f;System.out.println("double = " + d);System.out.println("float = " + f);}
- コンソール
12double = 3.4000000000000004float = 3.3999999
BigDecimalで加算した場合の例
- 上記の誤差が発生した計算をBigDecimalで実施すると以下の様に誤差は発生しなくなります。
12345public static void main(String[] args) {BigDecimal bd = new BigDecimal("1.3");BigDecimal bdAdd = new BigDecimal("2.1").add(bd);System.out.println("BigDecimal.add = " + bdAdd);}
- コンソール
1BigDecimal.add = 3.4
BigDecimalで減算、乗算した場合の例
- BigDecimalで減算する場合はsubstractメソッド、乗算する場合はmultiplyメソッドを使用します。
1234567public static void main(String[] args) {BigDecimal bd = new BigDecimal("1.3");BigDecimal bdSub = new BigDecimal("1.1").subtract(bd); // 減算BigDecimal bdMul = new BigDecimal("1.1").multiply(bd); // 乗算System.out.println("BigDecimal.subtract = " + bdSub);System.out.println("BigDecimal.multiply = " + bdMul);}
- コンソール
12BigDecimal.subtract = -0.2BigDecimal.multiply = 1.43
BigDecimalで除算(四捨五入、切り捨て、切り上げ)した場合の例
- BigDecimalで除算する場合はdivideメソッドを使用し、第2引数へ取得したい小数部の桁数、第3引数へ第2引数へ指定した桁以降の扱い(RoundingMode.*)を指定します。
12345678public static void main(String[] args) {BigDecimal bdDiv = new BigDecimal("1.1").divide(bd,4,RoundingMode.HALF_UP); // 除算(小数部第4位まで表示、第5位は四捨五入)BigDecimal bdDivFloor = new BigDecimal("1.1").divide(bd,4,RoundingMode.FLOOR); // 除算(小数部第4位まで表示、第5位は切り捨て)BigDecimal bdDivCeiling = new BigDecimal("1.1").divide(bd,4,RoundingMode.CEILING); // 除算(小数部第4位まで表示、第5位は切り上げ)System.out.println("BigDecimal.divide(HALF_UP) = " + bdDiv);System.out.println("BigDecimal.divide(FLOOR) = " + bdDivFloor);System.out.println("BigDecimal.divide(CEILING) = " + bdDivCeiling);}
- コンソール
123BigDecimal.divide(HALF_UP) = 0.8462BigDecimal.divide(FLOOR) = 0.8461BigDecimal.divide(CEILING) = 0.8462
- RoundingModeの種類について
RoundingModeの列挙型定数 説明 RoundingMode.HALF_UP 四捨五入 RoundingMode.FLOOR 切り捨て(負の無限大に近づくように丸める) RoundingMode.DOWN 切り捨て(0に近づくように丸める) RoundingMode.CEILING 切り上げ RoundingMode.HALF_DOWN 五捨六入(もっとも近い数字に丸める) RoundingMode.HALF_EVEN 偶数の場合はHALF_DOWN、奇数の場合はHALF_UPのように動作します RoundingMode.UNNECESSARY 何もしない
※丸めが必要な場合はArithmeticExceptionをスローします
補足:注意すべきポイントとベストプラクティス
本稿では BigDecimal を使って浮動小数点による丸め誤差を回避する基本的な使い方をご紹介しましたが、実務で使う際には以下の点にもご留意ください。
-
文字列コンストラクタを使う理由
new BigDecimal("1.3")のように文字列引数を用いるのは、new BigDecimal(1.3)のように浮動小数点リテラルを使うと、すでに 2 進数表現による誤差を内部に含んだ値が作られてしまうためです。実務では、入力が文字列・ユーザー入力・CSV等から来る場合が多く、こうした文字列からの生成の方が安全です。 -
スケールと丸めモードの明示
除算(divide())で小数部の桁数・丸めモードを指定しないと、ArithmeticExceptionがスローされる可能性があります。特に金額計算・単価計算など「必ず何桁以下で計算結果を示す」必要がある処理では、スケールとRoundingModeを明確に記述しましょう。 -
パフォーマンスへの配慮
BigDecimal は内部で任意精度の整数演算を行うため、四則演算や除算のたびにオブジェクト生成・メモリ操作が発生します。大量データや高頻度ループ処理、リアルタイム系の演算ではdouble/longといったプリミティブ型+補正処理(例えば「金額×100→long にして整数演算」)を併用検討することも現実的です。 -
比較処理の注意
BigDecimal の比較にはequals()ではなくcompareTo()を使うのが一般的です。例えばnew BigDecimal("1.0").equals(new BigDecimal("1.00"))は false ですが、compareTo()では 0(等価)を返します。使用目的に応じて適切な比較メソッドを選びましょう。 -
コンテキストに応じたツール選択
金額、為替、統計、計測値など「小数・精度が重要な値」の演算には BigDecimal が強力ですが、すべてに適用すべきというわけではありません。例えば「科学技術計算」「グラフィック演算」「高速な近似演算」などでは浮動小数点で十分だったり、むしろ高速化のためにそちらが適切だったりします。目的と制約(精度、性能、可読性、メモリ)を整理した上で、適切な型を選定してください。
