丸め誤差と浮動小数点で差がつく!エンジニア評価につながる対策コードの書き方・見せ方
「なぜ同じ計算をしているはずなのに、テストのたびに結果が微妙にズレるんだろう?」
「ローカル環境では動いたのに、本番環境でだけ計算が合わない…」
プログラミング学習者や実務経験の浅いエンジニアなら、一度はこのような現象に遭遇したことがあるはずです。実は、その原因の多くが、コンピュータ特有の浮動小数点による「丸め誤差」に潜んでいます。
しかし、この問題を「よくあることだ」と軽視してしまうと、システムの信頼性を根本から揺るがし、あなたのエンジニアとしてのレビュー評価を大きく落としかねません。
一方で、この誤差の本質を正しく理解し、適切な対策をコードで表現できるエンジニアは、「品質意識の高い、信頼できる開発者」としてチーム内で高く評価されます。
本記事では、丸め誤差がなぜ発生するのかという技術的な仕組みから、レビュー評価に直結する「正しいコードの書き方」と「賢い見せ方」までを、実践的なコード例と共に徹底的に解説します。
CONTENTS
なぜ「丸め誤差」の理解がエンジニア評価を左右するのか
まず結論から言うと、丸め誤差への態度は、そのエンジニアの「品質管理能力」と「リスク意識」を測るリトマス試験紙だからです。
したがって、浮動小数点演算の限界を理解しないまま実装を進めることは、非常に危険です。例えば、金融システムや決済処理において、「1円」の計算ズレが発生すれば、それはシステム障害として扱われます。さらに、ECサイトのポイント計算や在庫管理システムでも、単価計算のわずかな誤差が、月次決算のタイミングで数百万単位の損失として発覚するケースもあります。
「0.1 + 0.2」が0.3にならない衝撃
プログラミングの世界で最も有名な例がこれです。多くの言語で、0.1 + 0.2 を計算させると、0.3 ではなく 0.30000000000000004 のような微妙にズレた結果が返ってきます。
この現象を「コンピュータのバグ」や「仕方のないこと」として放置するエンジニアと、「なぜそうなるのか」を理解し、適切なデータ型(BigDecimalなど)を使って意図通り 0.3 を導き出すエンジニアとでは、コードレビュー担当者が受ける印象は天と地ほどの差があります。
誤差を放置するコードが招く「サイレントな不具合」
さらに、丸め誤差が恐ろしいのは、多くの場合「サイレントな不具合(エラーを吐かずに静かに進行するバグ)」を引き起こす点です。
例えば、以下のようなロジックは非常に危険です。
// JavaやC#での危険な例 (double型)
double total = 0.1 + 0.2;
// total は 0.30000000000000004 になっている
if (total == 0.3) {
// この処理は永遠に実行されない!
System.out.println("0.3になりました");
}
このようなコードは、テストケースが甘いと簡単に見逃され、本番環境で「特定の条件分岐だけがなぜか実行されない」という深刻な障害の原因となります。そのため、誤差を見越したテスト設計や、そもそも誤差を発生させないデータ型を選定できるエンジニアは、信頼性の高いコードを書く人材として高く評価されるのです。
【図解】なぜ浮動小数点で誤差が生まれるのか?
しかし、そもそもなぜコンピュータは 0.1 のような単純な小数を正確に扱えないのでしょうか?
その理由は、私たちが普段使う「10進数」と、コンピュータが内部で使う「2進数」の根本的な違いにあります。
理由1:コンピュータは「2進数」しか理解できない
コンピュータはすべての数値を「0」と「1」の組み合わせ(2進数)で記憶します。整数、例えば 10 は 1010 ときれいに表現できます。
一方で、問題は小数です。私たちが10進数で「割り切れない」数(例えば 1 ÷ 3 = 0.333...)に遭遇するように、コンピュータも2進数で表現すると「割り切れない」数が存在します。
そして、10進数の 0.1 は、まさにその「2進数にすると割り切れない数」なのです。
- 10進数
0.1を2進数に変換すると →0.00011001100110011...(0011が無限に続く循環小数)
したがって、コンピュータは 0.1 を 0.000110011... のように途中で打ち切って、最も近い近似値として記憶するしかありません。この瞬間に、最初の「丸め誤差」が発生します。
理由2:数値の「桁数」に限界がある (IEEE 754規格)
さらに、コンピュータが数値を記憶できる容量(ビット数)には限界があります。この小数の記憶方法は、国際標準規格である「IEEE 754」によって定められています。(参考: IEEE 754 Standard for Floating-Point Arithmetic)
多くの言語で使われる double 型(倍精度浮動小数点数)は、64ビットの領域を以下のように分けて数値を表現します。
| 型 | 合計ビット | 符号部 | 指数部 | 仮数部 | 10進数での精度 |
|---|---|---|---|---|---|
| double (倍精度) | 64 bit | 1 bit | 11 bit | 52 bit | 約 15〜16 桁 |
| float (単精度) | 32 bit | 1 bit | 8 bit | 23 bit | 約 7 桁 |
重要なのは「仮数部」です。これが実質的な数値の桁数を決めます。double型では52ビット(10進数で約15〜16桁)の精度しかありません。
そのため、0.1 のような無限に続く2進数を、この52ビットに無理やり丸めて格納しようとします。その結果、0.1 は内部的に 0.1000000000000000055... のような非常に近い「近似値」として扱われます。これが、0.1 + 0.2 が 0.30000000000000004 になる現象の正体です。
特に、float 型は精度が約7桁しかなく、さらに大きな誤差を生む可能性があるため、安易な使用は厳禁です。
【NG例】コードレビューで指摘される典型的な誤差パターン
丸め誤差の仕組みを理解したところで、次にコードレビューで「この人は誤差を理解していないな」と判断されてしまう典型的なNGパターンを見ていきましょう。
パターン1:double / float 型での金額計算
最も深刻なNGパターンです。金融、会計、ECサイトの決済処理など、1円単位の正確性が求められる処理で double や float を使うことは、バグを意図的に埋め込むようなものです。
// レビューで即指摘されるNGコード
public class PaymentCalculator {
// NG: 金額をdoubleで定義している
public double calculateTax(double price) {
double taxRate = 0.1; // この時点で 0.1 ではない
return price * taxRate; // 誤差が拡大する
}
}
パターン2:ループ処理内での double 加算
誤差は、計算を繰り返すほど蓄積して拡大します。特にループ処理内で double を加算し続けるコードは、最終的に無視できないズレを生み出します。
// NG: 誤差が蓄積するコード
double total = 0.0;
for (int i = 0; i < 100; i++) {
total += 0.1; // 0.1 (の近似値) を100回足す
}
// total は 10.0 ではなく 9.999999999999998 になる
System.out.println(total);
例えば、これが物理シミュレーションやゲームの座標計算だった場合、わずかなズレが蓄積し、オブジェクトが壁をすり抜けたり、宇宙船が軌道を外れたりする原因となります。
パターン3:== での浮動小数点数の比較
前述の通り、total == 0.3 が false になる問題です。計算結果が誤差を含む可能性があるため、浮動小数点数同士を ==(イコール)で比較するのは厳禁です。
しかし、驚くことにこのコードは多くの初心者が書いてしまい、レビューで必ず指摘されるポイントとなっています。
【OK例】評価につながる実践的な誤差対策と「見せ方」
では、どうすればこれらの問題を解決できるのでしょうか?
したがって、ここからは「誤差を発生させない」ための具体的な対策と、レビュー評価を高める「コードの見せ方」を解説します。
対策1:金融計算では BigDecimal を絶対的に使用する (Java/Python)
金融計算や契約計算など、絶対的な精度が求められる場合、double や float を使ってはいけません。代わりに、10進数を正確に扱うために設計されたクラスを使用します。
- Java:
BigDecimalクラス - Python:
decimalモジュール - C#:
decimal型
特に重要なのは、BigDecimal の初期化方法です。
// Javaでの正しいBigDecimalの使い方
// NG: double型から初期化すると、誤差ごとコピーしてしまう
BigDecimal priceNg = new BigDecimal(0.1);
System.out.println("NG例: " + priceNg); // 0.1000000000000000055...
// OK: 必ず「文字列」コンストラクタで初期化する
BigDecimal priceOk = new BigDecimal("0.1");
BigDecimal taxRate = new BigDecimal("0.1");
BigDecimal tax = priceOk.multiply(taxRate); // 乗算
System.out.println("OK例: " + priceOk); // 0.1
System.out.println("計算結果: " + tax); // 0.01 (正確!)
この「new BigDecimal("0.1")(文字列コンストラクタ)を使う」という知識は、エンジニアの品質意識を示す重要なポイントです。
対策2:整数(int / long)で管理する設計
さらに、より高速かつシンプルに解決する方法として、「そもそも小数を使わない」という設計思想があります。
例えば、金額を扱う場合、「円」単位で double を使うのではなく、「銭」単位(1円 = 100銭)で long 型(整数)を使います。
// 整数で管理する設計パターン
long priceInSen = 1000 * 100; // 1000円を「100000銭」として持つ
long taxRateNumerator = 10; // 税率10%を「10」と「100」で持つ
long taxRateDenominator = 100;
long taxInSen = priceInSen * taxRateNumerator / taxRateDenominator; // 10000銭
// 最後に表示するときだけ小数に戻す
System.out.println("税額: " + (taxInSen / 100.0) + "円"); // 100.0円
この方法は、データベースのスキーマ設計にも関わります。DECIMAL 型や NUMERIC 型(10進数を正確に格納できる型)を使うか、あるいは BIGINT 型(大きな整数)で「銭」単位で持つかは、設計段階での重要な判断となります。
対策3:「誤差許容範囲(EPSILON)」による比較
一方で、物理シミュレーションやグラフィックス処理など、金融ほどの厳密さは不要だが、ある程度の精度で比較したい場合もあります。その場合、== の代わりに「許容できるごくわずかな差(EPSILON)」を使って比較します。
// double型を安全に比較する方法
final double EPSILON = 0.0000001; // 許容する誤差の範囲
double a = 0.1 + 0.2; // 0.30000000000000004
double b = 0.3;
if (Math.abs(a - b) < EPSILON) {
// 差がEPSILON(イプシロン)未満なら、ほぼ同値とみなす
System.out.println("aとbは同値です");
}
このように、意図的に「誤差許容値」を設定するコードは、double の特性を理解した上で書いているという、技術力の高さを示すサインとして評価されます。
対策4:丸め(Rounding)のルールを明示する
BigDecimal を使っていても、割り算などで無限小数が発生した場合、どこかで「丸める」必要があります。その際、「切り捨て」「切り上げ」「四捨五入」のどれを選ぶかは、ビジネス要件(仕様)によって決まります。
// Javaでの丸め処理の明示
BigDecimal value = new BigDecimal("10");
BigDecimal divisor = new BigDecimal("3"); // 10 ÷ 3 = 3.333...
// NG: 丸めルールを指定しないと例外が発生する
// BigDecimal resultNg = value.divide(divisor);
// OK: 仕様に基づき「小数点第2位まで、四捨五入(HALF_UP)」を明示
BigDecimal resultOk = value.divide(divisor, 2, RoundingMode.HALF_UP);
System.out.println(resultOk); // 3.33
この setScale() や divide() の引数で RoundingMode(丸めモード)を明示的に指定しているかどうかは、レビュー担当者が「仕様を正しくコードに反映できているか」を判断する重要な評価ポイントになります。(参考: Oracle Java SE Documentation - BigDecimal)
「見せ方」で差をつける:評価されるコメント・命名・テスト
しかし、いくら完璧な誤差対策をしていても、その「意図」がレビュー担当者に伝わらなければ、評価にはつながりません。そのため、「なぜこのコードが必要なのか」を伝える「見せ方」の工夫が極めて重要です。
1. コメントで「なぜ」を明示する
BigDecimal を使っている箇所や、EPSILON比較をしている箇所には、必ず「なぜ」それを使っているのかをコメントで残しましょう。
// 評価されるコメント例
public BigDecimal calculatePayment(BigDecimal price) {
// 金額計算における丸め誤差を回避するため、double型ではなくBigDecimalを使用
BigDecimal taxRate = new BigDecimal("0.1");
BigDecimal total = price.multiply(taxRate);
// 仕様書 3.1.2項に基づき、税額は小数点以下切り捨て(DOWN)とする
return total.setScale(0, RoundingMode.DOWN);
}
2. 変数名・メソッド名で「意図」を伝える
命名(ネーミング)は、コードの可読性を左右する最も重要な要素の一つです。
- 悪い例:
val1,amount,result - 良い例:
priceInCents(セント単位の整数だと伝わる),amountRounded(丸め処理後だと伝わる),compareWithEpsilon()(誤差許容比較だと伝わる)
3. プルリクエスト(PR)で「対策」をアピールする
さらに、コードレビューを依頼する際のプルリクエスト(PR)のサマリー欄は、あなたの品質意識をアピールする絶好の場です。
【PRの概要】
決済モジュールの改修を実施。
【行った対策】
従来 double 型で処理されていた税額計算において、丸め誤差による計算ズレの可能性があったため、すべて BigDecimal(文字列コンストラクタ使用)に置き換えました。
また、仕様書の丸めルールに基づき、RoundingMode.DOWN(切り捨て)を明示的に指定しています。
【テスト】
0.1 のような誤差が出やすい値を使った単体テストケースを追加し、意図通りの精度で計算できることを確認済みです。
このように、「何を問題視し(誤差)」「どう対策し(BigDecimal)」「どう検証したか(テスト)」を明確に示すことで、レビュー担当者は安心してマージでき、あなたへの信頼も格段に向上します。
まとめ:丸め誤差を制する者が、信頼を制する

