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


415

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

应该是3,不是吗?为什么在C#中改为2?


5
这实际上是一个功能。请参阅<ahref=" msdn.microsoft.com/zh-cn/library/…MSDN文档</a>。这种舍入称为银行家舍入。作为一种解决方法,有<a href =“ msdn。 microsoft.com/en-us/library/…重载</a>,允许调用者指定如何进行舍入。

1
显然,在要求对两个整数之间的整数进行四舍五入时,round方法将返回偶数整数。因此,Math.Round(3.5)返回4。请参见本文
Matthew Jones,2009年

20
Math.Round(2.5, 0, MidpointRounding.AwayFromZero);
罗伯特·杜金2009年

SQL Server就是这样。有趣的测试结果,当存在C#单元测试ti验证在T-SQL中完成的舍入时。
idstam 2009年

7
@amed这不是一个错误。这就是二进制浮点的工作方式。1.005不能完全以双精度表示。可能是1.00499...。如果使用Decimal此问题将消失。IMO的Math.Round重载的存在需要双倍的十进制数字,这是IMO的可疑设计选择,因为它很少能以有意义的方式工作。
CodesInChaos 2011年

Answers:


560

首先,无论如何这都不是C#错误-而是.NET错误。C#是语言-它不确定如何Math.Round实现。

其次,否-如果您阅读了docs,则会看到默认的舍入是“四舍五入”(庄家的舍入):

返回值
类型:System.Double
最接近a的整数。如果a的小数部分位于两个整数的中间,其中一个为偶数,另一个为奇数,则返回偶数。请注意,此方法返回a Double而不是整数类型。

备注
此方法的行为遵循IEEE标准754,第4节。这种舍入有时称为“最接近的舍入”或“银行家的舍入”。它最大程度地减少了由于在一个方向上一致地舍入中点值而导致的舍入误差。

您可以指定如何Math.Round使用带值的重载舍入中点MidpointRounding。有一个重载,MidpointRounding每个重载对应一个重载,没有一个:

是否选择了该默认值是另一回事。(MidpointRounding仅在.NET 2.0中引入。在此之前,我不确定是否有任何简单的方法即可实现所需的行为而不自己做。)特别是,历史表明这不是预期的行为-在大多数情况下,这是预期的行为API设计中的一个主要罪过。我可以看到“ 银行家四舍五入” 为何有用...但是,这仍然令许多人感到惊讶。

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


4
我不知道这是否是一个错误,我认为这是设计使然,因为.5与最接近的最低整数一样接近。
Stan R.

3
我记得在应用.NET之前VB中的这种行为。
约翰·菲亚拉

7
实际上,文档中规定了IEEE标准754的第4节。
乔恩·斯基特

2
不久前,我被这个事弄得焦头烂额,以为那也是纯粹的疯狂。幸运的是,他们添加了一种方法来指定我们所有人在年级中学到的四舍五入;MidPointRounding。
乳木果

26
+1“这不是预期的行为[...]这是在API设计中的大忌”
BlueRaja -丹尼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)。

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

-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
也称为银行家四舍五入
Pondidum,

很好的解释!我想亲自看一下错误是如何累积的,我编写了一个脚本,该脚本显示从长远来看,使用banker的舍入取整的值的总和和平均值更接近于原始值。github.com/AmadeusW/RoundingDemo(可用地块图片)
Amadeusz Wieczorek

仅仅一小段时间:e勾号(= 2.8)是否应该比2勾号更远?
superjos

一种简单的记住方式,假设十分之一是5:-一个和第十个地方都是奇数=向上取整-一个和第十个地方都混合=向下取整*零不是奇数*反转为负数
阿卡姆天使

@ArkhamAngel,实际上似乎很难记住,而不仅仅是“使最后一位数字均匀” :-)
paxdiablo

42

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

最接近a的整数。如果a的小数部分位于两个整数的中间,其中一个为偶数,另一个为奇数,则返回偶数。

...因此介于2和3之间的2.5会四舍五入为偶数(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几乎不等于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等。

“备用四舍五入”会在四舍五入和四舍五入之间交替任何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节表明,以前没有标准的银行进位舍入方法。它指定应将“中途”金额四舍五入。

尤其对于银行而言,这似乎是一种非常奇怪的四舍五入方法,除非银行当然会使用收取偶数金额的大量存款。存入240万英镑,但我们称其为200万英镑。

IEEE 754标准可以追溯到1985年,并给出了两种取整方法,但是标准建议采用银行家标准。这篇Wikipedia文章列出了语言如何实现舍入的长长列表(如果以下任何一项有误,请纠正我),并且大多数不使用Bankers',而是在学校学习的舍入方法:

  • math.h中的C / C ++ round()舍入为零(不是银行家的舍入)
  • Java Math.Round舍入为零(入结果,将结果相加0.5,将其强制转换为整数)。BigDecimal中有一个替代方法
  • Perl使用与C类似的方式
  • Javascript与Java的Math.Round相同。

谢谢你的信息。我从来没有意识到这一点。您关于数百万美元的示例有点嘲笑它,但是即使您四舍五入,如果将所有半美分都四舍五入,则必须为一千万个银行帐户支付利息将使银行付出很多代价,或者如果将全部美分都舍入则将使客户付出很多代价。半美分四舍五入。所以我可以想象这是商定的标准。不确定银行家是否真的使用了此功能。大多数客户都不会注意到四舍五入,却带来了很多钱,但是我可以想象如果您居住在拥有客户友好法律的国家/地区,法律有义务这样做
Harald Coppoolse'8


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舍入


3

我遇到了这样的问题,我的SQL服务器将C#应用程序没有将其四舍五入为0.5到1。因此,您将看到两个不同的结果。

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

基本上就是这样说的:

返回值

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

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


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/


-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 2016年

-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并指定要四舍五入的方式也是如此。
Configurator

-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

尝试以两位小数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
帕纳吉奥蒂斯·卡纳沃斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.