先生!この人たち銀行丸めです!!!
ちょっと落ち着いてくれる?
一体何をしたの?
単純に四捨五入のコードを書いてただけです……。
問題のコード
double result = Math.Round(2.5);
こちら、実行するとresultに何が入るか分かります?
2.5の四捨五入だから……3?
残念!不正解です。
こちらのメソッドは「銀行丸め」なので正解は「2」です!
銀行丸めとは
ざっくりいうと2.5を四捨五入した時に2に変えてしまう丸め方。
統計的な処理であれば、四捨五入ではなく0.5の場合は近い偶数に近づけた方が計算しやすいし誤差が減るらしいです。
銀行丸め前 | 銀行丸め後 |
---|---|
1.5 | 2 |
2.5 | 2 |
3.5 | 4 |
4.5 | 4 |
5.5 | 6 |
C#ではそのまま四捨五入のメソッドを使うと四捨五入されない
実は四捨五入のround関数って色々な言語で実装されてはいるんですけど、初期状態は銀行丸めなこと、結構あります。
C#は上で紹介した通り、銀行丸めになってしまうのですが、実はC#だけではなく、VBAとか.net全般でも同じことが起こります。C#ベースのローコード開発ツール(outsystemsとか)などでも同じことが発生します。あとはPythonも同じく銀行丸めになります。
C#でもPythonでも四捨五入メソッドを適当に簡単そうなものを選ぶと失敗するのは共通です。
嫌なお揃いだ……。
C#の場合は、引数を渡さないと一般的にいう四捨五入にはならないし、Pythonなんてround使ってたら普通の四捨五入ができない。
一応、銀行丸めは誤差を減らすための計算方法らしいですが、正直誤動作の原因になっているので何も設定しないと自動で銀行丸めにされてしまう挙動は個人的にはあんまり好きじゃないです。
気が付かずに会計システムに組み込んでしまった日には地獄ですね……。
JavaはJavaでroundを使用すると四捨五入する桁数を指定できないっていうなかなか癖のある作りをしていますが、まあこの作りで事故ることはないのでヨシ!
対策方法
設計仕様に四捨五入が出てきた場合はどんなプログラミング言語で実装するにしても2.5が四捨五入されるようにテストを設定しておきましょう。
最悪はテストさえしていれば避けられます!
C#であれば、以下のようなコードを使えば広く使われている四捨五入処理になります。
コードレビューも大切ですね。
result = Math.Round(2.5, MidpointRounding.AwayFromZero);
そして以下のプログラミング言語を使うときには「四捨五入」というワードが出てきたら、銀行丸めではないか注意した方が良いです。
- python
- C#
- VBA
- MySQL
ついでに文中で一瞬だけ出てきたOutsystemsはどうなってたんだっけとリファレンスを見てみたら、なんかものすごくややこしいことになってました……。
指定された「小数点以下の桁数」で端数処理された小数「n」を返します。
適用される端数処理は、関数の使用場所によって異なります。– クライアント側とサーバー側のロジックのExpressionでは、最近接偶数への丸めの方法が適用されます(最も近い整数に四捨五入。0.5は最も近い偶数の整数に丸める)。
– SQL ServerまたはOracleデータベースにクエリを実行するAggregateでは、四捨五入(0.5は0から離れた数に丸める)が適用されます(最も近い整数に四捨五入。0.5は0から離れた数に丸める)。
– iDB2データベースにクエリを実行するAggregateでは、四捨五入(0.5は切り上げ)が適用されます(最も近い整数に四捨五入。0.5は切り上げ)。
Outsystems公式リファレンス