2

我每隔几个月偶然发现一个错误是这个:

        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!");
        }

如果我在第一个示例中使用十进制而不是双精度,则结果是“x 等于 y”。
但我在问自己这是不是因为“十进制”类型不容易受到这种行为的影响,或者它只是在这种情况下起作用,因为值“适合”到 128 位。

也许有人有比使用常量更好的解决方案?

顺便提一句。这不是 dotNet/C# 问题,它发生在我认为的大多数编程语言中。

4

3 回答 3

6

只要您保持在适当范围内自然是小数的值内,小数将是准确的。因此,例如,如果您只是进行加减运算,而不做任何会使所需数字范围产生过多偏差的事情(将非常大的数字添加到非常小的数字中),您最终会得到易于比较的结果。乘法也可能没问题,但我怀疑它更容易出错。

一旦你开始除法,问题就来了——尤其是当你开始除以除 2 或 5 以外的素数时。

底线:在某些情况下它是安全的,但您确实需要准确掌握您将要执行的操作。

请注意,在这里帮助您的不是十进制的 128 位 - 它是将数字表示为浮点而不是浮点。有关更多信息,请参阅我关于 .NET二进制浮点十进制浮点的文章。

于 2009-07-09T07:24:57.497 回答
0

System.Decimal 只是一个具有不同基数的浮点数,因此从理论上讲,它仍然容易受到您指出的那种错误的影响。我认为您只是发生在没有发生舍入的情况下。更多信息在这里

于 2009-07-09T07:28:39.017 回答
0

是的,.NET System.Double 结构受您描述的问题的影响。

来自http://msdn.microsoft.com/en-us/library/system.double.epsilon.aspx

两个明显等价的浮点数可能不相等,因为它们的最低有效数字不同。例如,C# 表达式 (double)1/3 == (double)0.33333 不比较相等,因为左侧的除法运算具有最大精度,而右侧的常数仅精确到指定的数字。如果您创建一个自定义算法来确定两个浮点数是否可以被视为相等,您必须使用一个大于 Epsilon 常数的值来确定两个值相等的可接受的绝对差值。(通常,差异幅度比 Epsilon 大很多倍。)

于 2009-07-09T07:28:40.547 回答