为啥 Math.Round(2.5) 返回 2 而不是 3?
Posted
技术标签:
【中文标题】为啥 Math.Round(2.5) 返回 2 而不是 3?【英文标题】:Why does Math.Round(2.5) return 2 instead of 3?为什么 Math.Round(2.5) 返回 2 而不是 3? 【发布时间】:2010-11-01 22:39:27 【问题描述】:在 C# 中,Math.Round(2.5)
的结果是 2。
应该是3,不是吗?为什么在 C# 中是 2?
【问题讨论】:
其实是一个特性。请参阅 msdn.microsoft.com/en-us/library/…MSDN 文档。这种舍入称为银行家舍入。至于解决方法,有 msdn.microsoft.com/en-us/library/…overload 允许调用者指定如何进行舍入。 显然 round 方法,当被要求在两个整数之间精确地舍入一个数字时,返回偶数。所以,Math.Round(3.5) 返回 4。见this articleMath.Round(2.5, 0, MidpointRounding.AwayFromZero);
SQL Server 以这种方式循环;当有一个 C# 单元测试 ti 验证在 T-SQL 中完成的舍入时,有趣的测试结果。
@amed 这不是错误。这是二进制浮点的工作方式。 1.005
不能完全用双精度表示。可能是1.00499...
。如果你使用Decimal
,这个问题就会消失。 Math.Round 重载在 double 上采用多个小数位的存在是 IMO 一个可疑的设计选择,因为它很少以有意义的方式工作。
【参考方案1】:
这称为舍入到偶数(或银行家的舍入),这是一种有效的舍入策略,可最大限度地减少总和(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
tick (= 2.8) 不应该比2
tick 更右边吗?
一个简单的记忆方法,假设第十位是 5: - 个位和第十位都是奇数 = 向上取整 - 个位和第十位混合 = 向下取整 * 零不是奇数* 反转为负数
@ArkhamAngel,实际上这似乎更难记住,而不仅仅是“使最后一个数字相等”:-)【参考方案2】:
来自 MSDN:
默认情况下,Math.Round 使用 MidpointRounding.ToEven。大多数人 不熟悉“四舍五入” 甚至”作为替代,“四舍五入 远离零”更常见 在学校任教。 .NET 默认为 “四舍五入”,因为它是 统计上优越,因为它 不认同的倾向 “从零四舍五入”来四舍五入 比它更频繁 向下(假设数字是 四舍五入往往是积极的。)
http://msdn.microsoft.com/en-us/library/system.math.round.aspx
【讨论】:
【参考方案3】:这篇文章有你正在寻找的答案:
http://weblogs.asp.net/sfurman/archive/2003/03/07/3537.aspx
基本上是这样写的:
返回值
精度等于数字的最接近值的数字。如果 value 介于两个数字之间,其中一个是偶数,另一个是奇数,则返回偶数。如果 value 的精度小于数字,则 value 原样返回。
此方法的行为遵循 IEEE 标准 754 第 4 节。这种四舍五入有时称为四舍五入或银行家四舍五入。如果数字为零,这种舍入有时称为向零舍入。
【讨论】:
【参考方案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 也是如此。
四舍五入使正数和负数都远离零。例如。 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)
)
【讨论】:
【参考方案5】:首先,这无论如何都不是 C# 错误 - 它将是 .NET 错误。 C# 是语言 - 它不决定如何实现 Math.Round
。
其次,不——如果您阅读the docs,您会看到默认舍入是“舍入到偶数”(银行家的舍入):
返回值类型:System.Double最接近 a 的整数。如果 a 的小数部分是一半 在两个整数之间,其中之一是 偶数和另一个奇数,然后偶数 返回号码。请注意,这 方法返回
Double
而不是 整型。备注此方法的行为遵循 IEEE 标准 754, 第 4 节。这种舍入是 有时称为四舍五入, 或银行家的四舍五入。它最小化 舍入误差导致 始终舍入中点值 一个方向。
您可以使用 an overload 指定 Math.Round
应如何舍入中点,该值采用 MidpointRounding
值。有一个带有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 中引入。在此之前,我不确定是否有任何简单的方法可以在不自己动手的情况下实现所需的行为。)特别是,历史表明它不是 预期的 行为——在大多数情况下,这是 API 设计中的一个大罪。我可以看到为什么银行家的四舍五入很有用......但它仍然让很多人感到惊讶。
您可能有兴趣查看最近的 Java 等效枚举 (RoundingMode
),它提供了更多选项。 (它不只是处理中点。)
【讨论】:
我不知道这是否是一个错误,我认为这是设计使然,因为 .5 与最接近的最低整数和最接近的最高整数一样接近。 我记得在应用 .NET 之前在 VB 中的这种行为。 确实,IEEE 标准 754,第 4 节,如文档所述。 前段时间我被这件事弄得焦头烂额,并认为这也太疯狂了。幸运的是,他们添加了一种方法来指定我们所有人在小学时所学的四舍五入;中点舍入。 +1 表示“这不是预期的行为 [...] 这是 API 设计中的主要罪过”【参考方案6】:来自MSDN, Math.Round(double a) 返回:
最接近a的整数。如果 a 的小数部分是一半 在两个整数之间,其中之一是 偶数和另一个奇数,然后偶数 返回数字。
... 所以 2.5,介于 2 和 3 之间,向下舍入到偶数 (2)。这称为Banker's Rounding(或四舍五入),是一种常用的四舍五入标准。
同一篇 MSDN 文章:
此方法的行为如下 IEEE 标准 754,第 4 节。这 有时称为舍入 四舍五入到最近的或银行家的 四舍五入。它最大限度地减少舍入误差 这是不断四舍五入的结果 单个中点值 方向。
您可以通过调用采用MidpointRounding
模式的 Math.Round 的重载来指定不同的舍入行为。
【讨论】:
【参考方案7】:您应该查看 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
【讨论】:
【参考方案8】:这很丑陋,但总能产生正确的算术舍入。
public double ArithRound(double number,int places)
string numberFormat = "###.";
numberFormat = numberFormat.PadRight(numberFormat.Length + places, '#');
return double.Parse(number.ToString(numberFormat));
【讨论】:
调用Math.Round
并指定您希望它如何舍入也是如此。【参考方案9】:
默认的MidpointRounding.ToEven
,或者银行家的四舍五入(2.5 变成 2,4.5 变成 4 等等)在写会计报告之前让我很痛苦,所以我会写几句话我之前发现的内容,以及在这篇文章中查看的内容。
这些对偶数进行四舍五入的银行家是谁(也许是英国银行家!)?
来自***
银行家一词的由来 四舍五入仍然更加模糊。如果这 四舍五入法曾经是一种标准 银行,证据证明 极难找到。到 相反,欧洲第 2 条 委员会报告介绍 欧元和货币的四舍五入 金额表明有 以前没有标准方法 银行业四舍五入;它 指定“中途”金额 应该四舍五入。
这似乎是一种非常奇怪的四舍五入方式,尤其是对银行业而言,除非银行当然习惯于接收大量等额存款。押金 240 万英镑,但先生,我们称之为 200 万英镑。
IEEE 标准 754 可追溯到 1985 年,提供了两种舍入方式,但标准推荐使用银行家的方式。这个wikipedia article 有一长串语言如何实现四舍五入(如果以下任何一个错误,请纠正我)并且大多数不使用银行家,但你在学校教的四舍五入:
C/C++ 来自 math.h 的 round() 从零开始舍入(不是银行家的舍入) Java Math.Round 从零开始舍入(它将结果取整,加 0.5,转换为整数)。 BigDecimal 有一个替代方案 Perl 使用与 C 类似的方式 javascript 与 Java 的 Math.Round 相同。【讨论】:
感谢您的信息。我从来没有意识到这一点。你关于数百万的例子有点嘲笑它,但即使你四舍五入,如果所有半美分都被四舍五入,那么必须支付 1000 万个银行账户的利息将使银行付出很多代价,或者如果全部舍入,客户也会付出很多代价半美分向下舍入。所以我可以想象这是公认的标准。不确定这是否真的被银行家使用。大多数客户不会注意到四舍五入,同时带来了很多钱,但我可以想象,如果你生活在一个有利于客户的法律的国家/地区,这是法律规定的【参考方案10】:由于 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
【讨论】:
【参考方案11】:我遇到了这个问题,我的 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
【讨论】:
【参考方案12】:这是我必须解决的方法:
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
【参考方案13】:
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/
【讨论】:
【参考方案14】:使用自定义舍入
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 允许您指定所需的舍入算法类型【参考方案15】:
简单的方法是:
Math.Ceiling(decimal.Parse(yourNumber + ""));
【讨论】:
你知道铸造的概念吧?以上是关于为啥 Math.Round(2.5) 返回 2 而不是 3?的主要内容,如果未能解决你的问题,请参考以下文章