Math.Round(2.5)が3ではなく2を返すのはなぜですか?


415

C#では、結果Math.Round(2.5)は2です。

3つはずだよね?なぜC#ではなく2なのですか?


5
それは実際には機能です。< ahref =" msdn.microsoft.com/en-us/library/…MSDNドキュメント </a >を参照してください。この種の丸めは、銀行員の丸めと呼ばれます。回避策としては、<a href = " msdn。 microsoft.com/en-us/library/…オーバーロード</a>。これにより、呼び出し元は丸めの方法を指定できます。
ジョー

1
どうやら、roundメソッドは、2つの整数の間で正確に数値を丸めるように要求されると、偶数の整数を返します。つまり、Math.Round(3.5)は4を返します。この記事を
マシュージョーンズ

20
Math.Round(2.5, 0, MidpointRounding.AwayFromZero);
Robert Durgin 2009年

SQL Serverはそのように丸めます。T-SQLで行われたC#ユニットテストti検証丸めがある場合の興味深いテスト結果。
idstam 2009年

7
@amedはバグではありません。これは、バイナリ浮動小数点が機能する方法です。1.005doubleで正確に表すことはできません。それはおそらく1.00499...です。Decimalこの問題を使用すると、消えます。doubleで小数点以下の桁数をとるMath.Roundオーバーロードの存在は、意味のある方法で動作することはまれであるため、疑わしい設計選択IMOです。
CodesInChaos

回答:


560

まず、これはいずれにせよC#のバグではない-.NETのバグである。C#は言語Math.Roundです。実装方法は決定されません。

2番目に、いいえ- ドキュメントを読むと、デフォルトの丸めが「偶数に丸める」(銀行の丸め)であることがわかります。

戻り値の
型:System.Double
aに最も近い整数。aの小数部が2つの整数の中間にあり、一方が偶数でもう一方が奇数の場合、偶数が返されます。このメソッドはDouble、整数型の代わりにを返すことに注意してください。

解説
このメソッドの動作は、IEEE標準754、セクション4に従います。この種の丸めは、最も近い値への丸め、または銀行の丸めと呼ばれることがあります。これは、中間点の値を一方向に一貫して丸めることから生じる丸め誤差を最小限に抑えます。

値を取るオーバーロードMath.Round使用して中間点を丸める方法を指定できますMidpointRounding。オーバーロードのMidpointRoundingないオーバーロードのそれぞれに対応する1つのオーバーロードがあります。

このデフォルトが適切に選択されたかどうかは、別の問題です。(MidpointRounding.NET 2.0でのみ導入されました。それ以前は、希望の動作を自分で実行せずに実装する簡単な方法があったかどうかはわかりません。)特に、履歴では、これが期待される動作ではないことが示されています。ほとんどの場合、 API設計における主要な罪。理由がわかりますBanker's Roundingが便利なますが、それでも多くの人にとって驚きです。

RoundingModeさらに多くのオプションを提供する、最も近いJavaの同等の列挙型()をご覧ください。(中点だけではありません。)


4
これがバグかどうかはわかりませんが、.5は最も近い最小整数に最も近いのと同じくらい近いので、仕様によるものだと思います。
スタンR.

3
.NETが適用される前のVBでのこの動作を覚えています。
ジョンフィアラ

7
実際、ドキュメントに記載されているIEEE標準754、セクション4。
Jon Skeet、

2
少し前にこれでやけどして、それも全くの狂気だと思いました。幸いにも、彼らは私たち全員が小学校で学んだ丸めを指定する方法を追加しました。MidPointRounding。
シェイ

26
「それは予期された動作ではない[...]それはAPI設計における主要な罪である」
BlueRaja-Danny Pflughoeft

215

これは、偶数への丸め(または銀行の丸め)と呼ばれます。これは、合計で発生したエラーを最小化するための有効な丸め戦略です(MidpointRounding.ToEven)。理論では、常に0.5の数値を同じ方向に丸めると、エラーがより速く発生します(偶数への丸めはそれを最小限に抑えることが想定されています)(a)

以下のMSDNの説明については、これらのリンクをたどってください。

  • Math.Floor、負の無限大に切り捨てます。
  • Math.Ceiling、正の無限大に切り上げます。
  • Math.Truncate、ゼロに向かって切り上げまたは切り下げます。
  • Math.Round、最も近い整数または指定した小数点以下の桁数に丸めます。最終桁が偶数になるように丸める( " Round(2.5,MidpointRounding.ToEven)"が2になる)か、ゼロから遠ざかる( " Round(2.5,MidpointRounding.AwayFromZero)"が3になる)など、2つの可能性の間で正確に等距離である場合の動作を指定できます。

次の図と表が役立つ場合があります。

