C#のMath.Round完全解説|小数点以下の丸め方・四捨五入にならない原因と対処法

はじめに

C#で小数点以下を丸めたいときによく使うのがMath.Roundです。たとえば、平均点を小数点第2位までにしたい、税込金額を整数にしたい、割合を小数第1位で表示したい、といった場面で登場します。

しかし、Math.Roundを使っていると「2.5が3にならない」「1.225を小数第2位に丸めたら期待と違う」「四捨五入のつもりだったのに結果がずれる」といった問題に遭遇することがあります。

その原因の多くは、C#のMath.Roundのデフォルトが一般的な四捨五入ではなく、MidpointRounding.ToEven、いわゆる銀行丸めになっていることです。また、double型の浮動小数点誤差も、丸め結果が期待と異なる原因になります。

この記事では、C#のMath.Roundの基本構文から、小数点以下の桁数指定、四捨五入にならない理由、MidpointRounding.AwayFromZeroを使った対処法、FloorCeilingTruncateとの違いまで、実務で迷いやすいポイントをまとめて解説します。

1. C#のMath.Roundとは?小数点以下を丸める基本

1-1. Math.Roundでできること

Math.Roundは、数値を指定した桁数に丸めるためのメソッドです。

主に次のような処理に使います。

C#
Math.Round(12.345);      // 整数に丸める
Math.Round(12.345, 2); // 小数点以下2桁に丸める

たとえば、12.345を小数点以下2桁に丸めると、結果は12.34または12.35になります。どちらになるかは、丸めモードや値の内部表現によって変わります。

Math.Roundは、単に「5以上なら切り上げ、4以下なら切り捨て」という処理ではありません。C#では、中央値、つまりちょうど0.5にあたる値の扱いをMidpointRoundingで指定できます。Microsoftの公式ドキュメントでも、Math.Roundは中央値の丸めに複数の規則をサポートしていると説明されています。Microsoft Learn

1-2. Math.Roundの基本構文

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

C#
Math.Round(数値);
Math.Round(数値, 小数点以下の桁数);
Math.Round(数値, 丸めモード);
Math.Round(数値, 小数点以下の桁数, 丸めモード);

実際のコード例です。

C#
double value = 12.345;

Console.WriteLine(Math.Round(value));
Console.WriteLine(Math.Round(value, 2));
Console.WriteLine(Math.Round(value, 2, MidpointRounding.AwayFromZero));

Math.Round(value)は整数に丸めます。
Math.Round(value, 2)は小数点以下2桁に丸めます。
Math.Round(value, 2, MidpointRounding.AwayFromZero)は、小数点以下2桁に、一般的な四捨五入に近いルールで丸めます。

1-3. 戻り値の型とdouble・decimalの違い

Math.Roundには、double型用とdecimal型用のオーバーロードがあります。

C#
double d = Math.Round(12.345);
decimal m = Math.Round(12.345m);

doubleを渡すと戻り値はdoubleです。
decimalを渡すと戻り値はdecimalです。

doubleは浮動小数点数で、科学技術計算や一般的な数値計算に向いています。一方、decimalは10進数の精度を重視する型で、金額計算や税計算などに向いています。

たとえば、金額を扱う場合は次のようにdecimalを使うのが基本です。

C#
decimal price = 1234.567m;
decimal rounded = Math.Round(price, 2, MidpointRounding.AwayFromZero);

decimalリテラルには末尾にmを付けます。12.345mのように書くことで、値がdecimalとして扱われます。

1-4. 小数点以下の桁数を指定するdigitsの使い方

Math.Roundの第2引数には、小数点以下の桁数を指定します。

C#
Math.Round(123.4567, 0); // 123
Math.Round(123.4567, 1); // 123.5
Math.Round(123.4567, 2); // 123.46
Math.Round(123.4567, 3); // 123.457

第2引数のdigitsは「残したい小数点以下の桁数」です。

注意したいのは、「小数点第2位で四捨五入」と「小数点以下2桁に丸める」は表現が少し紛らわしいことです。

たとえば、123.456を小数点以下2桁にする場合、見たい桁は小数第2位までです。そのため、第3位の6を見て丸め、結果は123.46になります。

C#
double value = 123.456;
double result = Math.Round(value, 2);

Console.WriteLine(result); // 123.46

