decimal はなぜ丸め誤差が出にくいのか
C#で小数を扱うとき、よく出てくる型に double と decimal があります。
例えば、次のような話を聞いたことがあるかもしれません。
double は小数の誤差が出ることがある
decimal は金額計算に向いている
ここで疑問になるのが、
でも、コンピュータの内部は全部 0 と 1 なのでは?
それなのに、なぜ decimal は 10進数の小数を正確に扱えるの?
という点です。
この記事では、decimal がなぜ丸め誤差を起こしにくいのかを、double との違いから整理します。
コンピュータ内部はすべて2進数
まず大前提として、コンピュータの内部では、すべての情報が 0 と 1 で表されます。
整数も、文字も、画像も、小数も、最終的には 0 と 1 の並びです。
例えば、整数の 8 は2進数では次のように表せます。
8₁₀ = 1000₂
つまり、decimal も内部では当然 0 と 1 で保存されています。
ではなぜ、decimal は 0.1 や 0.8 のような10進数の小数を正確に扱えるのでしょうか。
ポイントは、
小数そのものを2進数の小数として保存しているわけではない
というところです。
double は2進数の小数として保存する
まず、double の仕組みを見てみます。
double は、ざっくり言うと次のような形で数値を保存します。
1.xxxxx × 2の何乗
つまり、2進数を基準にした小数として保存します。
例えば、0.5 は2進数で正確に表せます。
0.5₁₀ = 0.1₂
これは、
1 / 2
だからです。
同じように、0.25 も正確に表せます。
0.25₁₀ = 0.01₂
これは、
1 / 4
だからです。
つまり、分母が 2、4、8、16 のように、2の累乗で表せる小数は、2進数でも正確に表せます。
0.1 や 0.8 は2進数では無限に続く
一方、0.1 はどうでしょうか。
10進数ではとてもきれいな数です。
0.1
しかし、2進数にすると無限に続きます。
0.1₁₀ = 0.00011001100110011...₂
途中から 0011 が繰り返されます。
同じように、0.8 も2進数では有限になりません。
0.8₁₀ = 0.1100110011001100...₂
つまり、10進数ではきれいに書ける小数でも、2進数では無限小数になることがあります。
しかし、double は無限の桁を保存できません。
途中で丸める必要があります。
そのため、内部では本当の 0.1 ではなく、
0.1 にとても近い値
として保存されます。
これが、double で丸め誤差が発生する理由です。
decimal は「整数」と「小数点の位置」で保存する

