我每隔几个月偶然发现一个错误就是这个错误:
double x = 19.08;
double y = 2.01;
double result = 21.09;
if (x + y == result)
{
MessageBox.Show("x equals y");
}
else
{
MessageBox.Show("that shouldn't happen!"); // <-- this code fires
}
你会认为代码显示“x等于y”,但事实并非如此 简短的解释是小数位表示为二进制数字,不适合双倍。
实施例: 2.625看起来像:
10.101
因为
1-------0-------1---------0----------1
1 * 2 + 0 * 1 + 1 * 0.5 + 0 * 0.25 + 1 * 0,125 = 2.65
有些值(如19.08加上2.01的结果)不能用双精度表示。
一种解决方案是使用常量:
double x = 19.08;
double y = 2.01;
double result = 21.09;
double EPSILON = 10E-10;
if ( x + y - result < EPSILON )
{
MessageBox.Show("x equals y"); // <-- this code fires
}
else
{
MessageBox.Show("that shouldn't happen!");
}
如果我在第一个例子中使用decimal而不是double,则结果为“x equals y” 但我问自己如果这是因为“十进制”类型不容易受到这种行为的影响,或者只是在这种情况下工作,因为值“适合”到128位。
也许某人有比使用常数更好的解决方案?
顺便说一下。这不是dotNet / C#问题,它发生在我认为的大多数编程语言中。
答案 0 :(得分:6)
十进制将是准确的。因此,如果您只是添加和减去,例如,不做任何会使所需数字范围偏差太大的任何事情(将非常大的数字添加到非常小的数字),您最终会得到容易比较的结果。乘法也可能没问题,但我怀疑使用它会更容易出错。
一旦你开始分裂,那就是问题可能发生的地方 - 特别是如果你开始除以包括2或5以外的素数因子的数字。
底线:在某些情况下它是安全的,但你真的需要很好地处理你将要执行的操作。
请注意,这不是帮助你的十进制的128位 - 它是数字表示为浮点十进制点值而不是浮点二进制点值。有关详细信息,请参阅有关.NET binary floating point和decimal floating point的文章。
答案 1 :(得分:0)
System.Decimal只是一个具有不同基数的浮点数,所以从理论上讲,它仍然容易受到你指出的那种错误的影响。我想你刚刚发生了一个没有发生舍入的情况。更多信息here。
答案 2 :(得分:0)
是的,.NET System.Double结构会受到您描述的问题的影响。
来自http://msdn.microsoft.com/en-us/library/system.double.epsilon.aspx:
两个明显等效的浮点数可能无法比较,因为它们的最低有效位数不同。例如,C#表达式(double)1/3 ==(double)0.33333,不比较相等,因为左侧的除法运算具有最大精度,而右侧的常量仅精确到指定的数字。如果您创建一个自定义算法来确定是否可以将两个浮点数视为相等,则必须使用大于Epsilon常量的值来确定可接受的两个值的绝对差值,以使两个值相等。 (通常,差异幅度比Epsilon大很多倍。)