-3        -2        -1         0         1         2         3
 +--|------+---------+----|----+--|------+----|----+-------|-+
    a                     b       c           d            e

                       a=-2.7  b=-0.5  c=0.3  d=1.5  e=2.8
                       ======  ======  =====  =====  =====
Floor                    -3      -1      0      1      2
Ceiling                  -2       0      1      2      3
Truncate                 -2       0      0      1      2
Round(ToEven)            -3       0      0      2      3
Round(AwayFromZero)      -3      -1      0      2      3

Roundそれは小数点以下の特定の番号に丸めることができますという理由だけで、それはそうよりも多くの強力です。他のすべては常に小数点以下をゼロに丸めます。例えば:

n = 3.145;
a = System.Math.Round (n, 2, MidpointRounding.ToEven);       // 3.14
b = System.Math.Round (n, 2, MidpointRounding.AwayFromZero); // 3.15

他の関数では、乗算/除算トリックを使用して同じ効果を達成する必要があります。

c = System.Math.Truncate (n * 100) / 100;                    // 3.14
d = System.Math.Ceiling (n * 100) / 100;                     // 3.15

(a)もちろん、その理論は、データの値が偶数の半分(0.5、2.5、4.5、...)と奇数の半分(1.5、3.5、...)にかなり均等に分散しているという事実に依存します。

場合は、すべての「半値は」(例えば)追いついているあなたは常に切り上げかのように、エラーは同じように高速に蓄積されます。


3
バンカーの丸め
ポンディダム

いい説明だ!エラーがどのように累積するのかを自分で確認したかったので、銀行の丸めを使用して丸めた値は、長期的には合計と平均が元の値に非常に近いことを示すスクリプトを書きました。github.com/AmadeusW/RoundingDemo(利用可能なプロットの写真)
Amadeusz Wieczorek

ほんの少し後:eティック(= 2.8)は2ティックよりも右にすべきではありませんか?
superjos

覚えるための簡単な方法で、10の位が5であると仮定します。-1の位と10の位はすべて奇数=切り上げ-1の場所と10の位は混合=切り捨て*ゼロは奇数ではありません*負の数の場合は逆
アーカムエンジェル

@ArkhamAngel、それは単に「最後の数字を
揃える

42

MSDNから、Math.Round(double a)は次を返します:

aに最も近い整数。aの小数部が2つの整数の中間にあり、一方が偶数で他方が奇数の場合、偶数が返されます。

