C#のdecimal型とは?doubleとの違い・精度・丸め・使いどころを初心者向けに徹底解説

はじめに

C#で小数を扱うとき、よく登場する型にdecimaldoublefloatがあります。どれも小数を扱える数値型ですが、用途や精度、処理速度には大きな違いがあります。

特にdecimal型は、金額、料金、税金、会計処理など「小数の誤差をできるだけ避けたい場面」でよく使われます。一方で、科学技術計算やグラフィック処理などではdoubleが使われることも多く、どちらを使うべきか迷う初心者も少なくありません。

この記事では、C#のdecimal型について、doubleとの違い、精度、丸め処理、使いどころ、基本的な書き方、よくあるエラーまで初心者向けにわかりやすく解説します。

1. C#のdecimal型とは?

1-1. decimal型は10進数を高精度に扱うための数値型

C#のdecimal型は、小数を高い精度で扱うための数値型です。

特に、10進数の小数を正確に扱いやすいという特徴があります。10進数とは、私たちが日常的に使っている「0.1」「1.25」「980.50」のような表現です。

たとえば、金額計算では次のような値を扱います。

C#
decimal price = 1200.50m;
decimal taxRate = 0.10m;
decimal total = price + (price * taxRate);

Console.WriteLine(total);

decimal型は、こうした金額や税率のような数値を扱う場面に向いています。

1-2. decimal型が使われる代表的な場面

decimal型は、次のような場面でよく使われます。

C#
decimal price = 1980m;        // 商品価格
decimal taxRate = 0.10m; // 消費税率
decimal discount = 0.15m; // 割引率
decimal balance = 50000.75m; // 残高

代表的な用途は、金額、料金、税金、売上、請求金額、会計データ、ポイント計算などです。

これらの処理では、わずかな誤差でも問題になることがあります。たとえば、請求金額が1円ずれる、税額が期待値と違う、合計金額が合わないといった不具合につながる可能性があります。

そのため、C#で金額計算を行う場合は、基本的にdoubleではなくdecimalを使うのが一般的です。

1-3. decimal型の基本的な書き方とmサフィックス

C#でdecimal型の小数リテラルを書くときは、数値の末尾にmまたはMを付けます。

C#
decimal amount = 123.45m;

この末尾のmを「サフィックス」と呼びます。mdecimal型の値であることを表します。

次のようにmを付けないと、C#では小数リテラルが基本的にdoubleとして扱われます。

C#
decimal amount = 123.45; // コンパイルエラー

123.45doubleとして解釈されるため、decimal型の変数にそのまま代入できません。正しくは次のように書きます。

C#
decimal amount = 123.45m;

整数の場合は、次のようにmを付けなくても代入できます。

C#
decimal count = 100;

ただし、小数をdecimalとして扱う場合は、必ずmを付けると覚えておきましょう。

1-4. decimal型の値の範囲と有効桁数

decimal型は、非常に高い精度で10進数を扱えます。

C#のdecimal型は、おおよそ±1.0 × 10^-28から±7.9 × 10^28までの範囲を表現できます。また、有効桁数はおよそ28〜29桁です。

たとえば、次のような細かい小数も扱えます。

C#
decimal value = 0.1234567890123456789012345678m;
Console.WriteLine(value);

ただし、decimalは「どんなに大きな数でも扱える型」ではありません。非常に大きな数や非常に小さな数を扱う科学技術計算では、doubleのほうが適している場合があります。

2. C#のdecimal型とdouble型の違い

2-1. decimalとdoubleの最大の違いは「10進数」か「2進数」か

decimaldoubleの最大の違いは、内部的な数値の表現方法です。

decimalは10進数の小数を扱いやすい形式です。一方、doubleは2進数の浮動小数点数として値を扱います。

コンピューターは内部的に2進数で計算します。そのため、doubleでは人間にとっては単純に見える0.10.2のような小数を、正確に表現できない場合があります。

たとえば、次のような計算です。

C#
double x = 0.1;
double y = 0.2;
double z = x + y;

Console.WriteLine(z);

期待する結果は0.3ですが、実際には0.30000000000000004のような値になることがあります。

一方、decimalを使うと、次のように10進数の小数をより期待に近い形で扱えます。

C#
decimal x = 0.1m;
decimal y = 0.2m;
decimal z = x + y;

Console.WriteLine(z); // 0.3

2-2. 精度の違い:decimalは金額計算に強い

decimalは有効桁数が約28〜29桁あり、金額のように小数点以下の正確さが重要な計算に向いています。

たとえば、金額に税率を掛ける計算を考えてみます。

