457

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

应该是3吧?为什么在 C# 中是 2?

4

15 回答 15

603

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

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

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

备注
此方法的行为遵循 IEEE 标准 754 第 4 节。这种舍入有时称为四舍五入或银行家四舍五入。它可以最大限度地减少因在单个方向上持续舍入中点值而导致的舍入误差。

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

这个默认值是否选择得当是另一回事。(MidpointRounding仅在 .NET 2.0 中引入。在此之前,我不确定是否有任何简单的方法可以在不自己动手的情况下实现所需的行为。)特别是,历史表明这不是预期的行为——在大多数情况下,这是API 设计中的一大罪过。我明白为什么银行家的四舍五入很有用……但这仍然让很多人感到惊讶。

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

于 2009-06-10T19:53:19.477 回答
232

这称为舍入到偶数(或银行家的舍入),这是一种有效的舍入策略,可最大限度地减少 sum 中的应计误差(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、...)中具有相当均匀的值分布。

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

于 2009-05-11T06:14:34.130 回答
45

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

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

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

同一篇 MSDN 文章:

此方法的行为遵循 IEEE 标准 754 第 4 节。这种舍入有时称为四舍五入或银行家四舍五入。它可以最大限度地减少因在单个方向上持续舍入中点值而导致的舍入误差。

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

于 2009-06-10T19:53:31.753 回答
43

您应该检查 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
于 2009-06-10T19:53:47.820 回答
33

舍入的性质

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

通常,或“算术”舍入,很明显 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 也是如此。

四舍五入使正数和负数都远离零。例如。2.5 到 3.0 和 -2.5 到 -3.0。

“四舍五入”通过截断不需要的数字来截断数字。这具有将数字移向零的效果。例如。2.5 到 2.0 和 -2.5 到 -2.0

在“庄家四舍五入”中——最常见的形式——要四舍五入的 0.5 向上或向下四舍五入,因此四舍五入的结果始终为偶数。因此 2.5 轮到 2.0、3.5 到 4.0、4.5 到 4.0、5.5 到 6.0,依此类推。

“交替舍入”在向下舍入和向上舍入之间交替处理任何 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)

于 2009-05-11T06:25:10.210 回答
30

默认值MidpointRounding.ToEven,或银行家的四舍五入(2.5 变成 2,4.5 变成 4 等等)在写会计报告之前让我很痛苦,所以我会写一些我之前发现的东西和调查它这个帖子。

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

来自维基百科

银行家四舍五入一词的起源仍然比较模糊。如果这种四舍五入方法曾经是银行业的标准,那么证据证明是极难找到的。相反,欧盟委员会报告“欧元的引入和货币金额的四舍五入”的第 2 节表明,以前没有标准的方法来四舍五入银行业;并指定“中途”金额应四舍五入。

这似乎是一种非常奇怪的四舍五入方式,尤其是对银行业而言,除非银行当然习惯于接收大量等额存款。押金 240 万英镑,但先生,我们称之为 200 万英镑。

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

  • math.h 中的C/C++ round() 从零开始舍入(不是银行家的舍入)
  • Java Math.Round 从零开始四舍五入(它对结果进行下限,加 0.5,强制转换为整数)。BigDecimal有一个替代方案
  • Perl使用与 C 类似的方式
  • Javascript 与 Java 的 Math.Round 相同。
于 2009-10-22T10:12:00.983 回答
15

来自 MSDN:

默认情况下,Math.Round 使用 MidpointRounding.ToEven。大多数人不熟悉“四舍五入”作为替代选项,“从零四舍五入”在学校更常见。.NET 默认为“四舍五入”,因为它在统计上更优越,因为它不具有“从零四舍五入”的趋势,向上舍入的频率略高于向下舍入的频率(假设被四舍五入的数字往往是正数。 )

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

于 2009-05-11T06:14:41.757 回答
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 舍入

于 2011-11-10T19:27:59.997 回答
3

我遇到了这个问题,我的 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
于 2012-12-07T14:53:44.223 回答
0

使用 .NET 进行四舍五入可以找到您正在寻找的答案。

基本上这就是它所说的:

返回值

精度等于数字的最接近的数字。如果 value 介于两个数字之间,其中一个是偶数,另一个是奇数,则返回偶数。如果 value 的精度小于位,则 value 原样返回。

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

于 2009-05-11T06:21:29.467 回答
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/

于 2013-04-17T18:54:29.037 回答
0

简单的方法是:

Math.Ceiling(decimal.Parse(yourNumber + ""));
于 2017-10-29T07:05:37.693 回答
-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);
}
于 2016-04-20T04:34:53.620 回答
-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 和 2 位小数将得到 1.91,但Math.Round(1.905,2,MidpointRounding.AwayFromZero)会得到 1.90!对于程序员可能遇到的大多数基本问题,Math.Round 方法绝对不一致且无法使用。我必须检查是否 (int) 1.905 * decimalPowerOfTen = Math.Round(number * decimalPowerOfTen, 2)因为我不想四舍五入应该四舍五入的内容。

于 2013-04-13T20:56:44.000 回答
-3

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

public double ArithRound(double number,int places){

  string numberFormat = "###.";

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

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

}
于 2009-09-04T14:12:15.780 回答