在VS2013中打破了毕达哥拉斯三角恒等式规则

时间:2015-09-01 04:31:16

标签: c++ visual-studio trigonometry math.h

升级到Visual Studio 2013之后,我注意到一些基本的数学函数变得不同,例如来自<cmath><math.h>的sin()。这可能是一个很好的变化,但一些测试显示其中一个主要的数学规则被打破。这是测试功能:

void Test()
{
  double d_angle = M_PI_2 / 2;

  auto sin_val = sin(d_angle);
  auto cos_val = cos(d_angle);
  auto sum_of_squares = sin_val*sin_val + cos_val*cos_val;

  Assert::AreEqual(1.0, sum_of_squares, 1e-16);
}

此测试在VS2013中失败,但在VS2012中通过。

如何处理这种情况并获得VS2012的结果?

1 个答案:

答案 0 :(得分:2)

基本问题是浮点数学不是真正的数学(或者至少不是实数集的数学)。 IEEE 754双精度浮点数具有53位尾数。您Assert::AreEqual指定sum_of_squares的尾数应为1位,后跟52为零位±1e-16。

最低有效位中的错误是1±2 -52 = 1±2.220446e-16,因此如果最低有效位的回合不同,则Assert::AreEqual将失败。基本上,你是说答案必须以0错误计算。

显然,VS2012得到的答案完全正确,现在VS2013没有。这可能有以下几种原因:

  1. VS2013没有符合IEEE 754标准的数学运算。数学错误在过去发生过,但很少见。如果你获得的结果在最不重要的位置偏离不到一位,则不太可能是编译器或数学库错误。

  2. VS2013编译器重新排序浮点运算,这会改变部分结果的舍入方式。如果结果发生变化,编译器不应该对浮点运算进行重新排序,但是大多数都有优化标志,允许重新排序,当数字会改善性能时,数学允许重新排序(例如,gcc中的-ffast-math)。除非您一直在试验编译器选项,否则这可能不是问题。

  3. VS2013编译器优化器决定使用什么混合的x87和SSE / SSE2指令,或者它改变了寄存器分配的方式。 x87 FPU具有80位浮点寄存器,而SSE2具有64位浮点寄存器。如果编译器在x87浮点寄存器中保持计算并且仅存储最终结果,则该结果的舍入方式可能与使用SSE2计算的结果不同,因为用于中间值的尾数较长。

  4. 由于寄存器压力,编译器会将中间结果强制进入内存。结果,一个或多个中间值被截断为64位浮点数。如此短的计划,这是不可能的。

  5. 如果你将delta参数碰到Assert::AreEqual高达2.23e-16并且它通过了,那么不同之处在于最低有效位是舍入的。

    根据VS2013 documentation for the /arch compiler option

      

    当指定/ arch时,优化器选择何时以及如何使用SSE和SSE2指令。当它确定使用SSE / SSE2指令和寄存器而不是x87浮点寄存器堆栈更快时,它使用SSE和SSE2指令进行某些标量浮点计算。因此,您的代码实际上可能混合使用x87和SSE / SSE2进行浮点计算。此外,使用/ arch:SSE2,SSE2指令可用于某些64位整数运算。

      

    由于x86编译器生成默认使用SSE2指令的代码,因此必须指定/ arch:IA32以禁用为x86处理器生成SSE和SSE2指令。

    VS2012文档说的几乎相同。关于编译器何时选择x87或SSE / SSE2,两者都很模糊。

    查看反汇编将告诉您编译器正在使用哪些指令,以及结果是否写入内存。