C#
decimal price = 1000m;
decimal taxRate = 0.10m;
decimal tax = price * taxRate;

Console.WriteLine(tax); // 100.00ではなく100.0など表示形式によって変わる

decimalは10進数の値を扱いやすいため、金額計算で意図しない誤差が出にくくなります。

ただし、表示上の小数点以下の桁数は、計算結果そのものとは別の問題です。金額として「100.00」のように表示したい場合は、ToStringなどで表示形式を指定します。

C#
Console.WriteLine(tax.ToString("F2")); // 100.00

2-3. 表現できる範囲の違い:doubleは非常に大きな数に強い

decimalは精度に優れていますが、表現できる数値の範囲はdoubleほど広くありません。

doubleは非常に大きな数や非常に小さな数を扱うことに向いています。そのため、科学技術計算、物理計算、統計処理、座標計算、3Dグラフィックスなどではdoubleがよく使われます。

たとえば、次のような値はdoubleで扱われることが多いです。

C#
double distance = 1.23e100;
double smallValue = 1.23e-100;

一方、decimalは高精度な10進数計算に向いていますが、指数表記で極端に大きい値や小さい値を扱う用途にはあまり向いていません。

2-4. 処理速度の違い:doubleのほうが高速なことが多い

処理速度の面では、一般的にdoubleのほうがdecimalより高速です。

doubleは多くの環境でCPUによる浮動小数点演算に適しており、高速に計算できます。一方、decimalは10進数としての精度を重視するため、計算コストが高くなることがあります。

大量の数値計算を行う場合、たとえばシミュレーションや画像処理、ゲームの座標計算などでは、decimalを使うとパフォーマンスが悪くなる可能性があります。

つまり、精度を優先するならdecimal、速度や広い範囲を優先するならdoubleと考えるとわかりやすいです。

2-5. decimal・double・floatの比較表

主な用途有効桁数の目安特徴
decimal金額、税金、会計約28〜29桁10進数の小数に強く、金額計算向き
double科学技術計算、座標、統計約15〜17桁範囲が広く、処理が速い
floatゲーム、画像処理、メモリ節約約6〜9桁軽量だが精度は低め

floatdoubleよりも精度が低い代わりに、メモリ使用量を抑えられます。ただし、通常のC#アプリケーションで小数を扱う場合は、まずdecimaldoubleを検討することが多いです。

金額ならdecimal、一般的な小数計算や科学技術計算ならdouble、メモリ効率を重視する特殊なケースではfloatという使い分けが基本です。

3. decimal型の精度を初心者向けに解説

3-1. decimalはなぜ小数計算の誤差が出にくいのか

decimalが小数計算の誤差に強い理由は、10進数の値を扱うことを重視した型だからです。

私たちが日常で使う金額や税率は、ほとんどが10進数です。

C#
decimal price = 100.10m;
decimal taxRate = 0.10m;

このような値を扱う場合、decimaldoubleよりも人間の感覚に近い結果を得やすくなります。

たとえば、次の計算ではdecimalを使うと期待どおりの結果になりやすいです。

C#
decimal result = 0.1m + 0.2m;
Console.WriteLine(result); // 0.3

一方、doubleでは同じ計算でも誤差が見えることがあります。

3-2. doubleで誤差が出る具体例

doubleでよく紹介される例が、0.1 + 0.2の計算です。

C#
double result = 0.1 + 0.2;
Console.WriteLine(result);

環境や表示方法によって見え方は変わりますが、結果が完全な0.3ではなく、0.30000000000000004のように表示されることがあります。

これは、doubleが2進数で小数を表現しているためです。10進数では有限小数で表せる0.10.2でも、2進数では正確に表せないことがあります。

比較すると、次のようになります。

C#
double doubleResult = 0.1 + 0.2;
decimal decimalResult = 0.1m + 0.2m;

Console.WriteLine(doubleResult);
Console.WriteLine(decimalResult);

出力例は次のようになります。

C#
0.30000000000000004
0.3

この違いが、金額計算でdecimalが選ばれる大きな理由です。

3-3. decimalでも完全に万能ではない理由

decimalは小数計算の誤差が出にくい型ですが、完全に万能ではありません。

たとえば、割り切れない計算ではdecimalでも有限桁に収まらないため、どこかで丸めが必要になります。

C#
decimal result = 1m / 3m;
Console.WriteLine(result);

この結果は、0.3333333333333333333333333333のようになります。数学的には3が無限に続きますが、decimalで表現できる桁数には限界があるため、途中で丸められます。