1-5. Math.Roundの主なオーバーロード一覧

Math.Roundには複数のオーバーロードがあります。代表的なものは次のとおりです。

オーバーロード内容
Math.Round(double value)doubleを整数に丸める
Math.Round(double value, int digits)doubleを指定桁数に丸める
Math.Round(double value, MidpointRounding mode)doubleを指定した丸めモードで整数に丸める
Math.Round(double value, int digits, MidpointRounding mode)doubleを指定桁数・指定モードで丸める
Math.Round(decimal value)decimalを整数に丸める
Math.Round(decimal value, int decimals)decimalを指定桁数に丸める
Math.Round(decimal value, MidpointRounding mode)decimalを指定した丸めモードで整数に丸める
Math.Round(decimal value, int decimals, MidpointRounding mode)decimalを指定桁数・指定モードで丸める

MicrosoftのAPIリファレンスでも、double向けにはRound(Double)Round(Double, Int32)Round(Double, MidpointRounding)Round(Double, Int32, MidpointRounding)などが定義されています。Microsoft Learn

2. Math.Roundの基本的な使い方

2-1. 整数に丸める方法

整数に丸めたい場合は、第1引数だけを指定します。

C#
Console.WriteLine(Math.Round(12.3)); // 12
Console.WriteLine(Math.Round(12.6)); // 13

ただし、.5の扱いには注意が必要です。

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

このように、2.53ではなく2になることがあります。これはMath.RoundのデフォルトがMidpointRounding.ToEvenだからです。

2-2. 小数点第1位・第2位・第3位で丸める方法

小数点以下の桁数を指定する場合は、第2引数に桁数を指定します。

C#
double value = 123.45678;

Console.WriteLine(Math.Round(value, 1)); // 123.5
Console.WriteLine(Math.Round(value, 2)); // 123.46
Console.WriteLine(Math.Round(value, 3)); // 123.457

第2引数の意味は次のとおりです。

指定意味
0小数点以下0桁、つまり整数123
1小数点以下1桁123.5
2小数点以下2桁123.46
3小数点以下3桁123.457

「小数第2位で丸める」と言う場合、人によって「小数第2位を残す」のか「小数第2位を見て丸める」のか解釈が分かれることがあります。プログラム仕様では、「小数点以下2桁に丸める」のように明確に書くことが重要です。

2-3. decimal型の値を丸める方法

decimal型の値を丸める場合も、使い方はほぼ同じです。

C#
decimal value = 123.4567m;

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

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

C#
123.46

金額を扱う場合は、doubleではなくdecimalを使うのが一般的です。

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

Console.WriteLine(rounded); // 1100

decimalは10進数の値を扱いやすいため、消費税、請求金額、単価、割引額などの計算でよく使われます。

2-4. double型の値を丸める方法

double型でも同じようにMath.Roundを使えます。

C#
double value = 123.4567;

Console.WriteLine(Math.Round(value, 2)); // 123.46

ただし、doubleは2進数で小数を表現するため、10進数ではきれいに見える値でも内部的にはわずかな誤差を含む場合があります。

たとえば、1.225を小数点以下2桁に丸めたい場合、期待では1.23になってほしいと思うかもしれません。しかし、doubleでは内部表現の影響で結果がずれることがあります。

C#
double value = 1.225;
Console.WriteLine(Math.Round(value, 2, MidpointRounding.AwayFromZero));

このようなケースでは、金額や厳密な10進計算が必要な値はdecimalで扱うことを検討します。

2-5. 実行結果で確認するMath.Roundのサンプルコード

次のコードで、Math.Roundの基本動作を確認できます。

C#
using System;

class Program
{
static void Main()
{
double value = 123.4567;

Console.WriteLine(Math.Round(value));
Console.WriteLine(Math.Round(value, 1));
Console.WriteLine(Math.Round(value, 2));
Console.WriteLine(Math.Round(value, 3));

Console.WriteLine(Math.Round(1.5));
Console.WriteLine(Math.Round(2.5));
Console.WriteLine(Math.Round(3.5));
Console.WriteLine(Math.Round(4.5));
}
}

出力例です。

C#
123
123.5
123.46
123.457
2
2
4
4

特に注目したいのは、2.524.54になっている点です。これが、Math.Roundが「常に一般的な四捨五入をするわけではない」と言われる理由です。