したがって、丸め誤差の理解は、単なるマニアックな技術知識ではなく、ビジネスの根幹を支える「信頼」を生むための基礎スキルです。
一方で、「結果を正しく出す」ことと、「結果が正しいと伝わるように見せる」ことは、同じくらい重要です。レビュー担当者や上司が注目しているのは、あなたが誤差の危険性を正しく把握し、再現性のある(誰が読んでも意図がわかる)方法で解決できているか、という点に尽きます。
最後に、あなたが「品質志向のエンジニア」として評価されるために、常に意識すべきチェックポイントをまとめます。
- 浮動小数点の限界(
doubleの危険性)を理解しているか? - 金融計算に
BigDecimalや整数管理を適切に実装しているか? BigDecimalの初期化は「文字列コンストラクタ」を使っているか?- 丸めルール(
RoundingMode)を仕様に基づき明示できているか? - その意図や仕様を、コメント、命名、PRで説明できているか?
丸め誤差を正しく恐れ、それを正確なコードと「見せ方」で対策できる人は、レビューでも信頼される「品質志向のエンジニア」として、間違いなく一歩先に進むことができます。
ドライブラインは「未経験からエンジニア」を本気で応援します
ドライブラインでは、社会人を含む未経験者がエンジニアへキャリアチェンジできる実践的な育成カリキュラムを提供しています。設計・開発・テスト・運用まで、現場で通用するスキルを体系的に学べます。
実務直結カリキュラム
要件定義〜Git運用・レビューまで、実践形式で習得可能。
メンター伴走型サポート
現役エンジニアが1on1で学習とポートフォリオを支援。
SE志向の育成
PG止まりから脱却し、上流工程を担える人材へ。
転職・案件参画サポート
OJT・案件参画・キャリア設計まで一貫支援。
「未経験から本気でエンジニアを目指したい」あなたへ。まずはドライブラインの採用ページで詳細をご覧ください。