简体   繁体   English

Visual C ++ math.h错误

[英]Visual C++ math.h bug

I was debugging my project and could not find a bug. 我正在调试我的项目,但找不到错误。 Finally I located it. 最后我找到了它。 Look at the code. 看看代码。 You think everything is OK, and result will be "OK! OK! OK!", don't you? 你认为一切都好,结果将是“好的!好的!好的!”,不是吗? Now compile it with VC (i've tried vs2005 and vs2008). 现在用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; 
}

The magic double constant is 90112.0. 神奇的双常数是90112.0。 When x < 90112.0 everything is OK, when x > 90112.0 -- Nope! 当x <90112.0一切正常时,当x> 90112.0时 - 不! You can change cos to sin. 你可以将cos改为罪。

Any ideas? 有任何想法吗? Dont forget that sin and cos are periodic. 不要忘记罪和cos是周期性的。

Could be this: http://www.parashift.com/c++-faq-lite/newbie.html#faq-29.18 可能是这个: http//www.parashift.com/c++-faq-lite/newbie.html#faq-29.18

I know it's hard to accept, but floating point arithmetic simply does not work like most people expect. 我知道这很难接受,但浮点运算根本不像大多数人所期望的那样有效。 Worse, some of the differences are dependent on the details of your particular computer's floating point hardware and/or the optimization settings you use on your particular compiler. 更糟糕的是,一些差异取决于您的特定计算机的浮点硬件和/或您在特定编译器上使用的优化设置的详细信息。 You might not like that, but it's the way it is. 你可能不喜欢这样,但事实就是如此。 The only way to "get it" is to set aside your assumptions about how things ought to behave and accept things as they actually do behave... “得到它”的唯一方法就是抛开关于事物应该如何表现的假设,接受事物的确如此行为......

(with emphasis on the word "often"; the behavior depends on your hardware, compiler, etc.): floating point calculations and comparisons are often performed by special hardware that often contain special registers, and those registers often have more bits than a double . (重点是“经常”这个词;行为取决于你的硬件,编译器等):浮点计算和比较通常由通常包含特殊寄存器的特殊硬件执行,而这些寄存器通常比double寄存器更多。 That means that intermediate floating point computations often have more bits than sizeof(double) , and when a floating point value is written to RAM, it often gets truncated, often losing some bits of precision... 这意味着中间浮点计算通常具有比sizeof(double)更多的位,并且当浮点值写入RAM时,它经常被截断,通常会丢失一些精度......

just remember this: floating point comparisons are tricky and subtle and fraught with danger. 请记住这一点:浮点比较是棘手的,微妙的,充满了危险。 Be careful. 小心。 The way floating point actually works is different from the way most programmers tend to think it ought to work. 浮点实际工作的方式与大多数程序员认为它应该工作的方式不同。 If you intend to use floating point, you need to learn how it actually works... 如果你打算使用浮点数,你需要了解它是如何工作的......

As others have noted, the VS math library is doing its computation on the x87 FPU, and generating 80-bit results even though the type is double. 正如其他人所指出的那样,VS数学库正在x87 FPU上进行计算,即使类型为double,也会生成80位结果。

Thus: 从而:

  1. cos( ) is called, and returns with cos(x) on the top of the x87 stack as an 80bit float 调用cos(),并在x87堆栈的顶部以cos(x)作为80位浮点返回
  2. cos(x) is popped off the x87 stack and stored to memory as a double; cos(x)从x87堆栈中弹出并作为double存储到内存中; this causes it to be rounded to 64bit float, which changes its value 这导致它被舍入到64位浮点数,这会改变它的值
  3. cos( ) is called, and returns with cos(x) on the top of the x87 stack as an 80bit float 调用cos(),并在x87堆栈的顶部以cos(x)作为80位浮点返回
  4. the rounded value is loaded onto the x87 stack from memory 舍入值从内存加载到x87堆栈
  5. the rounded and unrounded values of cos(x) compare unequal. cos(x)的舍入和未舍入值比较不等。