...したがって、2.5は2と3の中間にあり、偶数(2)に切り捨てられます。これはバンカーの丸めと呼ばれます(または、一般的に使用される丸めの標準です。

同じMSDN記事:

このメソッドの動作は、IEEE標準754のセクション4に準拠しています。この種の丸めは、最も近い値への丸め、または銀行の丸めと呼ばれることがあります。これは、中間点の値を一方向に一貫して丸めることから生じる丸め誤差を最小限に抑えます。

MidpointRoundingモードをとるMath.Roundのオーバーロードを呼び出すことで、異なる丸め動作を指定できます。


37

あなたはMSDNをチェックする必要がありますMath.Round

このメソッドの動作は、IEEE標準754のセクション4に準拠しています。この種の丸めは、最も近い値への丸め、または銀行の丸めと呼ばれることがあります。

Math.Roundオーバーロードを使用する動作を指定できます。

Math.Round(2.5, 0, MidpointRounding.AwayFromZero); // gives 3

Math.Round(2.5, 0, MidpointRounding.ToEven); // gives 2

31

丸めの性質

分数を含む数値を、たとえば整数に丸めるタスクを考えてみましょう。この状況での丸めのプロセスは、丸めている数を最もよく表す整数を決定することです。

一般的な、または「算術」丸めでは、2.1、2.2、2.3、および2.4が2.0に丸められることは明らかです。2.6、2.7、2.8、および2.9から3.0。

これにより、2.5が残り、3.0よりも2.0に近くなります。2.0と3.0のどちらを選択するかはあなた次第ですが、どちらも同様に有効です。

マイナスの数値の場合、-2.1、-2.2、-2.3および-2.4は-2.0になります。-2.6、2.7、2.8、および2.9は、算術丸めでは-3.0になります。

-2.5の場合、-2.0と-3.0の間の選択が必要です。

その他の丸めの形式

「切り上げ」は小数点以下の任意の数値を取り、次の「整数」にします。したがって、2.5と2.6を3.0に丸めるだけでなく、2.1と2.2も四捨五入します。

切り上げにより、正の数と負の数の両方がゼロから離れます。例えば。2.5〜3.0および-2.5〜-3.0。

「切り捨て」は、不要な数字を切り捨てることによって数値を切り捨てます。これは、数値をゼロに向かって移動する効果があります。例えば。2.5〜2.0および-2.5〜-2.0

「銀行の丸め」では、最も一般的な形式で、丸められる.5は、丸めの結果が常に偶数になるように、上または下に丸められます。したがって、2.5は2.0、3.5から4.0、4.5から4.0、5.5から6.0などに丸められます。

'Alternate rounding'は、切り捨てと切り上げの間で任意の.5のプロセスを交互に行います。

「ランダム丸め」は、完全にランダムに0.5を切り上げまたは切り下げます。

対称性と非対称性

丸め関数は、すべての数値をゼロから離れるように丸めるか、すべての数値をゼロに向けて丸める場合、「対称」であると言います。

正の数をゼロに向かって丸め、負の数をゼロから遠ざける場合、関数は「非対称」です。2.5から2.0; および-2.5〜-3.0。

また、非対称は、正の数をゼロから遠ざけ、負の数をゼロに向かって丸める関数です。例えば。2.5から3.0; および-2.5〜-2.0。

ほとんどの場合、対称的な丸めについて考えます。-2.5は-3.0に向かって丸められ、3.5は4.0に丸められます。(C#でRound(AwayFromZero)


28

デフォルトMidpointRounding.ToEven、または銀行家の丸め(2.5は2、4.5は4など)が2、4.5)は、会計のレポートを書く前に私を悩ませてきたので、以前にそしてそれを調べた結果、私が見つけたもののいくつかの言葉を書きますこの郵便受け。

偶数を切り捨てているこれらの銀行家は誰ですか(おそらく英国の銀行家!)

ウィキペディアから

銀行家の四捨五入の用語の起源は、いまだにはっきりしていません。この丸め方法が銀行の標準であった場合、証拠を見つけるのは非常に困難であることがわかりました。それどころか、欧州委員会のレポートのセクション2「ユーロの導入と通貨額の丸め」では、銀行での丸めには標準的なアプローチが以前にはなかったことが示唆されています。また、「中間」の金額は切り上げられることを指定しています。

もちろん銀行が偶数の預金を大量に受け取るのに使用しない限り、これは特に銀行にとっては奇妙な丸め方法のようです。入金は240万ポンドですが、200万ポンドと呼びます。

IEEE規格754は1985年に遡り、両方の端数処理方法を提供しますが、規格では銀行家の推奨を採用しています。このウィキペディアの記事には、言語が丸めを実装する方法の長いリストがあり(以下のいずれかが間違っている場合は修正してください)、ほとんどがBankersを使用しませんが、学校で教えられた丸めを使用します。

  • math.hからのC / C ++の round()はゼロから丸めます(銀行員の丸めではありません)
  • Java Math.Roundはゼロから四捨五入します(結果をフロアし、0.5を追加し、整数にキャストします)。BigDecimalには別の方法があります
  • PerlはCと同様の方法を使用します
  • Javascriptは、JavaのMath.Roundと同じです。

情報のおかげで。私はこれに気づかなかった。数百万件の例は少しばかげていますが、セントで四捨五入しても、1000万の銀行口座に利息を支払う必要がある場合、ハーフセントをすべて切り上げると銀行に多くの費用がかかります。半セントは切り捨てられます。だから私はこれが合意された基準だと想像できます。これが実際に銀行家によって使用されているかどうかはわかりません。大部分の顧客は多くのお金を持っている間、切り捨てに気づかないでしょう、しかしあなたが顧客に優しい法律のある国に住んでいるならば、これは法律によって義務付けられていると想像できます
Harald Coppoolse

15

MSDNから:

デフォルトでは、Math.RoundはMidpointRounding.ToEvenを使用します。ほとんどの人は、代替手段として「均等に丸める」に慣れていないため、「ゼロから離れて丸める」ことは学校でより一般的に教えられています。.NETは、「ゼロから四捨五入」が切り捨てよりも頻繁に切り上げられる傾向を共有しないため、統計的に優れているため、「四捨五入」がデフォルトです。 )

http://msdn.microsoft.com/en-us/library/system.math.round.aspx


3

SilverlightはMidpointRoundingオプションをサポートしていないため、独自に作成する必要があります。何かのようなもの:

public double RoundCorrect(double d, int decimals)
{
    double multiplier = Math.Pow(10, decimals);

    if (d < 0)
        multiplier *= -1;

    return Math.Floor((d * multiplier) + 0.5) / multiplier;

}

これを拡張機能として使用する方法を含む例については、投稿を参照してください:.NETおよびSilverlight Rounding


3

私のSQLサーバーが0.5から1に切り上げるという問題がありましたが、C#アプリケーションは切り上げませんでした。したがって、2つの異なる結果が表示されます。

