C++ pow在32位和64位应用程序中产生不同的结果

C++ pow在32位和64位应用程序中产生不同的结果,c++,c++11,x86-64,C++,C++11,X86 64,我在一些复杂的计算结果中发现了不匹配。当我彻底观察中间结果时,是std::pow函数造成了这种不匹配。 以下是输入/输出 long double dvalue = 2.7182818284589998; long double dexp = -0.21074699576017999; long double result = std::powl( dvalue, dexp); 64位->结果=0.80997896907296496和32位->结果= 0.80997896907296507 我

我在一些复杂的计算结果中发现了不匹配。当我彻底观察中间结果时,是std::pow函数造成了这种不匹配。 以下是输入/输出

long double dvalue = 2.7182818284589998;
long double dexp = -0.21074699576017999;
long double result = std::powl( dvalue, dexp); 
64位->结果=0.80997896907296496和32位->结果= 0.80997896907296507

我正在使用VS2008。 我尝试过pow函数的其他变体,它接受长双精度和返回长双精度,但仍然看到相同的差异

double-pow(双基,双指数)

long double powl(长双基,长双指数)

我已经阅读了一些关于这方面的信息:

英特尔x86处理器内部使用80位扩展精度,而 double通常为64位宽。不同的优化级别会影响 CPU中的浮点值保存到内存和 因此,从80位精度四舍五入到64位精度。或者, 使用长双精度类型,在gcc上通常为80位宽,以 避免从80位精度舍入到64位精度


有人能让我清楚地理解其中的差异以及克服这种差异的方法吗。

关于浮点计算,最重要的一点是它们(几乎总是)不精确。大多数数字不能准确地表示为浮点数。即使计算结果可以精确表示,实际计算的结果也可能不完全正确

处理这个问题的方法是编写不依赖于获得精确结果的代码。例如,您几乎不应该测试浮点数是否相等。或者,如果您需要测试某个数字是否为正数,您的程序可能需要拒绝非常小的正数(它们近似为负数)或接受非常小的负数(它们近似为正数)

同样,你应该避免数值上不稳定的算法,因为这些小错误很快就会爆发;相反,您应该尝试使用数值稳定的算法,因为这些算法是容错的


如何做好数值计算是一个完整的研究领域

可能发生的情况是,32位构建使用80位FPU寄存器进行计算,而64位构建使用64位值的SIMD操作,造成了轻微的差异。请注意,这两个答案都是14位小数,这大约是64位浮点值的最佳值

VisualC++提供了关于浮点操作是否更喜欢速度、一致性或精度的说法。使用这些选项(例如,

/fp:strict
),如果这对您很重要,您可能可以在两个版本之间获得一致的值


还要注意的是,VC++2008相当古老。较新版本修复了许多bug,包括一些与浮点相关的bug。(自2008年以来,
strtod
在开源软件中的流行实现已经检测到并修复了bug。)除了80位和64位操作之间的精度差异外,您还可能遇到解析和显示bug。尽管如此,浮点是很难的,而且。

您使用的是
double
类型的文本,而不是
long double
(您忘记了后缀)。这意味着当您编写
2.7182818284589998
(对于
double
来说是不可能的值)时,编译器必须在
2.718281828284589997936961935920407995581626892089849606983741978183388710021965625
2.7182818284589949606983741978183388710021965625
之间进行选择

当您编写
-0.21074699576017999
(对于
双精度
,另一个不可能的值)时,编译器必须在
-0.210746995760179994805483838381188321772214984893798828125
-0.21074699576017996704990764555986970663070787109375
之间进行选择

默认四舍五入为最近值时,存储在
dvalue
dexp
中的值为
2.71828182589997936961935920407995581626892089845375
-0.2107469957601799948054883261188832172214984893798828125
(在长双精度中存储双精度不会改变其值)

pow的结果应该接近
0.8099789690729650165287354526069381795064774787349755396529799935906692495007908050297373857070270299911499234375
,然后必须将其放在返回类型中,在您的情况下应该是
长双精度
(除了MSVC没有将它们与
double
区分开来,据我回忆和您的结果显示)

将结果放入64位
双精度
,我们必须在
0.80997896907296496049610823320108465850353240966796875
0.809978969072965071841069571673870086669921875
之间进行选择

正确答案(四舍五入到最近值)是
0.80997896907296507151841069571673870086669921875
,这正是您在“32位结果”中得到的结果,截断为
0.80997896907296507

你的“64位结果”看起来正好是另一个64位<代码>双值,从正确的结果中错误地绕过(并且被截断为<代码> 0.8099789690729649 < /代码>)。我认为QoI bug:GCC、CLAN、英特尔和Oracle都给出了唯一正确的结果。(即使他们不必这样做:IEEE对pow的精度要求允许超过0.5 ulp的误差)

顺便说一句,如果您的pow返回的是一个80位长的Intel double,则它必须介于
0.80997896907296501649515044207738886749884695746004581451416015625
0.8099789690729650165493607016638966888422146455230712890625
之间,后者是最接近的

在x86体系结构上,大多数C编译器实现长双精度编译 x86硬件[…]支持的80位扩展精度类型。异常