C++ visualc&x2B+;数学·h·bug

C++ visualc&x2B+;数学·h·bug,c++,floating-point,math.h,C++,Floating Point,Math.h,我正在调试我的项目,但找不到bug。最后我找到了它。看看代码。你们认为一切都很好,结果会是“好吧!好吧!好吧!”,不是吗?现在用VC编译它(我试过vs2005和vs2008) #包括 #包括 int main(){ 对于(double x=90100.0;x90112.0--Nope!您可以将cos更改为sin 有什么想法吗?别忘了正反两面都是周期性的。可能是这样的: 我知道这很难接受,但浮点运算并不像大多数人期望的那样工作。更糟糕的是,有些差异取决于特定计算机的浮点硬件的细节和/或特定编译器上

我正在调试我的项目,但找不到bug。最后我找到了它。看看代码。你们认为一切都很好,结果会是“好吧!好吧!好吧!”,不是吗?现在用VC编译它(我试过vs2005和vs2008)

#包括
#包括
int main(){
对于(double x=90100.0;x90112.0--Nope!您可以将cos更改为sin

有什么想法吗?别忘了正反两面都是周期性的。

可能是这样的:

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

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

请记住:浮点比较是微妙的,充满危险的。要小心。浮点的实际工作方式与大多数程序员认为它应该工作的方式不同。如果你打算使用浮点,你需要了解它的实际工作方式

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

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


浮点值有点棘手。用谷歌搜索浮点比较。

编译器可能生成的代码最终将64位双精度值与 80位内部浮点寄存器。测试浮点值是否相等
容易出现这种错误——你最好做一个“模糊”比较,比如(fabs(val1-val2)如何解决这个问题?修改ifblock:

if ( (float)cos(x) == (float)cos(x) )
如果((float)cos(x)==(float)cos(x))如其他人所述,则VS数学库正在x87 FPU上进行计算,并生成80位结果,即使类型为双精度

因此:

  • 调用cos(),并以80位浮点形式返回x87堆栈顶部的cos(x)
  • cos(x)从x87堆栈中弹出,并以双精度形式存储到内存中;这会使其四舍五入为64位浮点,从而更改其值
  • 调用cos(),并以80位浮点形式返回x87堆栈顶部的cos(x)
  • 舍入值从内存加载到x87堆栈
  • cos(x)的舍入值和未舍入值比较不相等
  • 许多数学库和编译器通过在SSE寄存器中以64位浮点值(如果可用)进行计算,或在比较之前强制存储和舍入值,或在实际计算cos()时存储和重新加载最终结果来保护您免受此影响。您碰巧使用的编译器/库组合不是很宽容。

    在发布模式下生成的cos(x)==cos(x)过程:

    00DB101A call _CIcos (0DB1870h) 00DB101F fld st(0) 00DB1021 fucompp 00DB101A呼叫(0DB1870h) 00DB101F fld街(0) 00DB1021 fucompp 该值被计算一次,然后被克隆,然后与自身进行比较-结果将是确定的

    在调试模式下也一样:

    00A51405 sub esp,8 00A51408 fld qword ptr [x] 00A5140B fstp qword ptr [esp] 00A5140E call @ILT+270(_cos) (0A51113h) 00A51413 fld qword ptr [x] 00A51416 fstp qword ptr [esp] 00A51419 fstp qword ptr [ebp-0D8h] 00A5141F call @ILT+270(_cos) (0A51113h) 00A51424 add esp,8 00A51427 fld qword ptr [ebp-0D8h] 00A5142D fucompp 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 fucompp 现在,奇怪的事情发生了。
    1.X加载到fstack(X,0)
    2.X存储在正常堆栈上(截断)
    3.计算余弦,浮点堆栈上的结果
    4.再次加载X
    5.X存储在普通堆栈上(截断,就目前而言,我们是“对称的”)
    6.堆栈上的第一个余弦的结果存储在内存中,现在,对第一个值进行另一次截断
    7.计算余弦,如果在浮点堆栈上,则为第二个结果,但该值仅被截断一次
    8.第一个值加载到fstack,但该值被截断两次(一次在计算余弦之前,一次在计算余弦之后)

    9.比较这两个值-我们得到了舍入误差。

    增加浮点值并将其作为循环控制变量进行测试通常是一个非常糟糕的主意。如果必须的话,创建一个单独的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);
    }
    

    <代码> >(int i=90100;Iu定位bug)-您没有本地化它。(我非常怀疑数学中存在bug。h。……很可能不是一个bug,但不精确的“IEEE764”特征通常漂浮在这个函数中。这在VS2008中很好(新项目的发布模式= > Win32 C++控制台应用的默认设置)@rstevents-但在调试模式下无法正常工作。在发布模式下,vc2008生成这样的代码:00DB101A调用_CIcos(0DB1870h)00DB101F fld st(0)00DB1021 fucompp,因此程序仅计算cos(x)
    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);
    }