つまり、decimalは「10進数の小数を正確に扱いやすい型」ですが、「すべての計算結果を完全に正確に表せる型」ではありません。

3-4. 小数点以下の桁数を意識すべきケース

decimalを使うときは、小数点以下の桁数を意識することが重要です。

特に金額計算では、次のようなルールが必要になることがあります。

C#
decimal amount = 1234.567m;
decimal rounded = Math.Round(amount, 2);

Console.WriteLine(rounded); // 1234.57

たとえば、円単位で扱うのか、銭単位で扱うのか、小数点以下2桁で扱うのかによって、丸める位置が変わります。

税金計算では、法律や業務ルールによって「小数点以下を切り捨て」「四捨五入」「切り上げ」などが決まっている場合があります。

そのため、decimalを使うだけで安心するのではなく、どのタイミングで何桁に丸めるのかを明確にすることが大切です。

4. decimal型の丸め処理

4-1. Math.Roundでdecimalを丸める方法

C#でdecimalを丸めるには、Math.Roundを使います。

C#
decimal value = 123.456m;
decimal rounded = Math.Round(value, 2);

Console.WriteLine(rounded); // 123.46

第1引数には丸めたい値、第2引数には小数点以下の桁数を指定します。

C#
Math.Round(123.456m, 2); // 123.46
Math.Round(123.456m, 1); // 123.5
Math.Round(123.456m, 0); // 123

金額を小数点以下2桁にしたい場合は、次のように書きます。

C#
decimal amount = 980.555m;
decimal roundedAmount = Math.Round(amount, 2);

Console.WriteLine(roundedAmount);

ただし、Math.Roundの標準の丸め方法には注意が必要です。C#では、ちょうど中間の値を丸めるときに「銀行丸め」と呼ばれる方法が使われることがあります。

4-2. 四捨五入・切り捨て・切り上げの違い

丸め処理には、主に四捨五入、切り捨て、切り上げがあります。

四捨五入は、指定した桁の次の桁が5以上なら上げ、5未満なら下げる方法です。

C#
decimal value = 123.456m;
decimal rounded = Math.Round(value, 2);

Console.WriteLine(rounded); // 123.46

切り捨ては、指定した桁より下を捨てる方法です。C#ではMath.Floorや計算式を使って実現できます。

C#
decimal value = 123.456m;
decimal truncated = Math.Floor(value * 100) / 100;

Console.WriteLine(truncated); // 123.45

切り上げは、指定した桁より下に値があれば上げる方法です。

C#
decimal value = 123.451m;
decimal ceiling = Math.Ceiling(value * 100) / 100;

Console.WriteLine(ceiling); // 123.46

ただし、Math.Floorは負の数では「より小さい方向」に丸めます。単純な切り捨てとは意味が変わる場合があるため、マイナスの金額を扱う場合は注意しましょう。

4-3. MidpointRoundingとは?

MidpointRoundingは、ちょうど中間の値をどのように丸めるかを指定するための列挙型です。

たとえば、1.5を整数に丸める場合、1にするのか、2にするのかというルールが必要です。

C#
decimal value = 2.5m;

Console.WriteLine(Math.Round(value, 0));

標準の丸めでは、期待していた「常に5以上なら上げる」という結果にならないことがあります。

明示的に一般的な四捨五入に近い動きを指定したい場合は、MidpointRounding.AwayFromZeroを使います。

C#
decimal value = 2.5m;
decimal rounded = Math.Round(value, 0, MidpointRounding.AwayFromZero);

Console.WriteLine(rounded); // 3

AwayFromZeroは、0から遠ざかる方向に丸めるという意味です。

C#
Console.WriteLine(Math.Round(2.5m, 0, MidpointRounding.AwayFromZero));  // 3
Console.WriteLine(Math.Round(-2.5m, 0, MidpointRounding.AwayFromZero)); // -3

4-4. 銀行丸めと一般的な四捨五入の違い

C#のMath.Roundで注意したいのが、銀行丸めです。

銀行丸めは、ちょうど中間の値を丸めるときに、最も近い偶数に丸める方法です。英語では「round to even」と呼ばれます。

たとえば、次のようになります。

C#
Console.WriteLine(Math.Round(1.5m)); // 2
Console.WriteLine(Math.Round(2.5m)); // 2
Console.WriteLine(Math.Round(3.5m)); // 4
Console.WriteLine(Math.Round(4.5m)); // 4

2.53ではなく2になる点に注意してください。これは、丸めた結果が偶数になるように処理されるためです。

一般的な四捨五入にしたい場合は、次のようにMidpointRounding.AwayFromZeroを指定します。

C#
Console.WriteLine(Math.Round(1.5m, MidpointRounding.AwayFromZero)); // 2
Console.WriteLine(Math.Round(2.5m, MidpointRounding.AwayFromZero)); // 3
Console.WriteLine(Math.Round(3.5m, MidpointRounding.AwayFromZero)); // 4
Console.WriteLine(Math.Round(4.5m, MidpointRounding.AwayFromZero)); // 5

金額計算では、業務ルールによって銀行丸めを使うのか、一般的な四捨五入を使うのかが変わります。必ず仕様を確認しましょう。

4-5. 金額計算で丸め処理を書くときの注意点

金額計算では、丸めるタイミングが非常に重要です。

たとえば、商品ごとに税額を丸めるのか、合計金額に対して税額を計算してから丸めるのかで、結果が変わることがあります。

C#
decimal item1 = 333m;
decimal item2 = 333m;
decimal taxRate = 0.10m;

decimal tax1 = Math.Round(item1 * taxRate, 0, MidpointRounding.AwayFromZero);
decimal tax2 = Math.Round(item2 * taxRate, 0, MidpointRounding.AwayFromZero);

decimal totalTaxByItem = tax1 + tax2;

decimal total = item1 + item2;
decimal totalTax = Math.Round(total * taxRate, 0, MidpointRounding.AwayFromZero);

Console.WriteLine(totalTaxByItem);
Console.WriteLine(totalTax);

このように、同じ税率でも丸める単位によって結果が異なる場合があります。

そのため、金額計算では次の点を明確にする必要があります。

確認項目
丸める単位商品ごと、明細ごと、合計ごと
丸める桁数円単位、小数点以下2桁
丸め方法四捨五入、切り捨て、切り上げ、銀行丸め
負の数の扱い返品、値引き、返金

decimalを使っていても、丸めルールが曖昧だと結果がずれる可能性があります。業務システムでは、計算式だけでなく丸め仕様もコードに明確に反映させることが大切です。

5. decimal型の使いどころ

5-1. 金額・料金・税金計算にはdecimalが向いている

C#で金額、料金、税金を計算する場合は、基本的にdecimalが向いています。

C#
decimal price = 1000m;
decimal taxRate = 0.10m;
decimal tax = price * taxRate;
decimal total = price + tax;

Console.WriteLine(total);

金額計算では、0.10.08のような10進数の小数を扱うことが多いため、doubleよりもdecimalのほうが適しています。

doubleを使うと、計算途中で小さな誤差が発生し、それが合計金額や請求金額に影響することがあります。

C#
double price = 0.1;
double total = price + price + price;

Console.WriteLine(total);

期待した結果とわずかに違う値が表示されることがあります。金額のように正確さが重要な値では、このような誤差を避けるためにdecimalを使います。

5-2. 売上・請求・会計システムでdecimalを使う理由

売上管理、請求管理、会計システムでは、数値の正確さが非常に重要です。

たとえば、次のようなデータはdecimalで扱うことが多いです。

C#
decimal salesAmount = 1500000m;
decimal invoiceAmount = 1650000m;
decimal paymentAmount = 100000m;
decimal balance = invoiceAmount - paymentAmount;

会計や請求では、1円の違いでも問題になることがあります。合計金額、消費税、源泉徴収、割引、手数料など、複数の計算を組み合わせる場合は特に注意が必要です。

decimalを使うことで、10進数の小数をより正確に扱いやすくなり、金額計算における誤差のリスクを減らせます。

5-3. 科学技術計算や座標計算ではdoubleが向いている

一方で、すべての小数計算にdecimalを使えばよいわけではありません。

科学技術計算、物理シミュレーション、統計処理、機械学習、3D座標、ゲーム開発などでは、doubleが向いていることが多いです。

C#
double x = 123.456;
double y = 789.012;
double distance = Math.Sqrt(x * x + y * y);

Console.WriteLine(distance);

Math.Sqrtのような数学関数はdoubleを返すものが多く、decimalではそのまま使いにくい場合があります。

また、科学技術計算では非常に大きな値や非常に小さな値を扱うことがあり、decimalよりもdoubleの広い範囲が役立ちます。

5-4. decimalを使わないほうがよいケース

decimalを使わないほうがよいケースもあります。

大量の数値計算を高速に行いたい場合、decimaldoubleより処理が遅くなる可能性があります。また、数学関数や外部ライブラリがdoubleを前提としている場合、decimalを使うと変換が増えてコードが複雑になることがあります。

たとえば、次のような処理ではdoubleが適していることが多いです。