3. C#のMath.Roundが四捨五入にならない原因

3-1. Math.Roundのデフォルトは銀行丸め

C#のMath.Roundのデフォルトは、MidpointRounding.ToEvenです。

ToEvenは、ちょうど中間の値を最も近い偶数に丸める方式です。これは「銀行丸め」や「偶数丸め」と呼ばれることがあります。

たとえば、整数に丸める場合は次のようになります。

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

1.512の中間です。この場合、偶数である2になります。
2.523の中間です。この場合、偶数である2になります。

つまり、.5だから常に切り上げるわけではありません。

3-2. ToEvenにより0.5が必ず切り上げられるとは限らない

MidpointRounding.ToEvenでは、丸め候補のうち偶数のほうを選びます。

C#
Console.WriteLine(Math.Round(10.5)); // 10
Console.WriteLine(Math.Round(11.5)); // 12
Console.WriteLine(Math.Round(12.5)); // 12
Console.WriteLine(Math.Round(13.5)); // 14

10.51011の中間なので、偶数の10になります。
11.51112の中間なので、偶数の12になります。

この動作は、丸めを大量に行ったときの偏りを抑える目的で使われます。常に.5を切り上げると、平均的に結果が大きい方向へ偏りやすくなるためです。

Microsoftの説明でも、MidpointRounding.ToEvenは中間値を最も近い偶数に丸める方式として説明されています。Microsoft Learn

3-3. 1.5・2.5・3.5で結果が変わる理由

1.52.53.5の結果を見てみましょう。

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

一般的な四捨五入の感覚では、次のように考えがちです。

C#
1.5  2
2.5 3
3.5 4

しかし、ToEvenでは次のようになります。

C#
1.5  2  // 偶数
2.5 2 // 偶数
3.5 4 // 偶数

つまり、Math.Roundのデフォルトでは「上に丸めるか下に丸めるか」ではなく、「どちらが偶数か」で決まります。

3-4. double型の誤差で期待どおりに丸められないケース

Math.Roundが期待どおりにならないもう一つの原因が、double型の誤差です。

doubleは多くの小数を正確に表せません。たとえば、0.1のような値も内部的には近似値として扱われます。そのため、見た目は11.5でも、内部的には11.499999999999998のような値になっていることがあります。

Microsoftのドキュメントでも、浮動小数点値の2進表現と精度の問題により、丸め結果が予期しないものになる可能性があると説明されています。Microsoft Learn

例を見てみます。

C#
double value = 11.1;

for (int i = 0; i < 4; i++)
{
value += 0.1;
}

Console.WriteLine(value);
Console.WriteLine(Math.Round(value, MidpointRounding.AwayFromZero));

期待としては11.5になり、12へ丸められるように見えます。しかし、内部的な値がわずかに11.5より小さい場合、11になる可能性があります。

3-5. 「小数点第n位で四捨五入したいのにずれる」原因

「小数点第2位まで四捨五入したいのに結果がずれる」という場合、主な原因は次の3つです。

1つ目は、Math.RoundのデフォルトがToEvenであることです。

C#
Console.WriteLine(Math.Round(2.345, 2)); // 2.35とは限らない

2つ目は、doubleの内部誤差です。

C#
double value = 1.225;
Console.WriteLine(Math.Round(value, 2));

3つ目は、仕様の言葉のあいまいさです。

「小数点第2位で四捨五入」と書いた場合、第2位を見て第1位に丸めるのか、第3位を見て第2位まで残すのかが曖昧です。

実装時は、次のように明確に決めましょう。

C#
// 小数点以下2桁に丸める
Math.Round(value, 2, MidpointRounding.AwayFromZero);

4. Math.Roundで一般的な四捨五入を実現する方法

4-1. MidpointRounding.AwayFromZeroを指定する

一般的な「0.5を切り上げる」感覚に近い丸めをしたい場合は、MidpointRounding.AwayFromZeroを指定します。

C#
Math.Round(2.5, MidpointRounding.AwayFromZero); // 3

AwayFromZeroは、ちょうど中間の値を0から遠ざかる方向へ丸めます。

正の数であれば、.5は大きいほうへ丸められます。

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

負の数の場合は、0から遠ざかるため、-2.5-3になります。

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

