C# decimal完全ガイド:doubleとの違い・計算誤差・丸め・型変換を初心者向けに解説

はじめに

C#で小数を扱うとき、よく登場する型にdecimaldoublefloatがあります。どれも小数を扱える型ですが、用途や精度、計算結果の性質は大きく異なります。

特に、金額計算や税金計算、請求処理などではdecimalを使うのが一般的です。一方で、座標計算、物理シミュレーション、統計処理、機械学習などではdoubleが使われることも多くあります。

この記事では、C#のdecimalについて、初心者にもわかりやすく解説します。doubleとの違い、計算誤差、丸め処理、型変換、フォーマット、よくあるエラーまで、実務で迷いやすいポイントをまとめて理解できる内容です。

1. C#のdecimalとは?初心者が最初に押さえるべき基本

1-1. decimal型の役割:小数を正確に扱いたい場面で使う型

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

特に、10進数としての小数を正確に扱いたい場面で使われます。

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

Console.WriteLine(total);

decimalは、doublefloatよりも金額計算に向いています。理由は、decimalが10進数ベースの値を扱うことを目的としているためです。

たとえば、0.10.01のような値は、金額や税率で頻繁に登場します。これらをできるだけ正確に扱いたい場合、decimalが適しています。

1-2. decimalがよく使われるケース:金額・税率・利率・請求処理

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

C#
decimal productPrice = 1980m;
decimal taxRate = 0.10m;
decimal interestRate = 0.035m;
decimal invoiceAmount = 25000m;

代表的な用途は、金額計算です。

商品価格、消費税、送料、割引額、請求金額、支払金額、利率、手数料など、数値の誤差が問題になりやすい処理ではdecimalがよく選ばれます。

たとえば、ECサイトで合計金額を計算する場合、わずかな誤差でも請求金額のズレにつながる可能性があります。

C#
decimal price = 1000m;
decimal discountRate = 0.15m;

decimal discount = price * discountRate;
decimal discountedPrice = price - discount;

Console.WriteLine(discountedPrice); // 850.00に相当する値

会計システムや販売管理システムでは、doubleよりもdecimalを使う設計が一般的です。

1-3. decimalの宣言方法と基本構文

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

C#
decimal amount = 1000m;
decimal rate = 0.08m;
decimal price = 1234.56m;

ポイントは、数値リテラルの末尾にmまたはMを付けることです。

C#
decimal value1 = 10.5m;
decimal value2 = 10.5M;

mMはどちらも同じ意味ですが、実務では小文字のmがよく使われます。

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

C#
decimal count = 10;

これは、intからdecimalへの暗黙的な変換が許可されているためです。

一方、小数リテラルをdecimalに代入する場合は、基本的にmが必要です。

C#
decimal amount = 10.5m;

1-4. decimalリテラルに「m」「M」を付ける理由

C#では、10.5のような小数リテラルは、何も指定しない場合doubleとして扱われます。

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

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

10.5double型の値として解釈されるため、decimalに暗黙的に代入できません。

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

C#
decimal value = 10.5m;

mを付けることで、この小数リテラルはdecimal型の値として扱われます。

C#
decimal taxRate = 0.10m;
decimal price = 1980.99m;
decimal fee = 250.5m;

初心者がよくつまずくポイントなので、decimalで小数を書くときは「末尾にmを付ける」と覚えておきましょう。

1-5. decimalで表せる範囲・有効桁数・メモリサイズ

decimalは、非常に大きな数値と小さな数値を扱えます。

目安として、decimalは約28〜29桁の有効桁数を持ちます。メモリサイズは128ビット、つまり16バイトです。

C#
Console.WriteLine(decimal.MaxValue);
Console.WriteLine(decimal.MinValue);

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

79228162514264337593543950335
-79228162514264337593543950335

decimaldoubleよりも表現できる範囲は狭いですが、10進数の精度を重視した型です。

つまり、decimalは「とても巨大な範囲を扱う型」というより、「金額や小数を正確に扱いたいときの型」と考えると理解しやすいです。

2. decimalとdouble・floatの違い

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

C#で小数を扱う主な型には、decimaldoublefloatがあります。

サイズ有効桁数の目安主な用途特徴
float32ビット約6〜9桁軽量な数値計算、ゲームなどメモリ効率がよいが精度は低め
double64ビット約15〜17桁科学技術計算、座標、統計高速で広い範囲を扱える
decimal128ビット約28〜29桁金額、税率、会計処理10進数の精度を重視

floatdoubleは2進数ベースの浮動小数点型です。一方、decimalは10進数の小数を扱いやすいように設計されています。

2-2. decimalは10進数、double・floatは2進数ベースで扱われる

doublefloatは、コンピューターが扱いやすい2進数ベースで小数を表現します。

しかし、私たちが普段使う0.10.2のような10進数の小数は、2進数では正確に表せないことがあります。

そのため、doubleでは次のような結果になることがあります。