Many math libraries and compilers protect you from this by either doing the computation in 64bit float in the SSE registers when available, or by forcing the values to be stored and rounded before the comparison, or by storing and reloading the final result in the actual computation of cos( ). 许多数学库和编译器通过在可用时在SSE寄存器中进行64位浮点计算,或者通过在比较之前强制存储和舍入值,或者通过在实际计算中存储和重新加载最终结果来保护您免受此影响。 cos()。 The compiler/library combination you happen to be working with isn't so forgiving. 您碰巧正在使用的编译器/库组合并不那么宽容。

You should never not compare doubles for equality in most cases. 在大多数情况下,你 绝不 应该将双打比较为平等。 You may not get what you expect. 你可能得不到你的期望。

Floating point registers can have a different size than memory values (in current intel machines, FPU registers are 80 bit vs 64 bit doubles). 浮点寄存器可以具有与存储器值不同的大小(在当前的intel机器中,FPU寄存器是80位对64位双倍)。 If the compiler is generating code that calculates the first cosine, then stores the value into memory, calculates the second cosine and compares the value in memory from that in the register then the values could differ (due to rounding issues from 80 to 64 bits). 如果编译器生成计算第一个余弦的代码,则将该值存储到存储器中,计算第二个余弦并将存储器中的值与寄存器中的值进行比较,然后值可能不同(由于80到64位的舍入问题) 。

Floating point values are a bit tricky. 浮点值有点棘手。 Google for floating point comparissons. 谷歌的浮点比较。

The cos(x) == cos(x) procedure generated in release mode: 在发布模式下生成的cos(x)== cos(x)过程:

00DB101A  call        _CIcos (0DB1870h) 
00DB101F  fld         st(0) 
00DB1021  fucompp

The value is computed once, and then cloned, then compared with itself - result will be ok 该值计算一次,然后克隆,然后与自身进行比较 - 结果将是正常的

The same in debug mode: 在调试模式下相同:

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

Now, strange things happen. 现在,奇怪的事情发生了。
1. X is loaded on fstack (X, 0) 1.在fstack(X,0)上加载X
2. X is stored on normal stack (truncation) 2. X存储在普通堆栈上(截断)
3. Cosine is computed, result on float stack 3.计算余弦,浮点堆栈上的结果
4. X is loaded again 4.再次加载X.
5. X is stored on normal stack (truncation, as for now, we are "symmetrical") 5. X存储在普通堆栈上(截断,就像现在一样,我们是“对称的”)
6. The result of 1st cosine that was on the stack is stored in memory, now, another truncation occurs for the 1st value 6.堆栈上的第一个余弦的结果存储在内存中,现在,第一个值发生了另一个截断
7. Cosine is computed, 2nd result if on the float-stack, but this value was truncated only once 7.计算余弦,如果在浮点堆栈上计算第二个结果,但此值仅被截断一次
8. 1st value is loaded onto the fstack, but this value was truncated twice (once before computing cosine, once after) 8.第一个值被加载到fstack上,但是这个值被截断两次(一次计算余弦之前,一次之后)
9. Those 2 values are compared - we're getting rounding errors. 9.比较这两个值 - 我们得到四舍五入的错误。

The compiler might have generated code that ends up comparing a 64-bit double value with an 80-bit internal floating point register. 编译器可能生成的代码最终将64位双精度值与80位内部浮点寄存器进行比较。 Testing floating point values for equality is prone to these sorts of errors -- you're almost always better off doing a "fuzzy" comparison like (fabs(val1 - val2) < EPSILON) rather than (val1 == val2). 测试浮点值的相等性容易出现这些错误 - 你几乎总是更好地进行“模糊”比较,比如(fabs(val1 - val2)<EPSILON)而不是(val1 == val2)。

Incrementing and testing a float value as a loop control variable is generally a really Bad Idea. 增加和测试浮点值作为循环控制变量通常是一个非常糟糕的想法。 Create a separate int LCV just for looping on, if you have to. 如果必须,创建一个单独的int LCV,仅用于循环。

In this case it's easier: 在这种情况下,它更容易:

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

How to around problem? 如何解决问题? Modify if block: 修改if块:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM