ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

它实际上是一个功能。请参阅 <a href="msdn.microsoft.com/en-us/library/… MSDN 文档</a>。这种舍入称为银行家舍入。至于解决方法,有 <a href="msdn.microsoft.com/en-us/library/… 重载</a>允许调用者指定如何进行舍入。
显然,当要求在两个整数之间精确舍入一个数字时,round 方法返回偶数。因此,Math.Round(3.5) 返回 4。参见 this article
Math.Round(2.5, 0, MidpointRounding.AwayFromZero);
SQL Server 就是这样循环的;当有一个 C# 单元测试 ti 验证在 T-SQL 中完成的舍入时,有趣的测试结果。
@amed 这不是错误。这是二进制浮点的工作方式。 1.005 不能完全用双精度表示。可能是1.00499...。如果您使用 Decimal,这个问题就会消失。 Math.Round 重载在 double 上采用多个十进制数字的存在是一个可疑的设计选择 IMO,因为它很少以有意义的方式工作。

J
Jon Skeet

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

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

返回值 类型:System.Double 最接近 a 的整数。如果 a 的小数部分介于两个整数之间,其中一个为偶数,另一个为奇数,则返回偶数。请注意,此方法返回 Double 而不是整数类型。备注 此方法的行为遵循 IEEE 标准 754 第 4 节。这种舍入有时称为四舍五入或银行家四舍五入。它可以最大限度地减少因在单个方向上持续舍入中点值而导致的舍入误差。

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

Round(Decimal) / Round(Decimal, MidpointRounding)

Round(Double) / Round(Double, MidpointRounding)

Round(Decimal, Int32) / Round(Decimal, Int32, MidpointRounding)

Round(Double, Int32) / Round(Double, Int32, MidpointRounding)

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

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


我不知道这是否是一个错误,我认为这是设计使然,因为 .5 与最接近的最低整数和最接近的最高整数一样接近。
我记得在应用 .NET 之前在 VB 中的这种行为。
实际上,文档所述的 IEEE 标准 754 第 4 节。
不久前我被这个烧伤了,并认为这也是纯粹的疯狂。幸运的是,他们添加了一种方法来指定我们所有人在小学时所学的四舍五入;中点舍入。
+1 表示“这不是预期的行为 [...] 这是 API 设计中的主要罪过”
p
paxdiablo

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

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


也称为银行家四舍五入
很好的解释!我想亲自看看错误是如何累积的,我编写了一个脚本,显示使用银行家四舍五入的值,从长远来看,它们的总和和平均值更接近原始值。 github.com/AmadeusW/RoundingDemo(可用地块图片)
不久之后:e 勾号 (= 2.8) 不应该比 2 勾号更靠右吗?
一个简单的记忆方法,假设第十位是 5: - 个位和第十位都是奇数 = 向上取整 - 个位和第十位混合 = 向下取整 * 零不是奇数 * 负数取反
@ArkhamAngel,这实际上似乎比“使最后一个数字相等”更难记住:-)
c
casperOne

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

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

...因此 2.5 介于 2 和 3 之间,向下舍入为偶数 (2)。这称为 Banker's Rounding(或四舍五入),是常用的四舍五入标准。

同一篇 MSDN 文章:

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

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


D
Dirk Vollmar

您应该在 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

P
Patrick Peters

舍入的性质

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

通常,或“算术”舍入,很明显 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)


C
Community

默认的 MidpointRounding.ToEven,或者银行家的四舍五入(2.5 变成 2,4.5 变成 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 相同。


谢谢提供信息。我从来没有意识到这一点。你关于数百万的例子有点嘲笑它,但即使你四舍五入,如果所有半美分都被四舍五入,那么必须支付 1000 万个银行账户的利息将使银行付出很多代价,或者如果全部舍入,客户也会付出很多代价半美分向下舍入。所以我可以想象这是公认的标准。不确定这是否真的被银行家使用。大多数客户不会注意到四舍五入,同时带来了很多钱,但我可以想象,如果你生活在一个对客户友好的法律的国家,这是法律规定的
C
Cristian Donoso

来自 MSDN:

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

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


J
JBrooks

由于 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


S
ShortFuse

我遇到了这个问题,我的 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

j
jascur2

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/


N
Nalan Madheswaran

简单的方法是:

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

你知道铸造的概念,对吧?
L
Lance U. Matthews

Rounding numbers with .NET 有您正在寻找的答案。

基本上这就是它所说的:

返回值

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

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


a
anandd360

使用自定义舍入

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 允许您指定所需的舍入算法类型
K
Kalu Singh Rao

这是我必须解决的方法:

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


Math.Round(1.905,2,MidpointRounding.AwayFromZero) 返回 1.91
J
James Montagne

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

public double ArithRound(double number,int places){

  string numberFormat = "###.";

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

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

}

调用 Math.Round 并指定您希望它如何舍入也是如此。