4

我正在调试我的项目,但找不到错误。最后我找到了它。看代码。您认为一切正常,结果将是“OK!OK!OK!”,不是吗?现在用VC编译它(我试过vs2005和vs2008)。

#include <math.h>
#include <stdio.h>


int main () {
    for ( double x = 90100.0; x<90120.0; x+=1 )
    {
        if ( cos(x) == cos(x) )
            printf ("x==%f  OK!\n", x);
        else
            printf ("x==%f  FAIL!\n", x);
    }

    getchar();
    return 0; 
}

神奇的双常数是 90112.0。当 x < 90112.0 时一切正常,当 x > 90112.0 时——不!你可以把cos改成sin。

有任何想法吗?不要忘记 sin 和 cos 是周期性的。

4

7 回答 7

36

可能是这样的:http: //www.parashift.com/c++-faq-lite/newbie.html#faq-29.18

我知道这很难接受,但浮点运算根本不像大多数人期望的那样工作。更糟糕的是,其中一些差异取决于您特定计算机的浮点硬件的详细信息和/或您在特定编译器上使用的优化设置。你可能不喜欢这样,但它就是这样。“得到它”的唯一方法是搁置你对事物应该如何表现的假设,并接受事物的实际表现......

(强调“经常”这个词;行为取决于您的硬件、编译器等):浮点计算和比较通常由通常包含特殊寄存器的特殊硬件执行,这些寄存器通常比double. 这意味着中间浮点计算通常具有比 更多的位sizeof(double),并且当将浮点值写入 RAM 时,它通常会被截断,通常会丢失一些精度...

请记住这一点:浮点比较是棘手的、微妙的并且充满危险。当心。浮点实际工作的方式与大多数程序员倾向于认为它应该工作的方式不同。如果您打算使用浮点,则需要了解它的实际工作原理......

于 2009-09-01T17:36:05.353 回答
10

正如其他人所指出的,VS 数学库正在 x87 FPU 上进行计算,并且即使类型为 double,也会生成 80 位结果。

因此:

  1. cos( ) 被调用,并以 x87 堆栈顶部的 cos(x) 作为 80 位浮点数返回
  2. cos(x) 从 x87 堆栈中弹出并作为双精度值存储到内存中;这会导致它被舍入到 64 位浮点数,这会改变它的值
  3. cos( ) 被调用,并以 x87 堆栈顶部的 cos(x) 作为 80 位浮点数返回
  4. 舍入后的值从内存加载到 x87 堆栈中
  5. cos(x) 的舍入值和未舍入值比较不相等。

许多数学库和编译器通过在可用时在 SSE 寄存器中以 64 位浮点数进行计算,或者通过在比较之前强制存储和舍入值,或者通过在实际计算中存储和重新加载最终结果来保护您免受这种情况的影响的 cos( )。您碰巧使用的编译器/库组合并不是那么宽容。

于 2009-09-01T17:49:34.797 回答
5

在大多数情况下,您永远不应该比较双精度数是否相等。你可能得不到你所期望的。

浮点寄存器可以具有与内存值不同的大小(在当前的英特尔机器中,FPU 寄存器是 80 位与 64 位双精度数)。如果编译器正在生成计算第一个余弦的代码,然后将值存储到内存中,计算第二个余弦并将内存中的值与寄存器中的值进行比较,那么这些值可能会有所不同(由于从 80 位到 64 位的舍入问题) .

浮点值有点棘手。谷歌浮点比较。

于 2009-09-01T17:39:21.820 回答
5

释放模式下生成的 cos(x) == cos(x) 过程:

00DB101A 调用_CIcos (0DB1870h)
00DB101F fld st(0)
00DB1021 富康普

该值计算一次,然后克隆,然后与自身比较 - 结果就可以了

在调试模式下也一样:

00A51405 子 esp,8
00A51408 fld qword ptr [x]
00A5140B fstp qword ptr [esp]
00A5140E 调用@ILT+270(_cos) (0A51113h)
00A51413 fld qword ptr [x]
00A51416 fstp qword ptr [esp]
00A51419 fstp qword ptr [ebp-0D8h]
00A5141F 调用 @ILT+270(_cos) (0A51113h)
00A51424 添加 esp,8
00A51427 fld qword ptr [ebp-0D8h]
00A5142D 富康普          

现在,奇怪的事情发生了。
1. X 加载到 fstack (X, 0)
2. X 存储在普通堆栈中(截断)
3. 计算余弦,结果在浮点堆栈上
4. X 再次加载
5. X 存储在普通堆栈中(截断,就目前而言,我们是“对称的”)
6. 堆栈上的第一个余弦的结果存储在内存中,现在,第一个值发生另一个截断
7. 计算余弦,如果在浮点堆栈上,则为第二个结果,但是这个值只被截断一次
8. 第一个值被加载到 fstack 上,但是这个值被截断了两次(一次在计算余弦之前,一次之后)
9. 比较这两个值 - 我们得到舍入错误。

于 2009-09-01T17:54:06.113 回答
1

编译器可能生成的代码最终将 64 位双精度值与 80 位内部浮点寄存器进行比较。测试浮点值是否相等很容易出现这类错误——你最好做一个“模糊”比较,比如 (fabs(val1 - val2) < EPSILON) 而不是 (val1 == val2)。

于 2009-09-01T17:40:36.077 回答
0

增加和测试浮点值作为循环控制变量通常是一个非常糟糕的主意。如果必须,创建一个单独的 int LCV 仅用于循环。

在这种情况下,它更容易:

for ( int i = 90100; i<90120; i+=1 )    {
    if ( cos(i) == cos(i) )
        printf ("i==%d  OK!\n", i);
    else
        printf ("i==%d  FAIL!\n", i);
}
于 2009-09-01T18:09:56.580 回答
-1

如何解决问题?修改if块:

if ( (float)cos(x) == (float)cos(x) )
于 2009-09-01T17:45:05.227 回答