C#
double a = 0.1;
double b = 0.2;

Console.WriteLine(a + b);

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

0.30000000000000004

一方、decimalでは次のように書けます。

C#
decimal a = 0.1m;
decimal b = 0.2m;

Console.WriteLine(a + b);

出力は期待どおりです。

0.3

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

2-3. 精度の違い:0.1を正確に表せるかどうか

decimaldoubleの違いを理解するうえで重要なのが、0.1をどう扱うかです。

doubleでは、0.1を内部的に完全には正確に表せません。近似値として保持されます。

C#
double d = 0.1;
Console.WriteLine(d);

表示上は0.1に見えることがありますが、内部的には完全な0.1ではありません。

一方、decimalでは、10進数の0.1を正確に表現できます。

C#
decimal m = 0.1m;
Console.WriteLine(m);

金額計算では、0.1円、0.01円、税率0.10などの10進数が頻繁に登場します。そのため、decimalのほうが適しています。

2-4. 計算速度の違い:パフォーマンス重視ならdoubleが向く場面

decimalは精度に優れていますが、計算速度はdoubleより遅くなる傾向があります。

doubleは多くのCPUで高速に処理しやすい形式です。一方、decimalは10進数の正確性を重視するため、処理コストが高くなりやすいです。

たとえば、次のような大量の数値計算ではdoubleが向いている場合があります。

C#
double x = 1.234;
double y = 5.678;
double result = x * y;

物理演算、3Dグラフィックス、統計処理、機械学習、シミュレーションなどでは、多少の丸め誤差よりも処理速度や表現範囲が重要になることがあります。

そのような場面では、decimalよりdoubleのほうが適しています。

2-5. 使い分けの目安:金額はdecimal、科学技術計算はdouble

実務では、次のように使い分けると判断しやすくなります。

金額、税率、利率、請求金額、会計処理にはdecimalを使います。

C#
decimal price = 1200m;
decimal tax = price * 0.10m;

座標、距離、速度、角度、統計、科学技術計算にはdoubleを使うことが多いです。

C#
double distance = 123.456;
double speed = 9.81;

floatは、メモリ使用量を抑えたい場合や、ゲーム開発などで大量の数値を扱う場合に使われます。

ただし、通常の業務アプリケーションでは、floatよりもdoubleまたはdecimalを使うことが多いです。

3. decimalを使うべき場面・使わないほうがよい場面

3-1. decimalを使うべき代表例

decimalを使うべき代表的な場面は、金額や数量など、10進数としての正確性が重要な処理です。

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

具体的には、次のような処理で使われます。

用途
金額計算商品価格、請求金額、支払金額
税金計算消費税、源泉徴収、軽減税率
会計処理売上、仕入、経費、利益
金融処理利率、手数料、残高
業務システム見積、請求、精算、給与

これらの処理では、わずかな誤差でも問題になることがあります。

たとえば、1件あたりは小さな誤差でも、数千件、数万件のデータを集計すると大きな差になる可能性があります。

3-2. doubleを使ったほうがよい代表例

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

次のような用途ではdoubleが適しています。

C#
double radius = 10.0;
double area = Math.PI * radius * radius;

doubleが向いている代表例は次のとおりです。

用途
科学技術計算物理、化学、数学
座標計算2D座標、3D座標、距離
統計処理平均、分散、標準偏差
グラフィックス回転、拡大縮小、角度
シミュレーション速度、加速度、確率

これらの分野では、非常に広い範囲の値を扱うことや、高速に大量計算することが重視されます。

そのため、decimalよりdoubleのほうが実用的な場合があります。

3-3. 「小数だから全部decimal」は正しいのか

「小数を扱うなら全部decimalにすればよい」と考えるのは、少し単純すぎます。

decimalは金額計算には強いですが、計算速度や表現範囲ではdoubleに劣る場面があります。

たとえば、三角関数を使う計算では、Math.SinMath.Cosなどの多くの数学関数がdoubleを引数に取ります。

C#
double angle = Math.PI / 4;
double sin = Math.Sin(angle);

このような処理で無理にdecimalを使うと、かえって型変換が増えてコードが複雑になることがあります。

decimalは「小数全般に使う型」ではなく、「10進数としての正確性が重要な小数に使う型」と考えるのが適切です。

3-4. 金額計算でdoubleを使うと起きやすい問題

金額計算でdoubleを使うと、見た目には小さな誤差でも、実務上の問題につながることがあります。

C#
double price = 0.1;
double total = price * 3;

Console.WriteLine(total);

出力例は次のようになることがあります。

0.30000000000000004

このような値をそのまま請求金額や集計結果に使うと、期待した値と一致しない可能性があります。

たとえば、次のような比較も問題になりやすいです。

C#
double total = 0.1 + 0.2;

Console.WriteLine(total == 0.3); // Falseになる可能性がある