4-2. 小数点以下の桁数とAwayFromZeroを組み合わせる

小数点以下の桁数を指定しつつ、一般的な四捨五入に近い処理をしたい場合は、第3引数にMidpointRounding.AwayFromZeroを指定します。

C#
decimal value = 12.345m;

decimal result = Math.Round(value, 2, MidpointRounding.AwayFromZero);

Console.WriteLine(result); // 12.35

doubleでも同じように書けます。

C#
double value = 12.345;

double result = Math.Round(value, 2, MidpointRounding.AwayFromZero);

Console.WriteLine(result);

ただし、金額計算ではdecimalを優先しましょう。

4-3. 正の数を四捨五入するサンプルコード

正の数を一般的な四捨五入に近い形で丸める例です。

C#
using System;

class Program
{
static void Main()
{
decimal[] values =
{
1.4m,
1.5m,
2.4m,
2.5m,
3.5m
};

foreach (decimal value in values)
{
decimal result = Math.Round(value, 0, MidpointRounding.AwayFromZero);
Console.WriteLine($"{value} -> {result}");
}
}
}

出力例です。

C#
1.4 -> 1
1.5 -> 2
2.4 -> 2
2.5 -> 3
3.5 -> 4

正の数だけを扱う場合、AwayFromZeroは多くの人がイメージする四捨五入に近い結果になります。

4-4. 負の数を四捨五入するサンプルコード

負の数では、AwayFromZeroの意味を正しく理解しておく必要があります。

C#
using System;

class Program
{
static void Main()
{
decimal[] values =
{
-1.4m,
-1.5m,
-2.4m,
-2.5m,
-3.5m
};

foreach (decimal value in values)
{
decimal result = Math.Round(value, 0, MidpointRounding.AwayFromZero);
Console.WriteLine($"{value} -> {result}");
}
}
}

出力例です。

C#
-1.4 -> -1
-1.5 -> -2
-2.4 -> -2
-2.5 -> -3
-3.5 -> -4

AwayFromZeroは「切り上げ」ではなく「0から遠ざける」です。負の数では、より小さい値に丸められる点に注意してください。

4-5. ToEvenとAwayFromZeroの結果比較

ToEvenAwayFromZeroの違いを比較してみましょう。

C#
using System;

class Program
{
static void Main()
{
decimal[] values = { 1.5m, 2.5m, 3.5m, 4.5m, -1.5m, -2.5m };

foreach (decimal value in values)
{
decimal toEven = Math.Round(value, 0, MidpointRounding.ToEven);
decimal away = Math.Round(value, 0, MidpointRounding.AwayFromZero);

Console.WriteLine($"{value} -> ToEven: {toEven}, AwayFromZero: {away}");
}
}
}

出力例です。

C#
1.5 -> ToEven: 2, AwayFromZero: 2
2.5 -> ToEven: 2, AwayFromZero: 3
3.5 -> ToEven: 4, AwayFromZero: 4
4.5 -> ToEven: 4, AwayFromZero: 5
-1.5 -> ToEven: -2, AwayFromZero: -2
-2.5 -> ToEven: -2, AwayFromZero: -3

ToEvenは偶数に寄せます。
AwayFromZeroは0から遠ざけます。

一般的な四捨五入を期待しているなら、Math.Round(value, digits, MidpointRounding.AwayFromZero)を明示するのが安全です。

5. MidpointRoundingの種類と使い分け

5-1. MidpointRounding.ToEven

MidpointRounding.ToEvenは、C#のMath.Roundでデフォルトとして使われる丸めモードです。

C#
Math.Round(2.5, MidpointRounding.ToEven); // 2
Math.Round(3.5, MidpointRounding.ToEven); // 4

中間値の場合、最も近い偶数に丸めます。

特徴は、丸めの偏りを抑えやすいことです。統計処理や大量の集計処理など、丸めによる偏りを減らしたい場面で使われます。

5-2. MidpointRounding.AwayFromZero

MidpointRounding.AwayFromZeroは、中間値を0から遠ざかる方向に丸めます。

C#
Math.Round(2.5, MidpointRounding.AwayFromZero);  // 3
Math.Round(-2.5, MidpointRounding.AwayFromZero); // -3

正の数では、学校で習う四捨五入に近い動作になります。請求金額や画面表示など、仕様として「0.5は上げる」と決まっている場合に使います。

