C#で数値データを扱う際、多くの開発者が最初に直面する疑問の一つが「doubleとdecimalのどちらを使うべきか」という問題です。
どちらも浮動小数点を扱うデータ型ですが、その内部構造と設計思想は根本的に異なります。
誤った選択をすると、金融アプリケーションで致命的な計算誤差を招いたり、計算負荷の高いシミュレーションでパフォーマンスを著しく低下させたりするリスクがあります。
本記事では、2026年現在のC#開発環境において、これらの型をどのように使い分けるべきか、その基準を理論と実践の両面から詳しく解説します。
浮動小数点数(double)の特性と内部構造
double 型は、IEEE 754規格に準拠した「倍精度浮動小数点数」です。
これはコンピュータのハードウェア(CPU)が直接処理できるように設計された形式であり、現代のプログラミングにおいて最も汎用的な実数型と言えます。
バイナリベースの表現
double は数値を「2進数(バイナリ)」で管理します。
符号部(1ビット)、指数部(11ビット)、仮数部(52ビット)の計64ビットで構成されており、非常に広い範囲の数値を表現できます。
しかし、この「2進数による表現」が、人間が日常的に使う「10進数」との間で変換誤差を生む原因となります。
パフォーマンスの優位性
double の最大のメリットは、その処理速度にあります。
現代のほとんどのCPUは浮動小数点演算ユニット(FPU)を搭載しており、double の演算をハードウェアレベルで高速に処理します。
科学技術計算、物理シミュレーション、グラフィックス処理など、大量の計算を短時間でこなす必要がある分野ではdouble一択となります。
doubleで発生する誤差の例
以下のコードは、10進数では単純な計算が、double では直感に反する結果になることを示しています。
using System;
public class DoubleExample
{
public static void Main()
{
// 0.1を3回足す計算
double val1 = 0.1;
double val2 = 0.1;
double val3 = 0.1;
double result = val1 + val2 + val3;
// 0.3と比較する
Console.WriteLine($"Result: {result}");
Console.WriteLine($"Is result equal to 0.3? : {result == 0.3}");
}
}
Result: 0.30000000000000004
Is result equal to 0.3? : False
この結果からわかる通り、double では 0.1 という数値を正確に保持できず、わずかな誤差(丸め誤差)が蓄積されます。
10進浮動小数点数(decimal)の特性と内部構造
一方の decimal 型は、人間が扱う10進数の計算を正確に行うために設計された型です。
128ビット(16バイト)のメモリを消費し、double よりも高い精度を提供します。
10進数ベースの正確性
decimal は、数値を「10進数の整数部分」と「小数点の位置(スケーリングファクタ)」として保持します。
これにより、0.1 や 0.01 といった10進数の数値を、誤差なく完全に表現することが可能です。
これは会計システムや税金計算において、1円あるいは1セントの狂いも許されない場面で極めて重要です。
ソフトウェアによる処理
double とは異なり、decimal の演算は主にソフトウェア(.NET ランタイム)側で制御されます。
ハードウェアのサポートが直接的ではないため、計算速度は double に比べて大幅に遅くなる傾向があります。
一般的なベンチマークでは、double よりも10倍から20倍程度時間がかかる場合もあります。
decimalでの正確な計算例
先ほどの例を decimal で書き換えてみましょう。
数値リテラルには m サフィックスを付ける必要があります。
using System;
public class DecimalExample
{
public static void Main()
{
// decimal型で0.1を3回足す
decimal val1 = 0.1m;
decimal val2 = 0.1m;
decimal val3 = 0.1m;
decimal result = val1 + val2 + val3;
// 0.3mと比較する
Console.WriteLine($"Result: {result}");
Console.WriteLine($"Is result equal to 0.3? : {result == 0.3m}");
}
}
Result: 0.3
Is result equal to 0.3? : True
decimal を使用することで、人間が期待する通りの正確な計算結果が得られました。
パフォーマンスと精度のトレードオフ
開発者がどちらの型を選択するか判断する際、「パフォーマンス」と「精度」のどちらを優先すべきかを明確にする必要があります。
| 特徴 | double | decimal |
|---|---|---|
| サイズ | 8バイト (64ビット) | 16バイト (128ビット) |
| 精度 | 約15~17桁 | 28~29桁 |
| 表現可能な範囲 | 約 ±5.0 × 10^−324 ~ ±1.7 × 10^308 | 約 ±1.0 × 10^−28 ~ ±7.92 × 10^28 |
| 処理速度 | 非常に高速 (CPU処理) | 低速 (ソフトウェア処理) |
| 主な用途 | 科学計算、ゲーム、センサー値 | 金融、給与計算、在庫管理 |
| 誤差の種類 | 10進数表現時の丸め誤差 | 非常に少ない (10進数に最適化) |
メモリ消費の考慮
decimal は double の2倍のメモリを消費します。
少量の変数であれば問題になりませんが、数百万件のデータを配列やリストで保持する場合、メモリ使用量の差がアプリケーション全体のパフォーマンスやスケーラビリティに影響を与える可能性があります。
使い分けの具体的な基準
最適な選択をするためのガイドラインを、具体的なユースケース別に整理します。
decimal を選ぶべきケース
「お金」が絡む計算には、例外なく decimal を使用してください。
- 商品の価格計算、消費税の算出
- 銀行の利息計算、為替レートの変換
- 給与計算、経理システム
- 在庫の数量管理(小数単位が発生する場合)
これらのケースでは、わずかな計算誤差がコンプライアンス上の問題や顧客トラブルに発展する可能性があります。
速度よりも正確性が最優先されます。
double を選ぶべきケース
自然界の連続的な値を扱う場合や、計算速度が求められる場合には double を使用してください。
- 物理的な計測値(温度、湿度、重量、長さ)
- 科学技術計算(微積分、統計解析)
- 機械学習のモデル作成と推論
- ゲームエンジン内の座標計算や物理演算
- 音声データや画像データの信号処理
自然界のデータはもともと計測誤差を含んでいるため、double の内部で発生する微小な誤差は実用上の問題にならないことがほとんどです。
注意点:型混合とキャストの危険性
実務でよくあるミスは、double と decimal を混在させて計算してしまうことです。
C#ではこれら2つの型の間で暗黙的な変換は行われません。
明示的なキャストが必要になりますが、その際に情報が失われるリスクがあります。
キャストによる精度の喪失
decimal から double への変換は、精度の低下を招きます。
逆に double から decimal への変換は、既に double 側で発生している誤差をそのまま decimal に持ち込むことになります。
double dblVal = 0.1;
// doubleの時点で既に誤差があるため、decimalにしても正確な0.1にはならない
decimal decVal = (decimal)dblVal;
Console.WriteLine($"Double: {dblVal}");
Console.WriteLine($"Converted Decimal: {decVal}");
Double: 0.1
Converted Decimal: 0.1000000000000000055511151231
このように、一度 double に入れた値を decimal に戻しても、失われた精度は回復しません。 システムの入り口から出口まで、一貫した型を選択し続けることが重要です。
データベース設計との整合性
C#のコード内だけでなく、保存先であるデータベース(SQL Serverなど)との型の一致も考慮する必要があります。
- SQL Server の decimal / numeric 型
- C# の
decimalと対応します。金融データに適しています。
- C# の
- SQL Server の float / real 型
- C# の
double(floatはdouble、realはfloat型) と対応します。科学計算に適しています。
- C# の
Entity Framework Core などのORMを使用する場合、モデルのプロパティ定義とデータベースの型定義が不一致だと、予期せぬ丸め処理が発生し、原因不明のバグにつながることがあります。
丸め処理のベストプラクティス
decimal を使用していても、除算(割り算)などを行えば無限小数が発生し、最終的にはどこかで「丸め」が必要になります。
C#の Math.Round メソッドを使用する際は、MidpointRounding 指定に注意してください。
デフォルトでは「銀行型丸め(最も近い偶数への丸め)」が行われます。
日本で一般的な「四捨五入」を行いたい場合は、以下のように指定します。
decimal amount = 12.5m;
// 銀行型丸め(偶数に近い方へ)
decimal rounded1 = Math.Round(amount, 0, MidpointRounding.ToEven); // 結果: 12
// 四捨五入(0から遠い方へ)
decimal rounded2 = Math.Round(amount, 0, MidpointRounding.AwayFromZero); // 結果: 13
Console.WriteLine($"ToEven: {rounded1}");
Console.WriteLine($"AwayFromZero: {rounded2}");
計算誤差を防ぐためには、計算の途中経過では丸めを行わず、最後の表示や保存の直前で一度だけ丸めるのが鉄則です。
まとめ
C#における double と decimal の使い分けは、アプリケーションの信頼性とパフォーマンスを左右する重要な判断です。
- double は「速くて広い」が、10進数の計算には「誤差」が伴う。科学計算やグラフィックス、物理的な計測値に最適。
- decimal は「正確で緻密」だが、計算は「遅い」。お金や在庫数など、正確な10進数演算が求められるビジネスロジックに必須。
2026年現在の開発においても、ハードウェアの進化で計算速度は向上していますが、この「バイナリ浮動小数点」と「10進浮動小数点」の原理的な違いは変わっていません。
それぞれの型の性質を正しく理解し、プロジェクトの目的に応じて適切な型を選択することで、バグの少ない堅牢なシステムを構築することができます。
もし、どちらを使うべきか迷ったときは、「その数値が1円でもズレたら問題になるか?」を自問してみてください。
YESであれば、迷わず decimal を選択しましょう。
一方、膨大なシミュレーションを1秒でも早く終わらせる必要があるなら、double の活用を検討すべきです。