金額計算では「ほぼ同じ」ではなく「正確に一致する」ことが求められる場面があります。そのため、doubleではなくdecimalを使うべきです。

3-5. 業務システム・会計処理でdecimalが選ばれる理由

業務システムや会計処理では、計算結果の説明可能性が重要です。

たとえば、請求書に記載された金額が、画面表示や帳票、データベース上の金額と一致していなければ問題になります。

decimalを使うことで、10進数の金額を扱いやすくなり、double特有の小さな誤差を避けやすくなります。

C#
decimal unitPrice = 199.99m;
int quantity = 3;

decimal subtotal = unitPrice * quantity;

Console.WriteLine(subtotal); // 599.97

また、税額計算や端数処理では、丸めルールを明確に実装する必要があります。

C#
decimal tax = Math.Round(subtotal * 0.10m, 0, MidpointRounding.AwayFromZero);

このように、decimalは業務ロジックを安全に実装するうえで重要な型です。

4. decimalと計算誤差の仕組み

4-1. doubleで計算誤差が起きる理由

doubleで計算誤差が起きる主な理由は、2進数で小数を表現しているためです。

10進数の0.1は、人間にとっては単純な値です。しかし、2進数では有限桁で正確に表せません。

そのため、double0.1は内部的には近似値になります。

C#
double a = 0.1;
double b = 0.2;
double result = a + b;

Console.WriteLine(result);

出力例は次のとおりです。

0.30000000000000004

これはC#だけの問題ではなく、多くのプログラミング言語で起きる浮動小数点数の性質です。

4-2. decimalなら計算誤差が完全になくなるのか

decimalを使えば、doubleでよく見られる2進数由来の誤差は避けやすくなります。

C#
decimal a = 0.1m;
decimal b = 0.2m;
decimal result = a + b;

Console.WriteLine(result); // 0.3

ただし、decimalを使えばすべての誤差が完全になくなるわけではありません。

たとえば、割り切れない計算では、decimalでも有限桁で表現できない結果になります。

C#
decimal result = 1m / 3m;

Console.WriteLine(result);

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

0.3333333333333333333333333333

decimalには有効桁数の上限があるため、無限に続く小数を完全には表せません。

4-3. decimalでも割り切れない計算では丸めが必要

decimalでも、割り切れない計算には丸め処理が必要です。

たとえば、3人で金額を割り勘する場合を考えます。

C#
decimal total = 1000m;
decimal perPerson = total / 3m;

Console.WriteLine(perPerson);

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

333.33333333333333333333333333

実際の支払いでは、小数点以下をどう扱うか決める必要があります。

C#
decimal rounded = Math.Round(perPerson, 0, MidpointRounding.AwayFromZero);

Console.WriteLine(rounded); // 333

金額計算では、切り捨て、切り上げ、四捨五入、銀行丸めなど、業務ルールに応じた丸め方法を明確にすることが重要です。

4-4. 0.1 + 0.2の結果をdecimalとdoubleで比較

decimaldoubleの違いは、0.1 + 0.2の結果を見るとわかりやすいです。

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

Console.WriteLine(doubleResult);
Console.WriteLine(decimalResult);
Console.WriteLine(doubleResult == 0.3);
Console.WriteLine(decimalResult == 0.3m);

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

0.30000000000000004
0.3
False
True

doubleでは、計算結果が0.3と完全には一致しないことがあります。

一方、decimalでは、10進数の0.10.2を正確に扱えるため、期待どおり0.3になります。

この違いが、C#で金額計算にdecimalを使う大きな理由です。

4-5. decimalの誤差・桁落ち・オーバーフローに注意する

decimalは高精度ですが、万能ではありません。

まず、有効桁数には上限があります。非常に大きな数値同士の計算や、桁数の多い計算では、精度に注意が必要です。

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

// decimal c = a + b; // オーバーフローが発生する

decimal.MaxValueを超える計算を行うと、OverflowExceptionが発生します。

また、極端に大きな数値と小さな数値を組み合わせる場合、期待した細かい桁が保持されないこともあります。

C#
decimal large = 10000000000000000000000000000m;
decimal small = 0.01m;

decimal result = large + small;

Console.WriteLine(result);

decimalは高精度ですが、無限の精度を持つわけではありません。金額計算では、扱う桁数、丸め位置、集計順序にも注意しましょう。

5. decimalの丸め処理:Math.Roundの使い方

5-1. 小数点以下を指定して丸める基本構文

decimalの丸めには、Math.Roundを使います。

基本構文は次のとおりです。

C#
decimal result = Math.Round(value, digits);

digitsには、小数点以下何桁まで残すかを指定します。

C#
decimal value = 123.4567m;

decimal rounded = Math.Round(value, 2);

Console.WriteLine(rounded); // 123.46

小数点以下2桁に丸めたい場合は、2を指定します。

金額計算では、小数点以下0桁に丸めて円単位にしたり、小数点以下2桁に丸めて通貨単位にしたりします。