C#
double angle = 45.0;
double radians = angle * Math.PI / 180.0;
double sinValue = Math.Sin(radians);

Console.WriteLine(sinValue);

Math.SinMath.Cosなどの三角関数はdoubleを使います。このような計算で無理にdecimalを使うメリットはあまりありません。

decimalは、正確な10進数計算が必要な場面で使う型です。速度、範囲、数学関数との相性を重視する場面では、doubleを選ぶほうが自然です。

6. decimal型の基本操作

6-1. decimal変数の宣言と代入

decimal型の変数は、次のように宣言します。

C#
decimal price;
price = 1000m;

宣言と代入を同時に行うこともできます。

C#
decimal price = 1000m;
decimal taxRate = 0.10m;
decimal amount = 123.45m;

小数を代入する場合は、末尾にmを付けます。

C#
decimal value = 1.23m;

整数の場合は、mを付けなくても代入できます。

C#
decimal count = 10;

ただし、コードの見やすさを重視して、金額を表す値には1000mのようにmを付けることもあります。

6-2. decimal同士の四則演算

decimal同士では、通常の四則演算ができます。

C#
decimal a = 10.5m;
decimal b = 2.0m;

decimal add = a + b;
decimal sub = a - b;
decimal mul = a * b;
decimal div = a / b;

Console.WriteLine(add); // 12.5
Console.WriteLine(sub); // 8.5
Console.WriteLine(mul); // 21.00
Console.WriteLine(div); // 5.25

金額計算でも同じように使えます。

C#
decimal price = 1200m;
decimal quantity = 3m;
decimal total = price * quantity;

Console.WriteLine(total); // 3600

数量が整数の場合は、intを使っても計算できます。

C#
decimal price = 1200m;
int quantity = 3;

decimal total = price * quantity;

Console.WriteLine(total); // 3600

decimalintの計算は可能ですが、decimaldoubleの計算はそのままではできません。

6-3. intやdoubleからdecimalへ変換する方法

intからdecimalへは、暗黙的に変換できます。

C#
int count = 10;
decimal value = count;

Console.WriteLine(value); // 10

一方、doubleからdecimalへは明示的な変換が必要です。

C#
double doubleValue = 123.45;
decimal decimalValue = (decimal)doubleValue;

Console.WriteLine(decimalValue);

また、Convert.ToDecimalを使う方法もあります。

C#
double doubleValue = 123.45;
decimal decimalValue = Convert.ToDecimal(doubleValue);

Console.WriteLine(decimalValue);

ただし、doubleはすでに誤差を含んでいる可能性があります。そのため、金額を扱う値を最初からdoubleで受け取ってしまうと、decimalに変換しても完全に期待通りにならないことがあります。

金額を扱う場合は、できるだけ最初からdecimalとして扱うのが理想です。

6-4. decimalからintやdoubleへ変換する方法

decimalからintへ変換する場合は、明示的なキャストが必要です。

C#
decimal value = 123.45m;
int intValue = (int)value;

Console.WriteLine(intValue); // 123

この場合、小数部分は切り捨てられます。四捨五入したい場合は、先にMath.Roundを使います。

C#
decimal value = 123.45m;
int intValue = (int)Math.Round(value, 0, MidpointRounding.AwayFromZero);

Console.WriteLine(intValue); // 123

decimalからdoubleへ変換する場合も、明示的なキャストを使えます。

C#
decimal decimalValue = 123.45m;
double doubleValue = (double)decimalValue;

Console.WriteLine(doubleValue);

ただし、doubleに変換すると、decimalで保持していた精度が失われる可能性があります。金額の最終結果をdoubleに変換する場合は注意しましょう。

6-5. decimal.Parseとdecimal.TryParseの使い方

文字列をdecimalに変換するには、decimal.Parseを使います。

C#
string text = "123.45";
decimal value = decimal.Parse(text);

Console.WriteLine(value); // 123.45

ただし、変換できない文字列を渡すと例外が発生します。

C#
string text = "abc";
decimal value = decimal.Parse(text); // 例外が発生

ユーザー入力を扱う場合は、decimal.TryParseを使うほうが安全です。

C#
string text = "123.45";

if (decimal.TryParse(text, out decimal value))
{
Console.WriteLine($"変換成功: {value}");
}
else
{
Console.WriteLine("変換できませんでした");
}

TryParseは、変換に成功した場合はtrue、失敗した場合はfalseを返します。入力フォームやCSV読み込みなど、正しくない値が入る可能性がある場面ではTryParseを使うのがおすすめです。

