C#で数値を丸める際、最も頻繁に利用されるのがMath.Roundメソッドです。
しかし、このメソッドを単純に使用すると、期待していた「四捨五入」の結果が得られないことがあります。
例えば「2.5」を丸めた結果が「2」になり、「3.5」を丸めた結果が「4」になるといった現象です。
これはバグではなく、C#の標準仕様である「銀行丸め(Banker’s Rounding)」によるものです。
本記事では、なぜこのような挙動になるのか、そして一般的な四捨五入を実現するためにはどのように記述すべきかを詳しく解説します。
Math.Roundのデフォルト挙動と「銀行丸め」の正体
C#のMath.Roundメソッドは、デフォルトでは「もっとも近い偶数」に丸めるアルゴリズムを採用しています。
これを「銀光丸め」あるいは「偶数への丸め」と呼びます。
一般的な算数で習う「四捨五入」は、端数が0.5の場合に必ず切り上げますが、銀行丸めでは異なります。
まずは、以下のコードで実際の挙動を確認してみましょう。
using System;
class Program
{
static void Main()
{
// 銀行丸めの例
Console.WriteLine($"1.5 -> {Math.Round(1.5)}");
Console.WriteLine($"2.5 -> {Math.Round(2.5)}");
Console.WriteLine($"3.5 -> {Math.Round(3.5)}");
Console.WriteLine($"4.5 -> {Math.Round(4.5)}");
}
}
1.5 -> 2
2.5 -> 2
3.5 -> 4
4.5 -> 4
実行結果を見ると、1.5は2に切り上げられていますが、2.5は2に切り捨てられていることが分かります。
同様に、3.5は4になりますが、4.5は4になります。
このように、端数がちょうど0.5のとき、結果が偶数になる方向へ丸められるのが特徴です。
なぜ銀行丸めが採用されているのか
なぜ直感に反するような「銀行丸め」がデフォルトで採用されているのでしょうか。
その主な理由は、大量のデータを集計した際の合計値の誤差を最小限に抑えるためです。
通常の四捨五入(0.5を常に切り上げ)を大量のデータに対して行うと、全体として数値がプラス方向に偏るという性質があります。
すべての「0.5」が切り上げられると、平均値や合計値が元のデータよりもわずかに大きくなってしまうのです。
一方、銀行丸めでは「切り上げ」と「切り捨て」が約半々の確率で発生します。
これにより、統計的な偏りが相殺され、より正確な合計値を算出できるというメリットがあります。
この仕様は、浮動小数点演算に関する国際標準規格であるIEEE 754でも推奨されています。
四捨五入(0.5を切り上げ)を正しく行う方法
業務アプリケーションや一般的な計算アプリでは、ユーザーの期待通りに「0.5を常に切り上げる」処理が求められるケースが多いでしょう。
C#でこれを実現するには、Math.Roundメソッドの第2引数(または第3引数)にMidpointRounding列挙型を指定します。
具体的には、MidpointRounding.AwayFromZeroを指定することで、私たちがよく知る四捨五入が可能になります。
using System;
class Program
{
static void Main()
{
// AwayFromZeroを指定した四捨五入
Console.WriteLine($"1.5 (AwayFromZero) -> {Math.Round(1.5, MidpointRounding.AwayFromZero)}");
Console.WriteLine($"2.5 (AwayFromZero) -> {Math.Round(2.5, MidpointRounding.AwayFromZero)}");
Console.WriteLine($"3.5 (AwayFromZero) -> {Math.Round(3.5, MidpointRounding.AwayFromZero)}");
Console.WriteLine($"4.5 (AwayFromZero) -> {Math.Round(4.5, MidpointRounding.AwayFromZero)}");
}
}
1.5 (AwayFromZero) -> 2
2.5 (AwayFromZero) -> 3
3.5 (AwayFromZero) -> 4
4.5 (AwayFromZero) -> 5
MidpointRounding.AwayFromZeroは、「中間点(0.5)に位置する場合、ゼロから遠い方の数値へ丸める」という意味を持ちます。
正の数の場合は切り上げ、負の数の場合は切り下げ(より小さな値へ)となります。
MidpointRoundingの主要なオプション
現在の.NET(C#)では、MidpointRoundingに複数のオプションが用意されています。
それぞれの挙動の違いを理解しておくことで、要件に合わせた適切な丸め処理を選択できます。
| 列挙型メンバー | 説明 | 2.5 の結果 | -2.5 の結果 |
|---|---|---|---|
ToEven | 最も近い偶数に丸める(デフォルト:銀行丸め) | 2 | -2 |
AwayFromZero | 0から遠い方の値に丸める(一般的な四捨五入) | 3 | -3 |
ToZero | 0に近い方の値に丸める(切り捨てに近い) | 2 | -2 |
ToNegativeInfinity | 負の無限大に近い方に丸める | 2 | -3 |
ToPositiveInfinity | 正の無限大に近い方に丸める | 3 | -2 |
これらのオプションを使い分けることで、金融計算、物理シミュレーション、UI表示など、用途に応じた制御が可能になります。
浮動小数点数(double)特有の注意点
Math.Roundを使用する際、もう一つ注意しなければならないのが浮動小数点数(double型やfloat型)の精度問題です。
コンピュータ内部でdouble型はバイナリ(2進数)で保持されているため、10進数の「0.5」が正確に表現できない場合があります。
例えば、人間の目には「2.5」に見えていても、内部的には「2.4999999999999998」となっていることがあります。
この場合、AwayFromZeroを指定していても、数値が0.5に達していないと判断され、結果が「2」になってしまう可能性があります。
double val = 2.5; // 内部的に2.499...になる可能性がある
double result = Math.Round(val, MidpointRounding.AwayFromZero);
このような精度問題を回避するためには、金額計算など正確な10進計算が必要な場面では必ずdecimal型を使用するのが鉄則です。
decimal型であれば、10進数を正確に表現できるため、Math.Roundも期待通りの動作をします。
decimal price = 2.5m;
decimal rounded = Math.Round(price, MidpointRounding.AwayFromZero); // 確実に3になる
実務で役立つ丸め処理の実装パターン
ここでは、特定の桁数で丸める方法や、負の数の扱いなど、実務でよく遭遇するケースを紹介します。
小数点第n位で四捨五入する
Math.Roundの第2引数に整数を指定することで、丸める桁数を指定できます。
例えば、小数点第2位までを残して第3位を四捨五入したい場合は、以下のように記述します。
decimal value = 1.235m;
// 小数点第2位まで残す(第3位を四捨五入)
decimal result = Math.Round(value, 2, MidpointRounding.AwayFromZero);
Console.WriteLine(result); // 出力: 1.24
この引数の指定を忘れると、デフォルトでは整数値に丸められてしまうため注意が必要です。
負の数の四捨五入における挙動
AwayFromZeroを使用する際、負の数の挙動にも注意が必要です。
「-1.5」をAwayFromZeroで丸めると、結果は「-2」になります。
これは「0から遠い方」という定義に基づいているためです。
もし「負の数であっても常に数値が大きい方(右方向)へ丸めたい」といった特殊な要件がある場合は、前述したToPositiveInfinityなどのオプションを検討してください。
2026年現在のC#における丸め処理のベストプラクティス
最新の.NET環境では、パフォーマンスと可読性の両立が重視されています。
丸め処理に関しても、単にMath.Roundを呼び出すだけでなく、文脈に応じた最適な手法を選択することが推奨されます。
1. 基本はdecimal型を選択する
ビジネスロジック、特に税計算や給与計算などの「お金」が絡む処理では、doubleではなくdecimalを一貫して使用してください。
Math.Round(decimal, MidpointRounding)のオーバーロードを使用することで、計算誤差のトラブルを未然に防ぐことができます。
2. 常にMidpointRoundingを明示する
コードの可読性と保守性を高めるため、引数が不要な場合であってもMidpointRoundingを明示的に指定することを推奨します。
デフォルトの挙動に依存すると、後からコードを読んだ開発者が「これは銀行丸めを意図しているのか、それとも単なる指定漏れなのか」を判断できなくなるためです。
3. 表示用であれば文字列フォーマットも検討する
計算結果をユーザーに表示するだけであれば、Math.Roundを使わずに文字列フォーマットで制御する方法もあります。
ただし、ToString("F0")などのフォーマットも内部的には丸め処理を行っており、そのアルゴリズムは環境によって異なる場合があるため、正確な制御が必要な場合はやはりMath.Roundで明示的に丸めてから表示するのが安全です。
まとめ
C#のMath.Roundメソッドは、標準では「銀行丸め(偶数への丸め)」という特有のアルゴリズムで動作します。
これは統計的な誤差を抑えるための優れた仕様ですが、一般的な四捨五入とは挙動が異なるため、注意が必要です。
期待通りの四捨五入を実現するためには、以下の3点を意識しましょう。
- 端数が0.5のときに切り上げたい場合は、
MidpointRounding.AwayFromZeroを明示的に指定する。 - 精度が重要な計算では、
double型ではなくdecimal型を使用する。 - .NET 5以降で追加された新しい丸めモードを活用し、要件に最適な手法を選択する。
これらの基本を押さえておくことで、数値計算における予期せぬバグを防ぎ、信頼性の高いアプリケーションを構築できるようになります。
日々のコーディングにおいて、数値の丸めが必要になった際は、ぜひ本記事の内容を思い出して適切なオプションを選択してください。