C#
decimal amount = 1234.567m;

decimal yen = Math.Round(amount, 0);
decimal twoDigits = Math.Round(amount, 2);

5-2. 小数第2位・第3位で丸めるサンプルコード

小数第2位まで表示したい場合は、Math.Round(value, 2)を使います。

C#
decimal value = 12.3456m;

decimal rounded2 = Math.Round(value, 2);
decimal rounded3 = Math.Round(value, 3);

Console.WriteLine(rounded2);
Console.WriteLine(rounded3);

出力例は次のとおりです。

12.35
12.346

注意点として、「小数第2位で丸める」という表現は、人によって意味があいまいになることがあります。

たとえば、12.3456を「小数点以下2桁にする」なら結果は12.35です。

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

一方、「小数第2位を見て整数に丸める」という意味なら、別の処理になります。

業務仕様では、「小数点以下何桁を残すのか」を明確にしましょう。

5-3. 四捨五入・銀行丸め・切り上げ・切り捨ての違い

丸め処理には、いくつかの種類があります。

丸め方法内容
四捨五入5以上なら上げる
銀行丸めちょうど中間の値を偶数側に丸める
切り上げ小数があれば上げる
切り捨て小数を捨てる
0方向への丸め正負に関係なく0に近づける

C#のMath.Roundは、指定方法によって丸めルールが変わります。

特に注意したいのは、Math.Roundの既定の丸めが一般的な四捨五入とは異なる場合があることです。

C#
Console.WriteLine(Math.Round(2.5m));
Console.WriteLine(Math.Round(3.5m));

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

2
4

これは、既定では銀行丸めに相当する丸めが使われるためです。銀行丸めでは、中間値を最も近い偶数に丸めます。

一般的な四捨五入に近い動作をさせたい場合は、MidpointRounding.AwayFromZeroを指定します。

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

5-4. MidpointRoundingの指定方法

MidpointRoundingを使うと、丸め方を明示できます。

C#
decimal value = 2.5m;

decimal result1 = Math.Round(value, 0, MidpointRounding.ToEven);
decimal result2 = Math.Round(value, 0, MidpointRounding.AwayFromZero);

Console.WriteLine(result1);
Console.WriteLine(result2);

出力は次のとおりです。

2
3

主な指定値は次のとおりです。

指定値意味
MidpointRounding.ToEven中間値を偶数側に丸める
MidpointRounding.AwayFromZero中間値を0から遠い方向に丸める
MidpointRounding.ToZero0に近づく方向へ丸める
MidpointRounding.ToPositiveInfinity正の無限大方向へ丸める
MidpointRounding.ToNegativeInfinity負の無限大方向へ丸める

業務システムでは、丸め方法を省略せずに明示することをおすすめします。

C#
decimal tax = Math.Round(123.456m, 0, MidpointRounding.AwayFromZero);

これにより、コードを読む人が「どの丸めルールを使っているのか」を理解しやすくなります。

5-5. 消費税・請求金額で丸め処理を行うときの注意点

消費税や請求金額の計算では、丸めるタイミングが非常に重要です。

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

C#
decimal price1 = 101m;
decimal price2 = 102m;
decimal taxRate = 0.10m;

decimal taxEach =
Math.Round(price1 * taxRate, 0, MidpointRounding.AwayFromZero) +
Math.Round(price2 * taxRate, 0, MidpointRounding.AwayFromZero);

decimal taxTotal =
Math.Round((price1 + price2) * taxRate, 0, MidpointRounding.AwayFromZero);

Console.WriteLine(taxEach);
Console.WriteLine(taxTotal);

商品単位で丸めるか、合計単位で丸めるかは、業務ルールや法令、契約条件、システム仕様によって決まります。

大切なのは、decimalを使うことだけではありません。

「どこで丸めるか」「何桁で丸めるか」「どの丸め方法を使うか」を明確にすることが重要です。

6. decimalの型変換とキャスト

6-1. intからdecimalへ変換する方法

intからdecimalへの変換は、暗黙的に行えます。

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

Console.WriteLine(value); // 10

整数値はdecimalで安全に表現できるため、明示的なキャストは不要です。

計算でも自然に使えます。

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

decimal subtotal = unitPrice * quantity;

Console.WriteLine(subtotal); // 3600

このように、価格はdecimal、数量はintという組み合わせはよく使われます。

6-2. decimalからintへ変換する方法

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

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

Console.WriteLine(result); // 123

この場合、小数部分は切り捨てられます。四捨五入ではありません。

四捨五入してからintにしたい場合は、Math.Roundを使います。

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

Console.WriteLine(result); // 123

123.5mであれば、次のようになります。

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

Console.WriteLine(result); // 124

また、decimalの値がintの範囲を超えている場合は、オーバーフローに注意が必要です。

6-3. doubleからdecimalへ変換するときの注意点

doubleからdecimalへは暗黙的に変換できません。明示的なキャストが必要です。