6-6. ToStringでdecimalを表示形式に変換する方法

decimalの値を表示用の文字列に変換するには、ToStringを使います。

C#
decimal amount = 1234.5m;

Console.WriteLine(amount.ToString()); // 1234.5
Console.WriteLine(amount.ToString("F2")); // 1234.50

"F2"は、小数点以下2桁で表示する指定です。

金額らしくカンマ区切りで表示したい場合は、次のように書けます。

C#
decimal amount = 1234567.8m;

Console.WriteLine(amount.ToString("N2")); // 1,234,567.80

通貨形式で表示したい場合は、"C"を使います。

C#
decimal amount = 1234.5m;

Console.WriteLine(amount.ToString("C"));

通貨記号や区切り文字は、実行環境のカルチャ設定によって変わります。日本円として明示したい場合は、カルチャを指定することもあります。

C#
using System.Globalization;

decimal amount = 1234.5m;
Console.WriteLine(amount.ToString("C", CultureInfo.GetCultureInfo("ja-JP")));

表示形式は、計算結果そのものを変えるわけではありません。計算用の値と表示用の文字列は分けて考えましょう。

7. decimal型でよくあるエラーと注意点

7-1. decimalに小数を代入するときはmを付ける

初心者がよく遭遇するエラーが、decimalに小数を代入するときにmを付け忘れることです。

C#
decimal price = 123.45; // エラー

このコードは、123.45doubleとして扱われるためエラーになります。

正しくは次のように書きます。

C#
decimal price = 123.45m;

mは小文字でも大文字でも構いません。

C#
decimal a = 123.45m;
decimal b = 123.45M;

一般的には小文字のmがよく使われます。

7-2. decimalとdoubleをそのまま計算できない理由

decimaldoubleは異なる型なので、そのまま計算できません。

C#
decimal a = 10.5m;
double b = 2.0;

var result = a + b; // エラー

C#では、decimaldoubleのどちらに合わせて計算すべきかを自動で判断しません。これは、変換によって精度が失われる可能性があるためです。

計算する場合は、どちらかの型に明示的に変換します。

C#
decimal a = 10.5m;
double b = 2.0;

decimal result = a + (decimal)b;
Console.WriteLine(result);

または、すべてdoubleにそろえます。

C#
decimal a = 10.5m;
double b = 2.0;

double result = (double)a + b;
Console.WriteLine(result);

金額計算であれば、基本的にはdecimalに統一するのがおすすめです。

7-3. 桁あふれが起きるケース

decimalには表現できる範囲があります。非常に大きな値を扱うと、桁あふれが起きる可能性があります。

C#
decimal max = decimal.MaxValue;
decimal result = max + 1m; // OverflowException

decimal.MaxValueは、decimalで表現できる最大値です。そこにさらに値を足そうとすると、範囲を超えてしまいます。

また、乗算でも桁あふれが起きることがあります。

C#
decimal a = decimal.MaxValue;
decimal b = 2m;

decimal result = a * b; // OverflowException

通常の金額計算では問題になりにくいですが、大きな集計値や長期間の累積計算を行う場合は注意が必要です。

7-4. 丸めるタイミングを間違えると計算結果がずれる

金額計算では、丸めるタイミングによって結果が変わることがあります。

たとえば、明細ごとに丸める場合と、合計後に丸める場合では結果が異なる可能性があります。

C#
decimal taxRate = 0.10m;

decimal item1 = 105.5m;
decimal item2 = 105.5m;

decimal taxByItem =
Math.Round(item1 * taxRate, 0, MidpointRounding.AwayFromZero) +
Math.Round(item2 * taxRate, 0, MidpointRounding.AwayFromZero);

decimal taxByTotal =
Math.Round((item1 + item2) * taxRate, 0, MidpointRounding.AwayFromZero);

Console.WriteLine(taxByItem);
Console.WriteLine(taxByTotal);

このような差は、消費税、割引、手数料、ポイント計算などで起こります。

重要なのは、「どのタイミングで丸めるか」を業務ルールとして明確にすることです。decimalを使っていても、丸めのタイミングが不適切だと、期待した結果にならないことがあります。

7-5. データベースのdecimal型との違いに注意する

C#のdecimalと、SQL ServerやMySQLなどのデータベースにあるdecimal型は、名前は似ていますが完全に同じものではありません。

データベースのdecimal型では、桁数と小数点以下の桁数を指定することが多いです。

SQL
DECIMAL(10, 2)

これは、全体で10桁、小数点以下2桁という意味です。