以下は、int / longを使用した実装です。これは、Javaが丸める方法です。

int roundedNumber = (int)Math.Floor(d + 0.5);

それはおそらくあなたが考えることもできる最も効率的な方法でしょう。

倍精度を維持し、小数精度を使用する場合は、小数点以下の桁数に基づいて10の指数を使用するだけです。

public double getRounding(double number, int decimalPoints)
{
    double decimalPowerOfTen = Math.Pow(10, decimalPoints);
    return Math.Floor(number * decimalPowerOfTen + 0.5)/ decimalPowerOfTen;
}

小数点には負の小数を入力でき、単語の細かさも同様です。

getRounding(239, -2) = 200


0

この投稿にはあなたが探している答えがあります:

http://weblogs.asp.net/sfurman/archive/2003/03/07/3537.aspx

基本的にこれはそれが言うことです:

戻り値

桁数に等しい精度の最も近い値。値が2つの数値の中間にあり、一方が偶数でもう一方が奇数の場合、偶数が返されます。値の精度が数字よりも小さい場合、値は変更されずに返されます。

このメソッドの動作は、IEEE標準754のセクション4に準拠しています。この種の丸めは、最も近い値への丸め、または銀行の丸めと呼ばれることがあります。桁がゼロの場合、この種の丸めは、ゼロへの丸めと呼ばれることがあります。


0

SilverlightはMidpointRoundingオプションをサポートしていません。MidpointRounding列挙型を追加するSilverlightの拡張メソッドを次に示します。

public enum MidpointRounding
{
    ToEven,
    AwayFromZero
}

public static class DecimalExtensions
{
    public static decimal Round(this decimal d, MidpointRounding mode)
    {
        return d.Round(0, mode);
    }

    /// <summary>
    /// Rounds using arithmetic (5 rounds up) symmetrical (up is away from zero) rounding
    /// </summary>
    /// <param name="d">A Decimal number to be rounded.</param>
    /// <param name="decimals">The number of significant fractional digits (precision) in the return value.</param>
    /// <returns>The number nearest d with precision equal to decimals. If d is halfway between two numbers, then the nearest whole number away from zero is returned.</returns>
    public static decimal Round(this decimal d, int decimals, MidpointRounding mode)
    {
        if ( mode == MidpointRounding.ToEven )
        {
            return decimal.Round(d, decimals);
        }
        else
        {
            decimal factor = Convert.ToDecimal(Math.Pow(10, decimals));
            int sign = Math.Sign(d);
            return Decimal.Truncate(d * factor + 0.5m * sign) / factor;
        }
    }
}

出典:http : //anderly.com/2009/08/08/silverlight-midpoint-rounding-solution/


-1

カスタム丸めを使用

public int Round(double value)
{
    double decimalpoints = Math.Abs(value - Math.Floor(value));
    if (decimalpoints > 0.5)
        return (int)Math.Round(value);
    else
        return (int)Math.Floor(value);
}

>.5と同じ動作をしMath.Roundます。問題は、小数部が正確にあるときに発生するかどうか0.5です。Math.Roundを使用すると、希望する丸めアルゴリズムの種類を指定できます
Panagiotis Kanavos

-2

これは地獄のように醜いですが、常に正しい算術丸めを生成します。

public double ArithRound(double number,int places){

  string numberFormat = "###.";

  numberFormat = numberFormat.PadRight(numberFormat.Length + places, '#');

  return double.Parse(number.ToString(numberFormat));

}

5
そのため、呼び出しMath.Round方法や丸め方の指定も行います。
コンフィギュレー

-2

これが私がそれを回避しなければならなかった方法です:

Public Function Round(number As Double, dec As Integer) As Double
    Dim decimalPowerOfTen = Math.Pow(10, dec)
    If CInt(number * decimalPowerOfTen) = Math.Round(number * decimalPowerOfTen, 2) Then
        Return Math.Round(number, 2, MidpointRounding.AwayFromZero)
    Else
        Return CInt(number * decimalPowerOfTen + 0.5) / 100
    End If
End Function

小数点以下2桁の1.905で試すと、期待どおり1.91がMath.Round(1.905,2,MidpointRounding.AwayFromZero)得られますが、1.90 が得られます。Math.Roundメソッドは、一貫性がなく、プログラマーが直面する可能性のあるほとんどの基本的な問題に対して使用できません。(int) 1.905 * decimalPowerOfTen = Math.Round(number * decimalPowerOfTen, 2)切り捨てる必要があるものを切り上げたくないのかどうかを確認する 必要があります。


Math.Round(1.905,2,MidpointRounding.AwayFromZero)リターン1.91
Panagiotis Kanavos
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.