C#
double d = 0.1;
decimal m = (decimal)d;

Console.WriteLine(m);

ただし、doubleの時点ですでに近似値になっている可能性があります。

つまり、doubleからdecimalへ変換しても、元のdoubleが持っていた誤差が消えるわけではありません。

C#
double d = 0.1 + 0.2;
decimal m = (decimal)d;

Console.WriteLine(d);
Console.WriteLine(m);

金額計算では、最初からdecimalとして値を扱うことが重要です。

C#
decimal value = 0.1m + 0.2m;

外部APIやライブラリからdoubleで値を受け取る場合は、その値が金額として妥当か、どの時点で丸めるかを慎重に決めましょう。

6-4. decimalからdoubleへ変換するときの精度低下

decimalからdoubleへ変換することもできますが、精度が落ちる可能性があります。

C#
decimal m = 12345678901234567890.123456789m;
double d = (double)m;

Console.WriteLine(m);
Console.WriteLine(d);

doubledecimalより有効桁数が少ないため、桁数の多い値では情報が失われることがあります。

金額計算の結果をdoubleに変換して再計算するのは避けたほうが安全です。

ただし、グラフ描画ライブラリや数学関数など、doubleを要求するAPIに渡す必要がある場合もあります。

その場合は、表示用や分析用の変換として扱い、元の金額データはdecimalで保持するのがおすすめです。

6-5. Convert.ToDecimal・decimal.Parse・TryParseの使い分け

decimalへの変換には、いくつかの方法があります。

C#
decimal a = Convert.ToDecimal("123.45");
decimal b = decimal.Parse("123.45");

Console.WriteLine(a);
Console.WriteLine(b);

Convert.ToDecimalは、さまざまな型からdecimalへ変換できます。

C#
int i = 100;
double d = 123.45;
string s = "999.99";

decimal a = Convert.ToDecimal(i);
decimal b = Convert.ToDecimal(d);
decimal c = Convert.ToDecimal(s);

decimal.Parseは、文字列をdecimalに変換します。

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

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

C#
// decimal value = decimal.Parse("abc"); // FormatException

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

C#
string input = "123.45";

if (decimal.TryParse(input, out decimal value))
{
Console.WriteLine(value);
}
else
{
Console.WriteLine("数値として変換できません");
}

6-6. 文字列からdecimalへ安全に変換する方法

フォーム入力やCSV、テキストファイルから値を受け取る場合、decimal.TryParseを使うと安全です。

C#
string input = "1980.50";

if (decimal.TryParse(input, out decimal price))
{
Console.WriteLine($"価格: {price}");
}
else
{
Console.WriteLine("価格の形式が正しくありません");
}

通貨記号やカンマを含む文字列を扱う場合は、NumberStylesCultureInfoを指定します。

C#
using System.Globalization;

string input = "1,980.50";

if (decimal.TryParse(
input,
NumberStyles.Number,
CultureInfo.InvariantCulture,
out decimal value))
{
Console.WriteLine(value);
}

日本の形式を意識する場合は、CultureInfo.GetCultureInfo("ja-JP")を使うこともあります。

C#
using System.Globalization;

string input = "1,980";

if (decimal.TryParse(
input,
NumberStyles.Number,
CultureInfo.GetCultureInfo("ja-JP"),
out decimal value))
{
Console.WriteLine(value);
}

ユーザー入力では、空文字、全角文字、カンマ、通貨記号、スペースなどが混ざることがあります。例外で処理するより、TryParseで検証するほうが実務では扱いやすいです。

7. decimalの比較・演算・フォーマット

7-1. 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 unitPrice = 980m;
int quantity = 4;

decimal subtotal = unitPrice * quantity;

Console.WriteLine(subtotal); // 3920

割り算では、割り切れない場合に小数が長くなることがあります。

C#
decimal result = 10m / 3m;

Console.WriteLine(result);

必要に応じて丸め処理を行いましょう。

C#
decimal rounded = Math.Round(result, 2, MidpointRounding.AwayFromZero);

7-2. decimalの大小比較とEqualsの使い方

decimalは、比較演算子で大小比較できます。

C#
decimal a = 100m;
decimal b = 200m;

Console.WriteLine(a < b); // True
Console.WriteLine(a <= b); // True
Console.WriteLine(a > b); // False
Console.WriteLine(a == b); // False

同じ値かどうかは、==またはEqualsで比較できます。

C#
decimal x = 10.0m;
decimal y = 10.00m;

Console.WriteLine(x == y); // True
Console.WriteLine(x.Equals(y)); // True

decimalでは、10.0m10.00mは数値として同じ値です。

金額計算では、doubleのように微小誤差を考慮した比較が必要になる場面は少なくなります。

ただし、計算途中で丸めが必要なケースでは、比較前に丸めルールをそろえることが大切です。

C#
decimal a = Math.Round(100.005m, 2, MidpointRounding.AwayFromZero);
decimal b = 100.01m;

