C++ pow在32位和64位应用程序中产生不同的结果
我在一些复杂的计算结果中发现了不匹配。当我彻底观察中间结果时,是std::pow函数造成了这种不匹配。 以下是输入/输出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 我
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位扩展精度类型。异常