一方、C#のdecimalは、約28〜29桁の有効桁数を持つ数値型です。C#側で扱える値でも、データベース側の桁数設定に収まらなければ保存時にエラーになったり、丸められたりする可能性があります。

たとえば、C#では次の値を扱えても、

C#
decimal amount = 1234567890.12m;

データベース側がDECIMAL(10, 2)であれば、整数部分が大きすぎて保存できない可能性があります。

そのため、C#とデータベースを連携する場合は、C#のdecimalだけでなく、データベース側の桁数と小数点以下桁数も必ず確認しましょう。

8. decimal型の実践サンプル

8-1. 商品価格と消費税を計算するサンプル

商品価格に消費税を加算して税込価格を計算するサンプルです。

C#
decimal price = 1000m;
decimal taxRate = 0.10m;

decimal tax = price * taxRate;
decimal total = price + tax;

Console.WriteLine($"税抜価格: {price}円");
Console.WriteLine($"消費税: {tax}円");
Console.WriteLine($"税込価格: {total}円");

出力例は次のようになります。

C#
税抜価格: 1000
消費税: 100.00
税込価格: 1100.00

円単位で丸めたい場合は、Math.Roundを使います。

C#
decimal price = 1000m;
decimal taxRate = 0.10m;

decimal tax = Math.Round(price * taxRate, 0, MidpointRounding.AwayFromZero);
decimal total = price + tax;

Console.WriteLine($"消費税: {tax}円");
Console.WriteLine($"税込価格: {total}円");

8-2. 割引率を使って販売価格を計算するサンプル

次は、商品価格に割引率を適用して販売価格を計算するサンプルです。

C#
decimal originalPrice = 5000m;
decimal discountRate = 0.20m;

decimal discountAmount = originalPrice * discountRate;
decimal salePrice = originalPrice - discountAmount;

Console.WriteLine($"定価: {originalPrice}円");
Console.WriteLine($"割引額: {discountAmount}円");
Console.WriteLine($"販売価格: {salePrice}円");

小数点以下を丸める場合は、次のように書きます。

C#
decimal originalPrice = 4980m;
decimal discountRate = 0.15m;

decimal discountAmount = Math.Round(
originalPrice * discountRate,
0,
MidpointRounding.AwayFromZero
);

decimal salePrice = originalPrice - discountAmount;

Console.WriteLine($"割引額: {discountAmount}円");
Console.WriteLine($"販売価格: {salePrice}円");

割引計算でも、割引額を先に丸めるのか、最終価格を丸めるのかによって結果が変わることがあります。実際のシステムでは、仕様に合わせて丸めるタイミングを決めましょう。

8-3. 小数点以下2桁で金額を表示するサンプル

金額を小数点以下2桁で表示したい場合は、ToString("F2")を使います。

C#
decimal amount = 1234.5m;

Console.WriteLine(amount.ToString("F2"));

出力は次のようになります。

C#
1234.50

カンマ区切りで表示したい場合は、ToString("N2")を使います。

C#
decimal amount = 1234567.8m;

Console.WriteLine(amount.ToString("N2"));

出力は次のようになります。

C#
1,234,567.80

日本円の通貨形式で表示する場合は、次のように書けます。

C#
using System.Globalization;

decimal amount = 1234567.8m;

string text = amount.ToString("C", CultureInfo.GetCultureInfo("ja-JP"));
Console.WriteLine(text);

表示形式は、画面表示や帳票出力では重要です。ただし、表示用に変換した文字列を再び計算に使うのは避け、計算はdecimalのまま行うのが基本です。

8-4. 入力された文字列をdecimalに変換するサンプル

ユーザーが入力した文字列をdecimalに変換するサンプルです。

C#
Console.Write("金額を入力してください: ");
string? input = Console.ReadLine();

if (decimal.TryParse(input, out decimal amount))
{
Console.WriteLine($"入力された金額: {amount}円");
}
else
{
Console.WriteLine("正しい金額を入力してください。");
}

decimal.Parseではなくdecimal.TryParseを使うことで、不正な入力があってもプログラムが落ちにくくなります。

たとえば、ユーザーがabcのような文字列を入力しても、TryParseならfalseを返してエラー処理に進めます。

実務では、入力値の検証も重要です。

C#
if (decimal.TryParse(input, out decimal amount) && amount >= 0)
{
Console.WriteLine($"有効な金額: {amount}円");
}
else
{
Console.WriteLine("0以上の金額を入力してください。");
}

金額入力では、数値に変換できるかだけでなく、マイナスを許可するか、上限金額を設けるか、小数点以下何桁まで許可するかも検討しましょう。

