cosf(M_PI_2)不返回零

时间:2009-10-22 06:51:36

标签: c math floating-point

今天早上突然开始。

这是原创的

float angle = (x+90)*(M_PI/180.0);
float xx = cosf(angle);
float yy = sinf(angle);

放入断点并悬停光标后..我得到yy的正确答案为1.但xx不为零。

我试过cosf(M_PI_2);仍然没有运气..直到昨天工作正常..我没有改变任何编译器设置等。

我正在使用截至今天的Xcode最新版本

6 个答案:

答案 0 :(得分:10)

首先要注意的是你正在使用float。这些本质上是不准确的,并且对于大多数计算,只给出了数学上正确答案的近似值。假设代码中的x值为0angle将近似于π/ 2。因此xx将近似为cos(π/ 2)。但是,由于近似和舍入问题,这不可能完全零。

如果您能够将代码更改为double而不是float,则可能会获得更高的准确性,并且答案会接近零。但是,如果此时代码产生的值非常重要,那么您将不得不重新考虑如何进行计算。

如果这不能解决您的特定问题,请向我们提供更多详细信息,我们会再考虑一下。

答案 1 :(得分:9)

与其他人所说的相反,这是 x87协处理器问题。默认情况下,XCode在Intel上使用SSE进行浮点计算(long double算术除外)。

“问题”是:当您编写cosf(M_PI_2)时,您实际上是在告诉XCode编译器(gcc或llvm-gcc或clang)执行以下操作:

  1. M_PI_2中查找<math.h>的扩展。根据POSIX标准,它是一个双精度字面值,可以转换为正确舍入的π/ 2值。
  2. 将转换后的双精度值舍入为单精度。
  3. 在单精度值上调用数学库函数cosf
  4. 请注意,在整个过程中,您不会对π/ 2的实际值进行操作。相反,您将对该值进行操作,舍入为可表示的浮点数。虽然cos(π/ 2)正好为零,但您并没有告诉编译器进行该计算。而是告诉编译器做cos(π/ 2 + tiny),其中tiny是舍入值(float)M_PI_2和π/ 2的(不可表示的)精确值之间的差值。如果cos的计算完全没有错误,则cos(π/ 2 +微小)的结果约为-tiny。如果它返回零,那么 将是一个错误。

    编辑:使用当前的XCode编译器逐步扩展Intel Mac上的计算:

    M_PI_2定义为

    1.57079632679489661923132169163975144
    

    但实际上并不是可表示的双精度数。当编译器将其转换为双精度值时,它就变为

    1.5707963267948965579989817342720925807952880859375
    

    这是最接近π/ 2的双精度数,但它与π/ 2的实际数学值相差约6.12 * 10 ^( - 17)。

    步骤(2)将此数字舍入为单精度,将值更改为

    1.57079637050628662109375
    

    大约π/ 2 + 4.37 * 10 ^( - 8)。当我们计算此数字的cosf时,我们得到:

    -0.00000004371138828673792886547744274139404296875
    

    非常几乎是此时评估的余弦的精确值:

    -0.00000004371139000186241438857289400265215231661...
    

    实际上,它是正确舍入的结果;计算可能没有返回更准确的值。这里唯一的错误是你要求编译器执行的计算与你要求它做的思考的计算不同。

答案 2 :(得分:6)

我怀疑答案尽可能接近0而不值得担心。

如果我通过相同的操作得到答案“-4.3711388e-008”,也可以写成“-0.000000043711388”。这是非常接近0.非常接近,不用担心它在小数点后8位。

编辑:继LiraLuna所说的,我在visual studio下编写了下面的x87汇编程序

    float fRes;
_asm
{
    fld1
    fld1
    fadd st, st(1)
    fldpi
    fdiv st, st(1)
    fcos
    fstp [fRes]
}
char str[16];
sprintf( str, "%f", fRes );

基本上,这使用x87的fcos指令来执行pi / 2的余弦。 str中保存的值为“0.000000”

然而,这并不是fcos返回的。它实际返回6.1230318e-017。这意味着错误发生在第17个小数位,而且说实话,这远远不如上面的标准调试cosf那么重要。

由于SSE3没有特定的余弦指令,我怀疑(虽然我无法确认没有看到汇编器生成)它是使用自己的泰勒系列扩展还是使用fcos指令。无论哪种方式,在我看来,你仍然不可能获得比在第17个小数位发生的错误更好的精确度。

答案 3 :(得分:3)

我唯一能想到的是恶意宏观替代品,即M_PI_2不再是1.57079632679489661923。

尝试拨打cosf( 1.57079632679489661923 )进行测试。

答案 4 :(得分:1)

你应该注意的是余弦的符号。确保它与您的预期相同。例如。如果你使用0到pi / 2之间的角度操作。确保确定 您使用的PI_2小于pi / 2的实际值

0.0000010.0之间的差异小于您的想法。

答案 5 :(得分:0)

原因

您遇到的是臭名昭着的x87 math co-processor浮动截断'错误' - 或者更确切地说 - 一个功能。 IEEE float具有惊人的数字范围,但需要付出代价。他们牺牲了高射程的进动。

它们并不像你想象的那样不准确 - 这是英特尔x87芯片设计产生的半神话,内部使用80位内部代表浮点数 - 虽然速度稍慢,但它们具有更优越的进动性。

执行浮点比较时,x87将浮点缓存为80位浮点数,然后当它的堆栈已满时,它会将32位表示保存在RAM中,从而在很大程度上降低精度。

解决方案

x87很旧,很老了。它的替代是SSE。 SSE原生计算32位浮点数和64位浮点数,导致数学上的最小进动损失。请注意浮动的进动问题仍然存在,但printf("%f\n", cosf(M_PI_2));应为零。哎呀 - 甚至浮动比较与SSE再次准确! (与x87不同)。

由于最新的Xcode实际上是GCC 4.2.1,请使用编译器开关-msse3 -mfpmath=sse,看看如何获​​得完美的圆0.00000(注意:如果你得到-0.00000,请不要担心,它完全没问题,仍然等于IEEE规范下的0.00000(详见wikipedia文章))。

所有英特尔Mac 保证具有SSE3支持(OSx86 Mac排除,如果您想支持这些,请使用-msse2)。