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

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

应该是3,不是吗?为什么在c#中它是2 ?

324375 次浏览

这被称为舍入到偶数(或银行家舍入),这是一个有效的舍入策略,可以最大限度地减少(MidpointRounding.ToEven)中的累积错误。理论是,如果你总是在同一个方向四舍五入一个0.5的数字,错误将会更快地累积(四舍五入到偶数应该是最小化)(一)

以下是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比它看起来强大得多,只是因为它可以舍入到小数点后的特定位数。其他的都是0小数。例如:

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

(一)当然,这个理论取决于这样一个事实:你的数据在偶数一半(0.5,2.5,4.5,…)和奇数一半(1.5,3.5,…)之间有一个相当均匀的值分布。

如果所有的“半值”是偶数(例如),错误会像你总是四舍五入一样快速累积。

从MSDN:

默认为Math。圆的使用 MidpointRounding.ToEven。大多数人 不熟悉“四舍五入? 甚至“作为替代”,四舍五入 “远离零”更常见 . net默认为 “四舍五入到偶数” 统计上的优势是因为 没有分享的倾向 四舍五入的意思是四舍五入 比它循环的频率稍高 向下(假设数字为 四舍五入往往是正的)

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

用。net舍入数字有你正在寻找的答案。

基本上它是这么说的:

返回值

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

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

舍入的性质

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

在普通或“算术”四舍五入中,很明显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之间进行选择。

其他形式的舍入

“四舍五入”取任何小数点后的数字,并使其成为下一个“整”数。因此,不仅2.5和2.6要四舍五入到3.0,2.1和2.2也要四舍五入到3.0。

四舍五入使正数和负数都远离零。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的过程在四舍五入和四舍五入之间交替进行。

“随机舍入”是在完全随机的基础上舍入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)中)

首先,无论如何这都不是c#错误——而是。net错误。c#是语言-它不决定Math.Round如何实现。

其次,不是——如果你阅读的文档,你会看到默认的舍入是“舍入到偶数”(银行家的舍入):

返回值
类型:系统。Double
最接近a的整数 a的分数分量是一半 两个整数之间,其中一个是 偶数和另一个奇数,然后是偶数 返回Number。注意这一点 方法返回Double而不是Double 积分类型。< / p >

讲话
此方法的行为遵循IEEE标准754, 第四节。这种舍入是 有时被称为四舍五入到最接近, 或者银行家四舍五入。它最小化 导致的舍入错误 始终舍入一个中点值

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

  • __abc2 / __abc3
  • __abc2 / __abc3
  • __abc2 / __abc3
  • __abc2 / __abc3

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

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

MSDN,数学。轮(双)返回:

最接近a的整数 a的分数分量是一半 两个整数之间,其中一个是 偶数和另一个奇数,然后是偶数

.返回Number

... 因此,位于2和3之间的2.5被四舍五入为偶数(2)。这被称为银行家的舍入(或四舍五入为偶数),是一个常用的四舍五入标准。

同一篇MSDN文章:

此方法的行为如下 IEEE标准754,第4节。这 四舍五入有时被称为 四舍五入到最接近的,或银行家的 舍入。它最小化舍入误差 这是持续舍入的结果 一个单一的中点值 方向。< / p >

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

你应该检查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

这是非常丑陋的,但总是产生正确的算术四舍五入。

public double ArithRound(double number,int places){


string numberFormat = "###.";


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


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


}

默认的MidpointRounding.ToEven,或Bankers'舍入(2.5变成2,4.5变成4,以此类推)曾经在写会计报告时刺过我,所以我将写几句话,谈谈我以前和为了这篇文章而研究它的发现。

那些四舍五入到偶数的银行家是谁(也许是英国银行家!)?

从维基百科

术语银行家的起源 四舍五入仍然比较模糊。如果这 四舍五入法曾经是一种标准 银行业,证据已经证明 很难找到。到 相反,欧洲的第2节 委员会报告的介绍 欧元和货币四舍五入 数量表明有 以前没有标准的方法 银行业的四舍五入;而且它 指定“中途”;量

这似乎是一种非常奇怪的四舍五入的方式,尤其是对银行业来说,当然,除非银行过去常常接受大量偶数的存款。押金240万英镑,但我们算200万英镑,先生。

IEEE标准754可以追溯到1985年,并给出了两种舍入方法,但标准推荐使用银行家的舍入方法。这个维基百科的文章有一个很长的语言如何实现舍入的列表(如果下面有任何错误请纠正我),大多数语言不使用Bankers',而是你在学校教过的舍入:

  • C / c++舍入()从math.h舍入到0(不是银行家舍入)
  • Java 数学。轮舍入0(它将结果取整,加0.5,强制转换为整数)。在BigDecimal中有一个替代方案
  • Perl < a href = " https://stackoverflow.com/questions/178539/how-do-you-round-a-floating-point-number-in-perl " > < / >使用与C类似的方法
  • Javascript与Java的Math.Round相同。

因为Silverlight不支持midpointrsurround选项,所以你必须自己编写。喜欢的东西:

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;


}

有关如何使用this作为扩展的示例,请参阅post: .NET和Silverlight round

我有这个问题,我的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

以下是我必须解决的方法:

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!数学。对于程序员可能遇到的大多数基本问题,Round方法是绝对不一致和不可用的。我必须检查是否(int) 1.905 * decimalPowerOfTen = Math.Round(number * decimalPowerOfTen, 2),因为我不想四舍五入应该四舍五入。

Silverlight不支持midpointrsurround选项。 下面是Silverlight的一个扩展方法,它添加了midpointrsurround枚举:

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/

使用自定义舍入

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

简单的方法是:

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