升级到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的结果?
答案 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没有。这可能有以下几种原因:
VS2013没有符合IEEE 754标准的数学运算。数学错误在过去发生过,但很少见。如果你获得的结果在最不重要的位置偏离不到一位,则不太可能是编译器或数学库错误。
VS2013编译器重新排序浮点运算,这会改变部分结果的舍入方式。如果结果发生变化,编译器不应该对浮点运算进行重新排序,但是大多数都有优化标志,允许重新排序,当数字会改善性能时,数学允许重新排序(例如,gcc中的-ffast-math)。除非您一直在试验编译器选项,否则这可能不是问题。
VS2013编译器优化器决定使用什么混合的x87和SSE / SSE2指令,或者它改变了寄存器分配的方式。 x87 FPU具有80位浮点寄存器,而SSE2具有64位浮点寄存器。如果编译器在x87浮点寄存器中保持计算并且仅存储最终结果,则该结果的舍入方式可能与使用SSE2计算的结果不同,因为用于中间值的尾数较长。
由于寄存器压力,编译器会将中间结果强制进入内存。结果,一个或多个中间值被截断为64位浮点数。如此短的计划,这是不可能的。
如果你将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,两者都很模糊。
查看反汇编将告诉您编译器正在使用哪些指令,以及结果是否写入内存。