Console.WriteLine(a == b); // True

7-3. decimalを通貨形式で表示する方法

decimalを通貨形式で表示するには、ToString("C")を使います。

C#
decimal amount = 1234.56m;

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

日本語環境では、次のように表示されることがあります。

¥1,235

環境によって通貨記号や小数点以下の桁数は変わります。

カルチャを明示したい場合は、CultureInfoを使います。

C#
using System.Globalization;

decimal amount = 1234.56m;

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

Console.WriteLine(text);

米国形式で表示したい場合は、次のようにします。

C#
string text = amount.ToString("C", CultureInfo.GetCultureInfo("en-US"));

Console.WriteLine(text);

通貨表示は、ユーザーに見せる画面や帳票でよく使います。

7-4. 小数点以下の桁数を固定して表示する方法

小数点以下の桁数を固定して表示したい場合は、F書式指定子を使います。

C#
decimal value = 12.3m;

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

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

12.30

F2は、小数点以下2桁で表示するという意味です。

C#
decimal value = 12.3456m;

Console.WriteLine(value.ToString("F0")); // 12
Console.WriteLine(value.ToString("F2")); // 12.35
Console.WriteLine(value.ToString("F3")); // 12.346

表示上の丸めと、計算上の丸めは分けて考える必要があります。

C#
decimal value = 12.3456m;

string display = value.ToString("F2");
decimal rounded = Math.Round(value, 2, MidpointRounding.AwayFromZero);

ToString("F2")は表示用です。値そのものを丸めて保存したい場合は、Math.Roundを使いましょう。

7-5. ToString・書式指定子・カルチャ設定の注意点

decimal.ToStringでは、書式指定子とカルチャの影響を受けます。

C#
decimal amount = 1234.56m;

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

主な書式指定子は次のとおりです。

指定子意味
F2小数点以下2桁1234.56
N2桁区切りあり、小数点以下2桁1,234.56
C通貨形式¥1,235など
0.00カスタム形式1234.56

カルチャによって、小数点や桁区切り、通貨記号が変わります。

C#
using System.Globalization;

decimal amount = 1234.56m;

Console.WriteLine(amount.ToString("N2", CultureInfo.GetCultureInfo("ja-JP")));
Console.WriteLine(amount.ToString("N2", CultureInfo.GetCultureInfo("en-US")));
Console.WriteLine(amount.ToString("N2", CultureInfo.GetCultureInfo("de-DE")));

国や地域によって、1,234.56のように表示する場合もあれば、1.234,56のように表示する場合もあります。

データ保存やAPI連携では、カルチャの違いで不具合が起きないように注意しましょう。

8. decimalでよくあるエラーと解決方法

8-1. 「decimalにdoubleを代入できない」エラーの原因

初心者がよく見るエラーのひとつが、decimaldoubleを代入しようとするものです。

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

1.23double型のリテラルとして扱われます。そのため、decimalへ暗黙的に変換できません。

解決方法は、末尾にmを付けることです。

C#
decimal value = 1.23m;

または、明示的にキャストする方法もあります。

C#
decimal value = (decimal)1.23;

ただし、金額などを扱う場合は、最初から1.23mと書くほうが安全です。

8-2. 「リテラルにmを付け忘れる」ミス

decimalを使うときによくあるミスが、mの付け忘れです。

C#
decimal taxRate = 0.10; // エラー

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

C#
decimal taxRate = 0.10m;

複数の計算式でも同じです。

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

次のように書くと、0.10doubleになるためエラーになります。

C#
decimal price = 1000m;
// decimal tax = price * 0.10; // エラー

decimalの計算式では、数値リテラルにmを付ける習慣をつけましょう。

8-3. 暗黙的変換と明示的キャストの違い

C#では、安全に変換できる場合は暗黙的変換が許可されます。

C#
int i = 100;
decimal d = i;

これは、intの値をdecimalで表現できるためです。

一方、情報が失われる可能性がある変換では、明示的キャストが必要です。

C#
decimal d = 123.45m;
int i = (int)d;

Console.WriteLine(i); // 123

decimalからintへ変換すると、小数部分が失われます。

doubledecimalの変換も、明示的に行う必要があります。

C#
double x = 1.23;
decimal y = (decimal)x;

decimal a = 1.23m;
double b = (double)a;

型変換では、値の範囲、精度、小数部分の扱いに注意しましょう。

8-4. Parseで例外が発生するケース

decimal.Parseは、文字列をdecimalに変換します。

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

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

C#
// decimal value = decimal.Parse("abc"); // FormatException

空文字でもエラーになります。

C#
// decimal value = decimal.Parse(""); // FormatException

nullを渡した場合も問題になります。

C#
string input = null;
// decimal value = decimal.Parse(input); // ArgumentNullException

ユーザー入力を扱う場合は、ParseよりTryParseを使うのがおすすめです。

C#
string input = "123.45";