では、decimal はどうしているのでしょうか。
decimal は、10進数の小数をそのまま2進数小数に変換して保存しているわけではありません。
考え方としては、
整数
+
小数点をどこに置くか
という形で保存します。
例えば、次の値を考えます。
123.45
decimal はこれを、内部的には次のように考えます。
12345
という整数を保存し、
小数点を2桁左に置く
という情報を持ちます。
つまり、
12345
↓
123.45
として扱います。
0.80 の場合
例えば、次の値を考えます。
decimal value = 0.80m;
この場合、考え方としては次のようになります。
80
という整数を保存し、
小数点を2桁左に置く
という情報を持ちます。
つまり、
80
↓
0.80
です。
ここで重要なのは、内部で
0.110011001100...
のような2進数小数に変換しているわけではない、ということです。
80 という整数なら、2進数でも正確に保存できます。
80₁₀ = 1010000₂
整数はコンピュータが得意とする形です。
そのため、0.80 は
80 という整数
+
小数点を2桁戻す情報
として、正確に扱えるのです。
decimal の内部イメージ
decimal は、内部的には大まかに次のような情報を持っています。
整数部分
符号
小数点の位置
イメージとしては、次のような形です。
+----------------------+
| 整数としての値 |
+----------------------+
| プラスかマイナスか |
+----------------------+
| 小数点を何桁戻すか |
+----------------------+
例えば、
123.45
なら、
整数としての値:12345
小数点の位置:2桁
です。
そして、
12345 ÷ 100 = 123.45
という意味になります。
同じように、
0.8
なら、
整数としての値:8
小数点の位置:1桁
です。
つまり、
8 ÷ 10 = 0.8
として扱います。
decimal の計算は整数計算に近い
例えば、次の計算を考えます。
decimal a = 0.80m;
decimal b = 0.15m;
decimal result = a + b;
Console.WriteLine(result);
人間の感覚では、
0.80 + 0.15 = 0.95
です。
decimal の内部イメージでは、次のように考えられます。
0.80 → 80、小数点2桁
0.15 → 15、小数点2桁
小数点の位置がそろっているので、
80 + 15 = 95
と整数として足し算できます。
そして最後に、
小数点を2桁左に置く
ので、
0.95
になります。
このように、decimal は10進数の小数を、整数と小数点位置の組み合わせとして扱うため、金額のような10進数の計算に向いています。
double と decimal の違い
double と decimal の違いを表にまとめると、次のようになります。
| 型 | 保存の考え方 | 得意な用途 |
|---|---|---|
| double | 2進数の小数として保存 | 科学技術計算、座標、物理計算など |
| decimal | 整数+小数点位置として保存 | 金額、会計、税率、単価計算など |
double は高速で、広い範囲の数値を扱えます。
一方、decimal は10進数の小数を正確に扱いやすいですが、double より処理は遅くなりやすいです。
decimal なら完全に誤差が出ないのか
ここで注意が必要です。
decimal は、10進数で表せる小数を正確に扱いやすい型です。
例えば、
0.1
0.8
0.01
123.45
のような値は、decimal に向いています。
しかし、どんな計算でも絶対に誤差が出ないわけではありません。
例えば、
1 ÷ 3
は10進数でも無限に続きます。
0.3333333333333333...
decimal にも保存できる桁数の限界があります。
そのため、1 / 3 のような計算では、どこかで丸めが必要になります。
つまり、
decimal は10進数の小数に強い
でも、無限小数まで完全に保存できるわけではない
と考えるとよいです。
金額計算では decimal を使う
金額計算では、基本的に decimal を使うのが適しています。
例えば、
decimal price = 1200m;
decimal taxRate = 0.10m;
decimal tax = price * taxRate;
Console.WriteLine(tax);
このように、金額や税率の計算では decimal を使うことで、10進数として自然な計算ができます。
ポイントは、数値の後ろに m を付けることです。
0.10m
この m は、C# に対して
これは decimal 型の値です
と伝えるための記号です。
もし m を付けずに、
decimal taxRate = 0.10;
と書くと、0.10 はまず double として扱われるため、エラーになります。
そのため、decimal の値を書くときは、次のようにします。
decimal taxRate = 0.10m;
double が悪いわけではない
ここまで読むと、
double は誤差が出るから使わないほうがいい
と思うかもしれません。
しかし、それは違います。
double は非常に重要な型です。
例えば、次のような場面では double がよく使われます。
3Dゲームの座標
物理シミュレーション
統計計算
グラフ描画
センサー値
画像処理
AIや機械学習
これらの分野では、金額計算のように「1円単位で完全に合っていること」よりも、
高速に計算できること
非常に大きい値や小さい値を扱えること
多少の誤差を許容できること
が重要になることがあります。
つまり、
金額計算なら decimal
科学技術計算や座標計算なら double
という使い分けが大切です。
まとめ
コンピュータ内部はすべて2進数です。
しかし、decimal は10進数の小数を、2進数の小数として直接保存しているわけではありません。
decimal は、
整数
+
小数点の位置
という形で数値を保存します。
例えば、
0.80
は、
80
+
小数点を2桁戻す
という形で扱います。
そのため、0.1 や 0.8 のような10進数の小数を、double より正確に扱うことができます。
ただし、decimal でも 1 ÷ 3 のような無限小数は完全には保存できません。
大切なのは、型の特徴を理解して使い分けることです。
double :2進数の小数として保存する。高速で科学技術計算向き。
decimal :整数+小数点位置で保存する。金額や会計計算向き。
decimal は、コンピュータ内部が2進数であることに逆らっているわけではありません。
2進数の世界の中で、10進数の小数を正確に表しやすいように、保存の仕組みを工夫しているのです。









ディスカッション
コメント一覧
まだ、コメントがありません