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

时间:2015-11-24 06:46:44

标签: 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

我正在使用VS2008。 我尝试过其他功能的pow功能,它需要很长的双倍并返回长双,但仍然看到相同的差异。

double pow( double base, double exponent );

long double powl( long double base, long double exponent );

我已经阅读了一些关于此的信息:

  

Intel x86处理器内部使用80位扩展精度,而   double通常是64位宽。不同的优化级别会影响   来自CPU的浮点值多久被保存到内存中   因此从80位精度四舍五入到64位精度。或者,   使用long double类型,通常在gcc上为80位宽   避免从80位精度舍入到64位精度。

有人能让我清楚地了解克服这种差异的不同之处和方法。

4 个答案:

答案 0 :(得分:4)

可能发生的是32位版本使用80位FPU寄存器进行计算,64位版本使用64位值进行SIMD操作,导致轻微差异。请注意,两个答案都同意14个小数位,这是64位浮点值所能达到的最佳值。

Visual C ++提供了compiler options,可以让您说明您是否更喜欢浮点运算的速度,一致性或精度。使用这些选项(例如/fp:strict),如果这对您很重要,您可以在两个版本之间获得一致的值。

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

答案 1 :(得分:2)

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

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

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

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

答案 2 :(得分:2)

您使用的是subquery.c类型的文字,而不是double(您忘记了后缀)。这意味着当您编写long double2.7182818284589998的一个不可能的值)时,编译器必须在double2.718281828458999793696193592040799558162689208984375之间进行选择,

当你写2.71828182845899934960698374197818338871002197265625时(-0.21074699576017999的另一个不可能的值),编译器必须在double-0.2107469957601799948054832611887832172214984893798828125之间进行选择。

默认舍入到最近,您在-0.210746995760179967049907645559869706630706787109375dvalue中存储的值为dexp2.718281828458999793696193592040799558162689208984375(在long double中存储double不会更改其值)

pow的结果应该接近-0.2107469957601799948054832611887832172214984893798828125,然后必须将其置于返回类型中,在您的情况下应该是0.8099789690729650165287354526069381795064774873497553965297999359066924950079080502973738475702702999114990234375(除了MSVC不区分它们long double 1}},据我记忆,结果显示)

将结果放在64位double中,我们必须在double0.80997896907296496049610823320108465850353240966796875之间进行选择。

正确答案(舍入到最近)是0.80997896907296507151841069571673870086669921875,这正是您在" 32位结果"中得到的结果,被截断为0.80997896907296507151841069571673870086669921875

您的" 64位结果"似乎正好是另一个64位0.80997896907296507值,从正确的结果中舍入错误的方式(并截断为double)。我会考虑一个QoI错误:gcc,clang,intel和oracle都给出了唯一的,正确的结果(即使他们不必:IEEE对pow的精度要求允许超过0.5 ulp的错误)

顺便说一下,如果你的粉末返回英特尔80位长的双倍,它必须在0.809978969072964960.8099789690729650164951504420773886749884695746004581451416015625之间,后者是最接近的。

答案 3 :(得分:0)

来自wikipedia page on long double

  

在x86架构上,大多数C编译器都实现了long double   x86硬件支持的80位扩展精度类型[...]。 Microsoft Visual C ++ for x86例外,   这使得long double成为double的同义词。

因此,当您在32位long double = double上进行编译时,但在x64上long double实际上是一个80位浮点,因此结果是不同的。

相关问题