代码之家  ›  专栏  ›  技术社区  ›  f0b0s

VisualC++数学

  •  4
  • f0b0s  · 技术社区  · 15 年前

    我正在调试我的项目,找不到错误。最后我找到了它。看看代码。你认为一切都好,结果会是“好的!好啊!好吧!”是吗?现在用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是周期性的。

    7 回复  |  直到 8 年前
        1
  •  36
  •   gnat Nat Poor    8 年前

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

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

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

    记住这一点:浮点比较既复杂又微妙,充满危险。小心。方式浮点 事实上 作品不同于大多数程序员的思维方式 应该 工作。如果您打算使用浮点,您需要了解它实际上是如何工作的…

        2
  •  10
  •   Stephen Canon    15 年前

    正如其他人所指出的,VS数学库正在X87 FPU上进行计算,并生成80位结果,即使类型是双精度的。

    因此:

    1. 调用cos(),并以80位浮点形式返回x87堆栈顶部的cos(x)
    2. cos(x)从x87堆栈中弹出并以双精度形式存储到内存中;这将使其四舍五入为64位浮点,从而 更改其值
    3. 调用cos(),并以80位浮点形式返回x87堆栈顶部的cos(x)
    4. 四舍五入的值从内存加载到x87堆栈中。
    5. cos(x)的四舍五入值和未四舍五入值比较不相等。

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

        3
  •  5
  •   Marcin Deptuła    15 年前

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

    00DB101A  call        _CIcos (0DB1870h) 
    00DB101F  fld         st(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          
    

    现在,奇怪的事情发生了。
    1。x加载到fstack上(x,0)
    2。x存储在正常堆栈上(截断)
    三。计算余弦,浮点数堆栈上的结果
    4。X再次加载
    5。x存储在正常堆栈上(截断,现在我们称之为“对称”)。
    6。堆栈上第一个余弦的结果存储在内存中,现在,第1个值发生了另一个截断。
    7。cosine是计算出来的,如果在float堆栈上则是第二个结果,但该值只被截断一次。
    8。第一个值加载到fstack,但该值被截断两次(计算cosine前一次,之后一次)
    9。比较这两个值-我们得到舍入误差。

        4
  •  5
  •   David Rodríguez - dribeas    15 年前

    你应该 从未 在大多数情况下,不要为了相等而比较双精度数。你可能得不到你所期望的。

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

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

        5
  •  1
  •   Jim Lewis    15 年前

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

        6
  •  0
  •   T.E.D.    15 年前

    增加和测试一个浮点值作为循环控制变量通常是一个非常糟糕的主意。如果必须,创建一个单独的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);
    }
    
        7
  •  -1
  •   Vitaly Dyatlov    15 年前

    如何解决问题?修改 如果 阻止:

    if ( (float)cos(x) == (float)cos(x) )