5-3. MidpointRounding.ToZero

MidpointRounding.ToZeroは、0に近づく方向へ丸めます。

C#
Math.Round(2.8, 0, MidpointRounding.ToZero);  // 2
Math.Round(-2.8, 0, MidpointRounding.ToZero); // -2

正の数では小さい方向、負の数では大きい方向に丸められます。

Math.Truncateに近い感覚で使えますが、Math.Roundの丸めモードとして指定できる点が特徴です。

5-4. MidpointRounding.ToNegativeInfinity

MidpointRounding.ToNegativeInfinityは、負の無限大方向へ丸めます。

C#
Math.Round(2.8, 0, MidpointRounding.ToNegativeInfinity);  // 2
Math.Round(-2.1, 0, MidpointRounding.ToNegativeInfinity); // -3

正の数では切り捨てに近く、負の数ではより小さい値になります。
Math.Floorに近い方向の丸めです。

5-5. MidpointRounding.ToPositiveInfinity

MidpointRounding.ToPositiveInfinityは、正の無限大方向へ丸めます。

C#
Math.Round(2.1, 0, MidpointRounding.ToPositiveInfinity);  // 3
Math.Round(-2.8, 0, MidpointRounding.ToPositiveInfinity); // -2

正の数では切り上げに近く、負の数では0に近づく方向になります。
Math.Ceilingに近い方向の丸めです。

MidpointRoundingには、近い値に丸める方式と、特定方向に丸める方式があります。Microsoftの補足ドキュメントでも、ToEvenAwayFromZeroは近い値への丸め、ToZeroToNegativeInfinityToPositiveInfinityは方向指定の丸めとして分類されています。Microsoft Learn

5-6. 用途別に選ぶ丸めモード

用途別に選ぶなら、次のように考えると分かりやすいです。

用途推奨される丸めモード
デフォルトの丸めでよいToEven
一般的な四捨五入にしたいAwayFromZero
0方向に寄せたいToZero
常に小さい方向へ寄せたいToNegativeInfinity
常に大きい方向へ寄せたいToPositiveInfinity
金額計算で仕様がある仕様に従って明示指定

重要なのは、丸めモードを省略しないことです。

C#
// 意図が曖昧
Math.Round(value, 2);

// 意図が明確
Math.Round(value, 2, MidpointRounding.AwayFromZero);

実務では、あとからコードを読んだ人が意図を理解できるように、MidpointRoundingを明示する書き方がおすすめです。

6. Math.Roundと切り捨て・切り上げの違い

6-1. Math.Floorで切り捨てる方法

Math.Floorは、指定した数値以下の最大の整数を返します。

C#
Console.WriteLine(Math.Floor(3.9));  // 3
Console.WriteLine(Math.Floor(3.1)); // 3
Console.WriteLine(Math.Floor(-3.1)); // -4

正の数では「小数点以下を切り捨てる」ように見えます。
しかし、負の数では注意が必要です。

-3.1以下の最大の整数は-4です。そのため、Math.Floor(-3.1)-3ではなく-4になります。

6-2. Math.Ceilingで切り上げる方法

Math.Ceilingは、指定した数値以上の最小の整数を返します。

C#
Console.WriteLine(Math.Ceiling(3.1));  // 4
Console.WriteLine(Math.Ceiling(3.9)); // 4
Console.WriteLine(Math.Ceiling(-3.9)); // -3

正の数では「小数点以下を切り上げる」動作になります。
一方、負の数では0に近づく方向になります。

6-3. Math.Truncateで小数点以下を除去する方法

Math.Truncateは、小数点以下を単純に取り除きます。

C#
Console.WriteLine(Math.Truncate(3.9));  // 3
Console.WriteLine(Math.Truncate(3.1)); // 3
Console.WriteLine(Math.Truncate(-3.9)); // -3

Floorとの違いは、負の数で分かりやすくなります。

C#
Math.Floor(-3.9);    // -4
Math.Truncate(-3.9); // -3

Truncateは0方向に小数部分を除去します。

6-4. Round・Floor・Ceiling・Truncateの違い

それぞれの違いを表にすると次のようになります。

