我发现一些复杂计算的结果不匹配。当我彻底观察到中间结果时,它的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位精度。
有人能让我清楚地了解克服这种差异的不同之处和方法。
答案 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 double
(2.7182818284589998
的一个不可能的值)时,编译器必须在double
和2.718281828458999793696193592040799558162689208984375
之间进行选择,
当你写2.71828182845899934960698374197818338871002197265625
时(-0.21074699576017999
的另一个不可能的值),编译器必须在double
和-0.2107469957601799948054832611887832172214984893798828125
之间进行选择。
默认舍入到最近,您在-0.210746995760179967049907645559869706630706787109375
和dvalue
中存储的值为dexp
和2.718281828458999793696193592040799558162689208984375
(在long double中存储double不会更改其值)
pow的结果应该接近-0.2107469957601799948054832611887832172214984893798828125
,然后必须将其置于返回类型中,在您的情况下应该是0.8099789690729650165287354526069381795064774873497553965297999359066924950079080502973738475702702999114990234375
(除了MSVC不区分它们long double
1}},据我记忆,结果显示)
将结果放在64位double
中,我们必须在double
和0.80997896907296496049610823320108465850353240966796875
之间进行选择。
正确答案(舍入到最近)是0.80997896907296507151841069571673870086669921875
,这正是您在" 32位结果"中得到的结果,被截断为0.80997896907296507151841069571673870086669921875
。
您的" 64位结果"似乎正好是另一个64位0.80997896907296507
值,从正确的结果中舍入错误的方式(并截断为double
)。我会考虑一个QoI错误:gcc,clang,intel和oracle都给出了唯一的,正确的结果(即使他们不必:IEEE对pow的精度要求允许超过0.5 ulp的错误)
顺便说一下,如果你的粉末返回英特尔80位长的双倍,它必须在0.80997896907296496
和0.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位浮点,因此结果是不同的。