1. はじめに
現実世界でお金を100兆分の1ユーロ単位で払うことなんてまずないし、部屋の長さを0.01ミリ単位で測ることもほぼないよね。プログラミングでも同じで、doubleやfloatを使うと、結果が「細かすぎる」ことがよくあるんだ。
こんなクラシックな問題があるよ:
double result = 10.0 / 3.0;
Console.WriteLine(result); // 3.3333333333333335
でも、もしこの数字を小数点以下2桁だけで表示したい(ユーザーを「ビビらせない」ために)なら?そのために丸めが必要なんだ。
2. C#の主な丸めメソッド
C#では浮動小数点数を丸める方法がいくつかあるよ。ここからが面白いところで、丸め方にもいろいろあるんだ!主なパターンを見て、違いを理解しよう。
1) Math.Round関数 — クラシックな丸め
一番よく使うのはMath.Roundメソッド。これは数字を一番近い整数(または指定した小数点以下の桁数)に丸めてくれるよ。
Math.Round(数値[, 桁数[, MidpointRounding]])
- 数値 — 丸めたい値。
- 桁数 — 小数点以下何桁残すか(デフォルトは0、つまり整数まで)。
- MidpointRounding — 0.5の時の丸め方(これは後で説明するね)。
例:
double x = 2.71828;
Console.WriteLine(Math.Round(x)); // 3
Console.WriteLine(Math.Round(x, 2)); // 2.72
Console.WriteLine(Math.Round(x, 3)); // 2.718
ビジュアライズ:
| 入力値 | Math.Round(x) | Math.Round(x, 2) |
|---|---|---|
| 2.3 | 2 | 2.3 |
| 2.5 | 2 | 2.5 |
| 2.7 | 3 | 2.7 |
| 2.71828 | 3 | 2.72 |
戻り値の型:
この講義で出てくる関数は全部double型を返すよ。だから丸めた結果を代入するとき、エラーになることもあるんだ:
double result = 10.0 / 5.0; // 結果は2.0
int x = Math.Round(x); // エラー! double型をint型にそのまま代入できない
もしdouble型をint型の変数に入れたいなら、キャストを明示的に書こう:
double result = 10.0 / 5.0; // 結果は2.0
int x = (int) Math.Round(x); // これでOK!
2) 切り上げ・切り捨ての丸め
C#では「クラシック」な丸めだけじゃなくて、強制的に上か下に丸めることもできるよ。
小さい方に丸める(Math.Floor)
Math.Floorメソッドは小数点以下を「切り捨て」(マイナス無限大方向)する。つまり、必ず下に丸める!
Console.WriteLine(Math.Floor(2.99)); // 2
Console.WriteLine(Math.Floor(-2.99)); // -3
大きい方に丸める(Math.Ceiling)
Math.CeilingメソッドはFloorの逆で、必ず上に丸める(プラス無限大方向)。
Console.WriteLine(Math.Ceiling(2.01)); // 3
Console.WriteLine(Math.Ceiling(-2.01)); // -2
比較表:
| 関数 | 2.3 | -2.3 |
|---|---|---|
| Math.Round | 2 | -2 |
| Math.Floor | 2 | -3 |
| Math.Ceiling | 3 | -2 |
3) Truncate: 小数点以下を単純に切り捨て
Math.Truncateメソッドは「冷静な外科医」みたいなもの。小数点以下をただ消すだけで、数学的な丸めは気にしない。xがプラスならFloorと同じ、マイナスならCeilingと同じになるよ。
Console.WriteLine(Math.Truncate(2.99)); // 2
Console.WriteLine(Math.Truncate(-2.99)); // -2
3. Math.Roundの「0.5」の丸め方(MidpointRounding)
例えば2.5があるとする。どっちに丸める?2と3のちょうど真ん中だよね。数学では2つの有名なやり方がある:
- 大きい方に丸める(「切り上げ」)
- 一番近い偶数に丸める(Banker's rounding)
C#のデフォルトは2番目の「一番近い偶数に丸める」(banker's rounding、または「偶数丸め」)。
Console.WriteLine(Math.Round(2.5)); // 2
Console.WriteLine(Math.Round(3.5)); // 4
なぜ?2つの同じ距離の整数があったら偶数が「勝つ」から。これは大量に丸めるとき、結果が偏らないようにするため。銀行で大金を扱うときに大事なんだ(だからbanker's roundingって呼ばれる)。
もし常に切り上げたいなら、モードを明示的に指定できるよ:
// 常に「ゼロから離れる」方向に丸める
Console.WriteLine(Math.Round(2.5, 0, MidpointRounding.AwayFromZero)); // 3
Console.WriteLine(Math.Round(-2.5, 0, MidpointRounding.AwayFromZero)); // -3
MidpointRoundingモードの表:
| 入力値 | Round(デフォルト) | AwayFromZero | ToZero / ToEven |
|---|---|---|---|
| 2.5 | 2 | 3 | 2 |
| 3.5 | 4 | 4 | 4 |
| -2.5 | -2 | -3 | -2 |
4. 必要な小数点以下の桁数に丸める:「8」や「3」に注意!
整数じゃなくて、小数点以下2桁とかに丸めたいときもあるよね(お金やパーセントの表示とか)。
double price = 149.9999;
double roundedPrice = Math.Round(price, 2);
Console.WriteLine(roundedPrice); // 150
もし単に余分な桁を「切り捨て」たいだけで、丸めたくない場合は?例えば123.4567を123.45にしたいとき。
こんな小技が使えるよ:
double num = 123.4567;
double result = Math.Floor(num * 100) / 100;
Console.WriteLine(result); // 123.45
小数点を「ずらして」、小数点以下を切り捨てて、また戻すって感じ。
5. 出力フォーマットと丸めの違い
出力フォーマット({x:F2})は、「本当の」丸めじゃなくて、画面上の「見た目」だけだよ。メモリ上では長い「しっぽ」のdoubleのまま。値自体を丸めて保存したいなら、Math.Roundを使おう。
こんな感じで違いが大事になることもあるよ:
double value = 2.555;
Console.WriteLine($"{value:F2}"); // 2.56
Console.WriteLine(Math.Round(value, 2)); // 2.56
double stored = Math.Round(value, 2);
Console.WriteLine(stored); // 2.56
// でも丸めずにフォーマットだけだと...
Console.WriteLine($"{value:F2}"); // 2.56、でもメモリ上は2.555
6. よくあるミスや注意点
- doubleで正確な金額計算に丸めを使わないで! お金には.NETのdecimal型を使おう。これは浮動小数点の「いたずら」がない(詳しくは別の講義で!)。
- 「しつこい8」問題:バイナリで保存するから、見た目通りに丸められないこともある。例えば0.1 + 0.2は0.3にならないことも。
- Math.FloorとMath.Roundを混同しないで! 前者は常に下、後者は数学的な丸め。
- 誤差の蓄積に注意:計算途中で何度も丸めると、最終結果がズレることがある。普通は最後だけ丸めるのがベター。
7. まとめ表:各メソッドの結果
| 入力値 | Math.Round(x) | Math.Floor(x) | Math.Ceiling(x) | Math.Truncate(x) |
|---|---|---|---|---|
| 3.2 | 3 | 3 | 4 | 3 |
| 3.5 | 4 | 3 | 4 | 3 |
| -3.2 | -3 | -4 | -3 | -3 |
| -3.5 | -4 | -4 | -3 | -3 |
8. おもしろ豆知識
- 昔、NASAは座標の丸めが甘くて、メートル法とヤード・ポンド法の違いもあって宇宙機を失ったことがあるんだ。教訓:丸めは慎重に!
- 銀行はずっと前から「banker's rounding」を使ってる。昔は全部切り上げで、客が毎回ちょっとずつ損してた。
- C#にはMath.Signメソッドもあって、数字の符号を返してくれる。丸め前にどっちに「切る」か決めたいとき便利だよ。
GO TO FULL VERSION