9. decimal型に関するよくある質問

9-1. C#で金額計算にはdecimalとdoubleのどちらを使うべき?

C#で金額計算をする場合は、基本的にdecimalを使うべきです。

doubleは2進数の浮動小数点数であり、0.10.2のような10進数の小数を正確に表現できない場合があります。そのため、金額計算では小さな誤差が発生する可能性があります。

一方、decimalは10進数の小数を扱いやすく、金額、税金、請求、会計処理に向いています。

C#
decimal price = 1000m;
decimal taxRate = 0.10m;
decimal total = price + price * taxRate;

ただし、科学技術計算や座標計算などではdoubleが向いていることもあります。用途に応じて使い分けましょう。

9-2. decimal型にmを付けないとどうなる?

小数リテラルにmを付けないと、C#では基本的にdoubleとして扱われます。

そのため、次のコードはエラーになります。

C#
decimal value = 1.23; // エラー

正しくは、末尾にmを付けます。

C#
decimal value = 1.23m;

mdecimal型の値であることを示すサフィックスです。小数をdecimalとして書くときは、必ずmを付けると覚えておきましょう。

9-3. decimal型は誤差がまったく出ない?

decimal型は、10進数の小数を扱う場面では誤差が出にくい型です。しかし、誤差がまったく出ないわけではありません。

たとえば、1 / 3のように割り切れない計算では、decimalでも有限の桁数に収める必要があります。

C#
decimal result = 1m / 3m;
Console.WriteLine(result);

結果は、3が無限に続くわけではなく、decimalが表現できる範囲で丸められます。

つまり、decimalは「金額などの10進数計算に強い型」ですが、「すべての数学的な値を完全に表現できる型」ではありません。

9-4. decimal型はパフォーマンスが悪い?

decimalは、一般的にdoubleよりも計算が遅くなることがあります。

これは、decimalが10進数の精度を重視した型であり、処理コストが高くなる場合があるためです。

ただし、通常の業務アプリケーションで金額計算を行う程度であれば、decimalの処理速度が大きな問題になることは多くありません。正確さが重要な場面では、速度よりも計算結果の信頼性を優先してdecimalを使うべきです。

一方で、何百万回、何千万回もの数値計算を行うシミュレーションや画像処理などでは、doubleを使ったほうが適している場合があります。

9-5. decimal型とデータベースのdecimalは同じ?

C#のdecimal型とデータベースのdecimal型は、似ていますが完全に同じではありません。

C#のdecimalは、約28〜29桁の有効桁数を持つ数値型です。一方、データベースのdecimal型は、DECIMAL(10, 2)のように桁数と小数点以下の桁数を指定することが多いです。

SQL
DECIMAL(10, 2)

この場合、全体で10桁、小数点以下2桁まで保存できます。

C#側では扱える値でも、データベース側の桁数設定を超えると保存できない場合があります。

C#
decimal amount = 1234567890.12m;

この値をDECIMAL(10, 2)に保存しようとすると、整数部分の桁数が大きすぎて問題になる可能性があります。

C#とデータベースを連携する場合は、両方の桁数、小数点以下桁数、丸めルールをそろえることが重要です。

まとめ

C#のdecimal型は、10進数の小数を高い精度で扱うための数値型です。特に、金額、税金、料金、請求、会計など、誤差をできるだけ避けたい計算に向いています。

doubleは2進数の浮動小数点数であり、広い範囲の数値を高速に扱える一方、0.1 + 0.2のような計算で誤差が見えることがあります。そのため、金額計算ではdoubleではなくdecimalを使うのが基本です。

ただし、decimalも万能ではありません。割り切れない計算では丸めが必要になり、非常に大きな数や高速な科学技術計算には向かない場合があります。また、Math.Roundを使う際は、銀行丸めと一般的な四捨五入の違いにも注意が必要です。

decimalを正しく使うためのポイントは、次のとおりです。

ポイント内容
小数にはmを付けるdecimal value = 1.23m;
金額計算にはdecimalを使う税金、料金、請求、会計に向いている
doubleと混在させない型を統一して計算する
丸め方法を明確にする四捨五入、切り捨て、切り上げを決める
丸めるタイミングに注意する明細ごとか合計後かで結果が変わる
DBの桁数と合わせるDECIMAL(10, 2)などの設定を確認する

C#で小数を扱うときは、「金額ならdecimal」「科学技術計算ならdouble」という基本を押さえると、型選びで迷いにくくなります。decimalの特徴、精度、丸め処理、使いどころを理解して、正確で安全な数値計算を実装しましょう。