メソッド意味3.7-3.7
Math.Round指定ルールで丸める4-4または-4
Math.Floor負の無限大方向へ3-4
Math.Ceiling正の無限大方向へ4-3
Math.Truncate0方向へ小数部を除去3-3

Roundは丸め、Floorは下方向、Ceilingは上方向、Truncateは0方向です。

「切り捨て」と言うとき、正の数だけならFloorTruncateの結果は同じになりやすいですが、負の数を含む場合は結果が変わります。

6-5. 目的別の使い分け早見表

目的別には、次のように使い分けます。

目的使うメソッド
小数点以下を指定桁数に丸めたいMath.Round
一般的な四捨五入に近くしたいMath.Round(value, digits, MidpointRounding.AwayFromZero)
常に小さい整数にしたいMath.Floor
常に大きい整数にしたいMath.Ceiling
小数点以下を単純に消したいMath.Truncate
金額を指定ルールで丸めたいdecimal + Math.Round

Math.Roundは万能なように見えますが、切り捨てや切り上げをしたい場合は、FloorCeilingを使ったほうが意図が明確です。

7. Math.Roundでよくある実務上の注意点

7-1. 金額計算ではdecimal型を使う

金額計算では、基本的にdecimal型を使います。

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

decimal roundedTax = Math.Round(tax, 0, MidpointRounding.AwayFromZero);

doubleは2進数で小数を表すため、10進数の金額計算では誤差が問題になることがあります。

たとえば、消費税、請求金額、割引額、単価、合計金額などは、decimalを使ったほうが安全です。

7-2. 表示だけ整えたい場合はToStringや書式指定を使う

画面に「小数点以下2桁で表示したい」だけなら、必ずしもMath.Roundで値そのものを丸める必要はありません。

C#
double value = 12.3;

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

ToString("F2")は、小数点以下2桁で文字列として表示します。

C#
decimal price = 1234.5m;

Console.WriteLine(price.ToString("N2")); // 1,234.50

表示形式を整えたいだけなら、数値の丸め処理と表示の書式指定を分けて考えましょう。

7-3. 計算途中で丸めすぎると誤差が大きくなる

計算途中で何度もMath.Roundを使うと、最終結果が本来の値からずれることがあります。

悪い例です。

C#
decimal a = Math.Round(10.123m, 2);
decimal b = Math.Round(20.456m, 2);
decimal total = Math.Round(a + b, 2);

計算途中で丸めると、途中の誤差が積み重なります。

できるだけ、計算中は十分な精度を保ち、最後に必要な桁数へ丸めるのが基本です。

C#
decimal a = 10.123m;
decimal b = 20.456m;
decimal total = Math.Round(a + b, 2, MidpointRounding.AwayFromZero);

7-4. 税率・割合・平均値計算での注意点

税率、割合、平均値の計算では、どのタイミングで丸めるかが重要です。

たとえば、消費税計算では次のような違いが出ます。

C#
decimal item1 = 101m;
decimal item2 = 102m;
decimal taxRate = 0.10m;

// 商品ごとに税を丸める
decimal tax1 = Math.Round(item1 * taxRate, 0, MidpointRounding.AwayFromZero);
decimal tax2 = Math.Round(item2 * taxRate, 0, MidpointRounding.AwayFromZero);
decimal totalTax1 = tax1 + tax2;

// 合計に対して税を丸める
decimal totalTax2 = Math.Round((item1 + item2) * taxRate, 0, MidpointRounding.AwayFromZero);

商品ごとに丸めるのか、合計後に丸めるのかで結果が変わることがあります。

平均値も同じです。

C#
decimal average = scores.Average();
decimal roundedAverage = Math.Round(average, 1, MidpointRounding.AwayFromZero);

「いつ丸めるか」は業務仕様として決める必要があります。

7-5. 仕様として丸め方法を明確に決める

実務で重要なのは、丸め方法をコードを書く人の判断に任せないことです。

仕様書や設計書では、次のように明確に書くと安全です。

金額はdecimal型で計算する。
税額は合計金額に税率を掛けた後、小数点以下0桁に丸める。
丸め方法はMidpointRounding.AwayFromZeroとする。

または、次のように書きます。

平均値は小数点以下第2位まで表示する。
値そのものは丸めず、表示時にF2形式を使用する。

Math.Roundを使うかどうかだけでなく、型、桁数、丸めモード、丸めのタイミングを明確にすることが大切です。

