cosf(M_PI_2)不返回零

Raj*_*yan 3 c math floating-point

今天早上突然开始.

这是原始的线条

float angle = (x+90)*(M_PI/180.0);
float xx = cosf(angle);
float yy = sinf(angle);
Run Code Online (Sandbox Code Playgroud)

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

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

我在今天的日期使用Xcode最新版本

Tim*_*Tim 10

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

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

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

  • 浮动肯定不总是*不准确,但正如我所说,它们本身*不准确.尽管某些值可以完美准确地表示,但是它们通常使用的值的类型往往不在此集合中,并且在每次计算中都会丢失一些精度*,而与所使用的浮点类型无关*.当然,有更多*准确和*更少*准确的方法,但它们的不准确程度不同. (4认同)

Ste*_*non 9

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

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

  1. 查看M_PI_2in 的扩展<math.h>.根据POSIX标准,它是一个双精度字面值,可以转换为正确舍入的π/ 2值.
  2. 将转换后的双精度值舍入为单精度.
  3. cosf在单精度值上调用数学库函数.

请注意,在整个过程中,您不会对π/ 2 的实际值进行操作.而是将该值四舍五入为可表示的浮点数.虽然cos(π/ 2)恰好为零,但您并没有告诉编译器进行该计算.而是告诉编译器做cos(π/ 2 + tiny),其中tiny是舍入值(float)M_PI_2和π/ 2的(不可表示的)精确值之间的差值.如果cos计算完全没有错误,则cos(π/ 2 +微小)的结果约为-tiny.如果它返回零,将是一个错误.

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

M_PI_2 被定义为

1.57079632679489661923132169163975144
Run Code Online (Sandbox Code Playgroud)

但这实际上并不是一个可表示的双精度数.当编译器将其转换为双精度值时,它就会完全变为

1.5707963267948965579989817342720925807952880859375
Run Code Online (Sandbox Code Playgroud)

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

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

1.57079637050628662109375
Run Code Online (Sandbox Code Playgroud)

大约是π/ 2 + 4.37*10 ^( - 8).当我们计算cosf这个数字时,我们得到:

-0.00000004371138828673792886547744274139404296875
Run Code Online (Sandbox Code Playgroud)

这是非常在这一点上评估余弦近精确值:

-0.00000004371139000186241438857289400265215231661...
Run Code Online (Sandbox Code Playgroud)

事实上,这是正确的圆润结果 ; 计算可能没有返回更准确的值.这里唯一的错误是您要求编译器执行的计算与您认为要求它执行的计算不同.


Goz*_*Goz 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 );
Run Code Online (Sandbox Code Playgroud)

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

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

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