if (decimal.TryParse(input, out decimal value))
{
Console.WriteLine(value);
}
else
{
Console.WriteLine("数値ではありません");
}

8-5. null許容decimalを扱うときの注意点

decimalは値型なので、通常はnullを代入できません。

C#
// decimal amount = null; // エラー

nullを許可したい場合は、decimal?を使います。

C#
decimal? amount = null;

decimal?は、Nullable<decimal>の省略形です。

値があるかどうかは、HasValueで確認できます。

C#
decimal? amount = 100m;

if (amount.HasValue)
{
Console.WriteLine(amount.Value);
}

また、??演算子を使うと、nullの場合の既定値を指定できます。

C#
decimal? amount = null;

decimal actualAmount = amount ?? 0m;

Console.WriteLine(actualAmount); // 0

データベースの金額カラムがNULLを許可している場合や、入力が任意項目の場合には、decimal?を使うことがあります。

ただし、計算する前にnullの扱いを明確にしておく必要があります。

C#
decimal? price = null;
decimal quantity = 2m;

decimal total = (price ?? 0m) * quantity;

9. 実践サンプル:金額計算でdecimalを使う

9-1. 商品価格と数量から小計を計算する

ここでは、decimalを使った金額計算のサンプルを見ていきます。

まず、商品価格と数量から小計を計算します。

C#
decimal unitPrice = 1980m;
int quantity = 3;

decimal subtotal = unitPrice * quantity;

Console.WriteLine($"小計: {subtotal}円");

出力は次のとおりです。

小計: 5940円

単価はdecimal、数量はintで扱うのが自然です。

C#
decimal subtotal = unitPrice * quantity;

intdecimalに暗黙的に変換されるため、この計算は問題なく行えます。

9-2. 消費税を計算して丸める

次に、消費税を計算します。

C#
decimal subtotal = 5940m;
decimal taxRate = 0.10m;

decimal tax = subtotal * taxRate;

Console.WriteLine($"税額: {tax}円");

税額に小数が出る可能性がある場合は、丸め処理を行います。

C#
decimal subtotal = 999m;
decimal taxRate = 0.10m;

decimal tax = Math.Round(
subtotal * taxRate,
0,
MidpointRounding.AwayFromZero);

Console.WriteLine($"税額: {tax}円");

出力例は次のとおりです。

税額: 100円

ここでは、一般的な四捨五入に近い動作としてMidpointRounding.AwayFromZeroを指定しています。

ただし、実際の業務では、消費税の端数処理が「切り捨て」「切り上げ」「四捨五入」のどれなのかを仕様で確認する必要があります。

9-3. 合計金額を通貨形式で表示する

小計と税額を足して、合計金額を表示します。

C#
using System.Globalization;

decimal subtotal = 5940m;
decimal tax = 594m;
decimal total = subtotal + tax;

Console.WriteLine(total.ToString("C", CultureInfo.GetCultureInfo("ja-JP")));

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

¥6,534

通貨形式では、カルチャによって表示が変わります。

画面表示や帳票出力では、ToString("C")ToString("N0")などを使って、見やすい形式に整えます。

C#
Console.WriteLine(total.ToString("N0")); // 6,534

計算用の値と表示用の文字列は分けて扱いましょう。

C#
decimal total = 6534m;
string displayText = total.ToString("C", CultureInfo.GetCultureInfo("ja-JP"));

9-4. 入力値をdecimal.TryParseで検証する

ユーザーが入力した金額をdecimalに変換する場合は、decimal.TryParseを使います。

C#
string input = "1980";

if (decimal.TryParse(input, out decimal price))
{
Console.WriteLine($"入力された価格: {price}");
}
else
{
Console.WriteLine("価格を正しく入力してください");
}

カンマ付きの入力に対応したい場合は、NumberStylesを指定します。

C#
using System.Globalization;

string input = "1,980";

if (decimal.TryParse(
input,
NumberStyles.Number,
CultureInfo.GetCultureInfo("ja-JP"),
out decimal price))
{
Console.WriteLine($"入力された価格: {price}");
}
else
{
Console.WriteLine("価格を正しく入力してください");
}

ユーザー入力では、必ず不正な値が入る可能性を考えておきましょう。

C#
string input = "abc";

if (!decimal.TryParse(input, out decimal price))
{
Console.WriteLine("数値として変換できません");
}

9-5. doubleで書いた場合との結果比較

最後に、decimaldoubleで金額計算の結果を比較してみます。

C#
double doubleTotal = 0.1 + 0.2;
decimal decimalTotal = 0.1m + 0.2m;

Console.WriteLine(doubleTotal);
Console.WriteLine(decimalTotal);

出力例は次のとおりです。

0.30000000000000004
0.3

さらに比較してみます。

C#
Console.WriteLine(doubleTotal == 0.3);
Console.WriteLine(decimalTotal == 0.3m);

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

False
True

このように、doubleでは小数の誤差が比較結果に影響することがあります。