8. Math.Roundのエラー・例外・ハマりどころ

8-1. digitsに指定できる範囲

Math.Rounddigitsに指定できる範囲には制限があります。

doubleの場合、digits0から15までです。範囲外を指定するとArgumentOutOfRangeExceptionが発生します。Microsoftのドキュメントでも、double向けのdigitsは0から15までとされています。Microsoft Learn

C#
double value = 1.2345;

// OK
Math.Round(value, 2);

// 例外
Math.Round(value, 16);

decimalの場合、指定できる小数桁数は0から28までです。Microsoft Learn

C#
decimal value = 1.2345m;

// OK
Math.Round(value, 28);

// 例外
Math.Round(value, 29);

8-2. decimalとdoubleを混在させるリスク

decimaldoubleを混在させると、意図しない型変換やコンパイルエラーの原因になります。

たとえば、次のコードはそのままでは扱いにくくなります。

C#
decimal price = 1000m;
double rate = 0.1;

// decimal tax = price * rate; // コンパイルエラー

decimaldoubleは暗黙的に混ぜられません。

金額計算であれば、税率もdecimalにします。

C#
decimal price = 1000m;
decimal rate = 0.1m;

decimal tax = price * rate;

値の型を統一することで、丸め結果も予測しやすくなります。

8-3. 期待値と実行結果が異なるときの確認ポイント

Math.Roundの結果が期待と違う場合は、次の点を確認します。

まず、丸めモードを省略していないか確認します。

C#
Math.Round(value, 2);

この書き方では、デフォルトのToEvenになります。一般的な四捨五入にしたいなら、次のように書きます。

C#
Math.Round(value, 2, MidpointRounding.AwayFromZero);

次に、型を確認します。

C#
double value = 1.225;
decimal value2 = 1.225m;

doubleでは誤差の影響を受けることがあります。金額や10進数の精度が重要な処理ではdecimalを使いましょう。

最後に、丸める桁数が正しいか確認します。

C#
// 小数点以下2桁にする
Math.Round(value, 2);

「どの桁を残したいのか」を明確にすることが重要です。

8-4. 単体テストで丸め結果を検証する方法

丸め処理は、単体テストで明示的に確認しておくべきです。

たとえば、xUnitなら次のように書けます。

C#
using Xunit;

public class RoundingTests
{
[Theory]
[InlineData(1.5, 2)]
[InlineData(2.5, 3)]
[InlineData(-2.5, -3)]
public void AwayFromZeroで丸める(double input, double expected)
{
double actual = Math.Round(input, 0, MidpointRounding.AwayFromZero);

Assert.Equal(expected, actual);
}
}

decimalでテストする場合です。

C#
[Theory]
[InlineData("12.345", "12.35")]
[InlineData("12.344", "12.34")]
public void Decimalを小数点以下2桁に丸める(string inputText, string expectedText)
{
decimal input = decimal.Parse(inputText);
decimal expected = decimal.Parse(expectedText);

decimal actual = Math.Round(input, 2, MidpointRounding.AwayFromZero);

Assert.Equal(expected, actual);
}

丸め処理は、境界値のテストが特に重要です。

C#
1.4
1.5
2.5
-1.5
-2.5
12.345
12.355

.5を含む値、負の値、小数点以下の境界値をテストに入れておきましょう。

8-5. バージョンや実行環境による注意点

MidpointRounding.ToZeroToNegativeInfinityToPositiveInfinityなどの丸めモードは、比較的新しい.NET環境で使われます。古い.NET FrameworkやUnity環境では、利用できるMidpointRoundingの値が限られる場合があります。

そのため、ライブラリやアプリケーションで複数の実行環境を対象にする場合は、対象フレームワークを確認しましょう。

XML
<TargetFramework>net8.0</TargetFramework>

Unityの場合も、使用しているUnityのバージョンや.NET互換設定によって使えるAPIが変わることがあります。

実務では、次の点を確認しておくと安心です。

確認項目内容
対象フレームワーク.NET Framework、.NET 6、.NET 8など
使用する型doubledecimal
丸めモードToEvenAwayFromZero
桁数範囲doubleは0〜15、decimalは0〜28
実行環境サーバー、クライアント、Unityなど

9. C#のMath.Roundに関するよくある質問

9-1. Math.Roundは四捨五入ではないのか

Math.Roundは、丸め処理を行うメソッドです。ただし、デフォルトでは一般的な四捨五入ではなく、MidpointRounding.ToEvenが使われます。

そのため、次のような結果になります。

C#
Math.Round(2.5); // 2
Math.Round(3.5); // 4

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

C#
Math.Round(2.5, MidpointRounding.AwayFromZero); // 3

9-2. 0.5を必ず切り上げるにはどうすればよいか

正の数で0.5を上方向に丸めたい場合は、MidpointRounding.AwayFromZeroを指定します。

C#
double result = Math.Round(2.5, MidpointRounding.AwayFromZero);

Console.WriteLine(result); // 3

小数点以下の桁数も指定する場合は、次のように書きます。

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

Console.WriteLine(result); // 12.35

負の数の場合、AwayFromZeroは0から遠ざかる方向に丸めます。

C#
Math.Round(-2.5, MidpointRounding.AwayFromZero); // -3

9-3. 小数点第2位まで表示するだけならMath.Roundでよいか

表示だけを整えたい場合は、Math.Roundではなく書式指定を使うほうが適している場合があります。

C#
double value = 12.3;

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

Math.Roundは値そのものを丸めます。
ToString("F2")は表示を小数点以下2桁に整えます。

計算結果を保存するために丸めたいのか、画面表示だけ整えたいのかを分けて考えましょう。

9-4. doubleとdecimalのどちらを使うべきか

用途によって使い分けます。

用途推奨
金額、税、請求、単価decimal
科学技術計算、座標、一般的な浮動小数点計算double
Unityの座標や物理計算floatまたはdouble
厳密な10進計算decimal

金額計算では、基本的にdecimalを使いましょう。

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

doubleを使う場合は、浮動小数点誤差に注意が必要です。

9-5. UnityのMathf.Roundとの違いはあるか

Unityでは、Mathf.RoundMathf.RoundToIntがよく使われます。

C#
float value = 10.5f;

float rounded = Mathf.Round(value);
int roundedInt = Mathf.RoundToInt(value);

UnityのMathf.Roundfloatを受け取り、最も近い整数値に丸めたfloatを返します。Unityの公式ドキュメントでも、.5でちょうど中間の場合は偶数のほうを返すと説明されています。Unity

つまり、Mathf.Round(10.5f)10Mathf.Round(11.5f)12になります。

C#
Mathf.Round(10.5f); // 10
Mathf.Round(11.5f); // 12

C#のMath.Roundと同じく、.5が常に上がるわけではない点に注意しましょう。

Unityで一般的な四捨五入にしたい場合は、対象の値が正の数だけなのか、負の数も含むのかを確認したうえで、独自の丸め処理やSystem.Math.Roundの利用を検討します。

まとめ

C#のMath.Roundは、小数点以下を丸めるための便利なメソッドです。整数に丸めるだけでなく、小数点以下の桁数を指定したり、MidpointRoundingで丸めモードを指定したりできます。

ただし、Math.Roundのデフォルトは一般的な四捨五入ではなく、MidpointRounding.ToEvenです。そのため、2.53ではなく2になることがあります。

一般的な四捨五入に近い処理をしたい場合は、次のようにMidpointRounding.AwayFromZeroを明示します。

C#
Math.Round(value, 2, MidpointRounding.AwayFromZero);

また、金額計算ではdoubleではなくdecimalを使うことが重要です。

C#
decimal result = Math.Round(amount, 0, MidpointRounding.AwayFromZero);

表示だけを整えたい場合は、Math.RoundではなくToString("F2")などの書式指定を使うほうが適している場合もあります。

Math.Roundを正しく使うポイントは、次の5つです。

ポイント内容
デフォルト動作ToEven、つまり銀行丸め
一般的な四捨五入AwayFromZeroを指定
金額計算decimalを使う
表示だけToStringや書式指定を使う
実務仕様型、桁数、丸めモード、タイミングを明確にする

Math.Roundはシンプルに見えますが、丸めモードや型の違いを理解していないと、実務で思わぬ不具合につながります。特に金額、税率、平均値、割合などを扱う場合は、仕様として「どの桁で、どの方法で、いつ丸めるのか」を明確にしてから実装しましょう。