在 C# 中,Math.Round(2.5)
的结果是 2。
应该是3吧?为什么在 C# 中是 2?
Math.Round(2.5, 0, MidpointRounding.AwayFromZero);
1.005
不能完全用双精度表示。可能是1.00499...
。如果您使用 Decimal
,这个问题就会消失。 Math.Round 重载在 double 上采用多个十进制数字的存在是一个可疑的设计选择 IMO,因为它很少以有意义的方式工作。
首先,这无论如何都不会是 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
),它提供了更多选项。 (它不只是处理中点。)
这称为舍入到偶数(或银行家的舍入),这是一种有效的舍入策略,可最大限度地减少总和 (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、...)中具有相当均匀的值分布。
如果所有“半值”都是偶数(例如),则错误将像您总是四舍五入一样快速累积。
e
勾号 (= 2.8) 不应该比 2
勾号更靠右吗?
从 MSDN, Math.Round(double a) 返回:
最接近 a 的整数。如果 a 的小数部分介于两个整数之间,其中一个为偶数,另一个为奇数,则返回偶数。
...因此 2.5 介于 2 和 3 之间,向下舍入为偶数 (2)。这称为 Banker's Rounding(或四舍五入),是常用的四舍五入标准。
同一篇 MSDN 文章:
此方法的行为遵循 IEEE 标准 754 第 4 节。这种四舍五入有时称为四舍五入或银行家四舍五入。它可以最大限度地减少因在单个方向上持续舍入中点值而导致的舍入误差。
您可以通过调用采用 MidpointRounding
模式的 Math.Round 的重载来指定不同的舍入行为。
您应该在 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
舍入的性质
考虑将包含分数的数字四舍五入为整数的任务。在这种情况下四舍五入的过程是确定哪个整数最能代表您要四舍五入的数字。
通常,或“算术”舍入,很明显 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)
)
默认的 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 相同。
来自 MSDN:
默认情况下,Math.Round 使用 MidpointRounding.ToEven。大多数人不熟悉“四舍五入”作为替代选项,“从零四舍五入”在学校更常见。 .NET 默认为“四舍五入”,因为它在统计上更优越,因为它不具有“从零四舍五入”的趋势,向上舍入的频率略高于向下舍入的频率(假设被四舍五入的数字往往是正数。 )
http://msdn.microsoft.com/en-us/library/system.math.round.aspx
由于 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
我遇到了这个问题,我的 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
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/
简单的方法是:
Math.Ceiling(decimal.Parse(yourNumber + ""));
Rounding numbers with .NET 有您正在寻找的答案。
基本上这就是它所说的:
返回值
精度等于数字的最接近的数字。如果 value 介于两个数字之间,其中一个是偶数,另一个是奇数,则返回偶数。如果 value 的精度小于位,则 value 原样返回。
此方法的行为遵循 IEEE 标准 754 第 4 节。这种四舍五入有时称为四舍五入或银行家四舍五入。如果数字为零,这种舍入有时称为向零舍入。
使用自定义舍入
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 允许您指定所需的舍入算法类型
这是我必须解决的方法:
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
这很丑陋,但总是产生正确的算术舍入。
public double ArithRound(double number,int places){
string numberFormat = "###.";
numberFormat = numberFormat.PadRight(numberFormat.Length + places, '#');
return double.Parse(number.ToString(numberFormat));
}
Math.Round
并指定您希望它如何舍入也是如此。