为什么Math.Round(2.5)返回2而不是3?

时间:2009-06-10 19:51:06

标签: .net rounding

在C#中,Math.Round(2.5)的结果是2。

应该是3,不是吗?为什么它是2而不是C#?

15 个答案:

答案 0 :(得分:526)

首先,这不会是一个C#错误 - 它将是一个.NET错误。 C#是语言 - 它不决定如何实现Math.Round

其次,不,如果您阅读the docs,您会看到默认的舍入是“舍入到均匀”(银行家的舍入):

  

返回值
类型:System.Double
最接近a的整数。如果   a的分数成分是中途   两个整数之间,其中一个是   偶数和其他奇数,然后是偶数   号码被退回。请注意这一点   方法返回Double而不是   积分型。

     

备注
此方法的行为遵循IEEE标准754,   第4节。这种舍入是   有时被称为舍入到最近,   或银行家的四舍五入。它最小化   舍入由此产生的错误   始终围绕中点值   在一个方向。

您可以指定使用an overloadMidpointRounding值的Math.Round对中点进行舍入的方式。有一个MidpointRounding的重载对应于每个没有一个的重载:

这个默认选择是否合适是另一回事。 (MidpointRounding仅在.NET 2.0中引入。在此之前,我不确定是否有任何简单的方法可以实现所需的行为,而无需自己动手。)特别是,历史表明它不是预期的行为 - 在大多数情况下,这是API设计中的主要罪行。我可以看到为什么银行家的舍入很有用......但对许多人来说仍然是一个惊喜。

您可能有兴趣查看最近的Java等效枚举(RoundingMode),它提供了更多选项。 (它不只是处理中点。)

答案 1 :(得分:193)

这称为舍入到均匀(或银行家的舍入),这是一种有效的舍入策略,用于最小化总和(MidpointRounding.ToEven)中的累积错误。理论上说,如果你总是在同一个方向上舍入0.5的数字,那么错误会更快地产生(圆形到偶数应该最小化)(a)

请按照以下链接获取MSDN描述:

  • Math.Floor,向下舍入负无穷大。
  • Math.Ceiling,向正无穷大方向前进。
  • Math.Truncate,向上或向下舍入为零。
  • Math.Round,它会舍入到最接近的整数或指定的小数位数。如果是两种可能性,如四舍五入之间正好等距离您可以指定的行为,使最终的数字为偶数(“Round(2.5,MidpointRounding.ToEven)”成为二)左右,它是从零更远(“Round(2.5,MidpointRounding.AwayFromZero)”成为3)。

以下图表和表格可能有所帮助:

-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

使用其他函数,你必须使用multiply / divide trickery来达到同样的效果:

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,......)。

如果 all “半值”是平均值(例如),则错误将像您总是向上舍入一样快地累积。

答案 2 :(得分:42)

MSDN, Math.Round(double a)返回:

  

最接近a的整数。如果   a的分数成分是中途   两个整数之间,其中一个是   偶数和其他奇数,然后是偶数   号码被退回。

...等2.5,在2和3之间,向下舍入到偶数(2)。这称为Banker's Rounding(或舍入到偶数),是常用的舍入标准。

相同的MSDN文章:

  

此方法的行为如下   IEEE标准754,第4节   有时称为舍入   四舍五入到最近的,或银行家的   四舍五入。它最大限度地减少了舍入误差   持续四舍五入的结果   单个中点值   方向。

您可以通过调用采用MidpointRounding模式的Math.Round重载来指定不同的舍入行为。

答案 3 :(得分:37)

您应该检查Math.Round的MSDN:

  

此方法的行为遵循IEEE标准754第4节。这种舍入有时称为舍入到最近,或者是银行家的舍入。

您可以使用重载指定Math.Round的行为:

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

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

答案 4 :(得分:31)

舍入的性质

考虑将包含分数的数字四舍五入到整数的任务。在这种情况下舍入的过程是确定哪个整数最能代表你正在舍入的数字。

通常,或“算术”舍入,很明显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.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之间进行选择。

其他形式的舍入

'Rounding up'取任意小数位数,并使其成为下一个'整数'。因此,不仅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,依此类推。

'替代舍入'在舍入和舍入之间交替任何.5的过程。

'随机舍入'在完全随机的基础上向上或向下舍入.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#中{{1} })

答案 5 :(得分:24)

默认的MidpointRounding.ToEven,或者银行家的四舍五入( 2.5变成2,4.4变为4,依此类推)在写会计报告之前已经惹我生气了所以我会写一个我之前发现的,以及从这篇文章中查看它的几句话。

这些银行家是谁(即使是英国银行家也是如此!)?

来自维基百科

  

银行家一词的起源   四舍五入仍然更加模糊。如果这   舍入方法一直是标准   银行业,证据证明了这一点   很难找到。到了   相反,欧洲第2节   委员会报告引言   欧元和货币的四舍五入   金额表明有   以前没有标准方法   转向银行;而且它   指定“中途”金额   应该四舍五入。

这似乎是一种非常奇怪的四舍五入的方式,尤其是银行业务,除非银行当然会用来接收大量的偶数存款。存款240万英镑,但我们称之为200万英镑。

IEEE标准754可以追溯到1985年,并提供两种舍入方式,但银行家是标准推荐的方式。这个wikipedia article有一长串的语言如何实现四舍五入(如果下面的任何一个错误,请纠正我),大多数不使用银行家,但是你在学校教授的四舍五入:

    来自math.h的
  • C / C ++ round()从零开始(不是银行家的舍入)
  • Java Math.Round从零开始舍入(它将结果置于底层,添加0.5,转换为整数)。 BigDecimal
  • 中有另一种选择
  • Perl 使用与C
  • 类似的方式
  • Javascript与Java的Math.Round相同。

答案 6 :(得分:15)

来自MSDN:

  

默认情况下,Math.Round使用   MidpointRounding.ToEven。大多数人   不熟悉“四舍五入”   甚至“作为替代方案”,四舍五入   远离零“更常见   在学校教书。 .NET默认为   实际上是“四舍五入”   在统计上优越,因为它   不同意的倾向   “从零开始四舍五入”到四舍五入   比它更圆一些   向下(假设数字为   四舍五入往往是积极的。)

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

答案 7 :(得分: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 and Silverlight Rounding

答案 8 :(得分:2)

我有这个问题,我的SQL服务器向上舍入0.5比1,而我的C#应用​​程序没有。所以你会看到两个不同的结果。

这是一个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

答案 9 :(得分:0)

这篇文章有您正在寻找的答案:

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

基本上这就是它所说的:

返回值

数字最接近的值,精度等于数字。如果值在两个数字之间,其中一个是偶数而另一个是奇数,则返回偶数。如果值的精度小于数字,则返回值不变。

此方法的行为遵循IEEE标准754第4节。这种舍入有时称为舍入到最近,或者是银行家的舍入。如果数字为零,则这种舍入有时称为向零舍入。

答案 10 :(得分:0)

Silverlight不支持MidpointRounding选项。 这是Silverlight的扩展方法,它添加了MidpointRounding枚举:

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/

答案 11 :(得分:0)

使用自定义舍入

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);
}

答案 12 :(得分:0)

简单的方法是:

Math.Ceiling(decimal.Parse(yourNumber + ""));

答案 13 :(得分:-1)

以下是我必须解决的问题:

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)是否因为我不想围绕应该向下舍入的内容。

答案 14 :(得分:-2)

这很丑陋,但总能产生正确的算术四舍五入。

public double ArithRound(double number,int places){

  string numberFormat = "###.";

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

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

}