金額計算では、decimalを使うことで、10進数として自然な結果を得やすくなります。

実務で金額を扱う場合は、最初から最後までdecimalで計算することが重要です。

10. decimalに関するよくある質問

10-1. decimalとDecimalは違うのか

C#のdecimalSystem.Decimalは、基本的に同じものです。

C#
decimal a = 100m;
System.Decimal b = 100m;

Console.WriteLine(a == b); // True

decimalは、System.DecimalのC#におけるキーワードです。

同じように、intSystem.Int32doubleSystem.Doubleに対応します。

C#
int x = 10;
System.Int32 y = 10;

通常のC#コードでは、decimalと書くことが多いです。

C#
decimal amount = 1000m;

一方、リフレクションや型情報を扱う場面では、System.Decimalという名前を見ることがあります。

10-2. decimalとdoubleはどちらを使えばよいのか

金額、税金、利率、請求金額などを扱う場合はdecimalを使うのが基本です。

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

科学技術計算、座標計算、統計処理、物理演算などではdoubleを使うことが多いです。

C#
double x = 1.23;
double y = Math.Sin(x);

判断基準は、「10進数としての正確性が重要かどうか」です。

金額のように、0.01単位の正確性が重要な場合はdecimalを選びましょう。

大量の数値を高速に処理したい場合や、数学関数を多用する場合はdoubleが向いています。

10-3. decimalはなぜmを付けるのか

C#では、1.23のような小数リテラルは、何も付けないとdoubleとして扱われます。

そのため、decimalとして扱いたい場合は、末尾にmまたはMを付けます。

C#
decimal value = 1.23m;

mを付けないと、次のコードはエラーになります。

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

mは、C#コンパイラに「この数値はdecimal型です」と伝えるための記号です。

実務では小文字のmを使うことが多いです。

C#
decimal taxRate = 0.10m;
decimal discountRate = 0.15m;

10-4. decimalは小数点以下何桁まで扱えるのか

decimalは、約28〜29桁の有効桁数を扱えます。

ただし、「小数点以下が必ず28桁まで使える」という意味ではありません。

decimalは、整数部と小数部を含めた全体の有効桁数に上限があります。

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

整数部が長くなるほど、小数部に使える桁数は少なくなります。

金額計算では、通常は十分な精度がありますが、非常に大きな金額や細かい単位を同時に扱う場合は、桁数に注意しましょう。

10-5. decimalでも誤差は発生するのか

decimalは、doubleのような2進数由来の誤差を避けやすい型です。

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

しかし、割り切れない計算では、decimalでも完全な値を表せないことがあります。

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

出力は有限桁に丸められた値になります。

0.3333333333333333333333333333

そのため、decimalでも丸め処理は必要です。

特に金額計算では、計算結果を何桁で丸めるか、どのタイミングで丸めるかを明確に決める必要があります。

10-6. decimalはパフォーマンスが悪いのか

decimalは、doubleに比べると計算速度が遅くなる傾向があります。

これは、decimalが10進数の正確性を重視した型であり、処理コストが高くなりやすいためです。

ただし、一般的な業務システムの金額計算では、decimalの速度が問題になるケースは多くありません。

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

一方で、数百万回、数億回のループで大量の数値計算を行うような場合は、doubleのほうが適していることがあります。

重要なのは、速度だけで型を選ばないことです。

金額計算では正確性が重要なのでdecimal、科学技術計算や大量の数値処理ではdoubleというように、用途に応じて選びましょう。

まとめ

C#のdecimalは、金額や税率、利率、請求金額など、10進数としての正確性が重要な場面で使う数値型です。

doublefloatも小数を扱えますが、2進数ベースの浮動小数点型であるため、0.1 + 0.2のような計算で誤差が見えることがあります。

一方、decimalは10進数の小数を扱いやすく、金額計算に向いています。

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

ただし、decimalも万能ではありません。割り切れない計算では丸めが必要ですし、有効桁数やオーバーフローにも注意が必要です。

丸め処理では、Math.RoundMidpointRoundingを使って、丸め方法を明示しましょう。

C#
decimal tax = Math.Round(99.9m, 0, MidpointRounding.AwayFromZero);

型変換では、doubleからdecimalへ変換しても、元のdoubleが持っていた誤差が消えるわけではありません。金額を扱う場合は、最初からdecimalとして値を持つことが大切です。

C#
decimal amount = 123.45m;

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

ポイント内容
金額計算decimalを使う
科学技術計算doubleを使うことが多い
小数リテラルmを付ける
丸め処理Math.Roundで明示する
ユーザー入力decimal.TryParseで安全に変換する
表示ToString("C")ToString("F2")を使う

decimaldoublefloatの違いを理解しておくと、C#での数値計算のトラブルを大きく減らせます。

特に業務システムや会計処理では、decimalを適切に使い、丸めルールや型変換の仕様を明確にすることが重要です。