我想知道如何解释我今天观察到的浮点差异。这是一个错误,还是绊倒了一些未定义的行为?下面是代码。我希望能够理解这种行为,以便使其在编译器、处理器和平台之间保持一致。
#include <stdio.h>
#include <math.h>
#define BUG_ON_GCC
#ifdef BUG_ON_GCC
__attribute__((noinline))
#else
inline __attribute__((always_inline))
#endif
double safe_pow(
double x,
double y)
{
printf("x = %llxn", *((unsigned long long *)&x));
printf("y = %llxn", *((unsigned long long *)&y));
double result = pow(x, y);
printf("r = %llxn", *((unsigned long long *)&result));
return result;
}
int main() {
printf("%ldn", sizeof(pow(15.034465284692086, 3.466120406090667)));
printf("%.24fn", pow(15.034465284692086, 3.466120406090667));
printf("%ldn", sizeof(safe_pow(15.034465284692086, 3.466120406090667)));
printf("%.24fn", safe_pow(15.034465284692086, 3.466120406090667));
return 0;
}
我在Linux虚拟机中使用Clang的结果是:
8
12020.670425990641888347454369
8
x = 402e11a56f0d331e
y = 400bba9d55e142e0
r = 40c77a55d084d419
12020.670425990641888347454369
在同一VM中使用GCC:
8
12020.670425990643707336857915
8
x = 402e11a56f0d331e
y = 400bba9d55e142e0
r = 40c77a55d084d419
12020.670425990641888347454369
在同一台机器上的macOS上使用Clang:
8
12020.670425990643707336857915
8
x = 402e11a56f0d331e
y = 400bba9d55e142e0
r = 40c77a55d084d41a
12020.670425990643707336857915
附件A.
正如您所看到的,没有对pow
的内联或其他调用。gcc
和clang
都在编译时计算它,并且它们的计算方式不同。差异在最低有效位。
您的两个操作数都不能精确地表示为IEEE双精度。一个无限精度计算器建议GCC使用精确的操作数,并以比double
更高的精度计算答案,然后将结果转换为double
,而Clang则将操作数转换为失去一定精度的double
,并以此进行计算。行为没有对错之分,按照标准都是对的。
我希望了解这种行为,以便使其在编译器、处理器和平台之间保持一致。
简单地说,xy的一致性要求公差大于±0.5000…ULP。
pow(x,y)
操作数的值是多少
15.034465284692086和3.466120406090667都不能准确地表示为double
,相反,附近的值用于x, y
// 15.0344652846920 86
x = 0x1.e11a56f0d331ep+3 15.0344652846920 85873530231765471398830413818359375
y = 0x1.bba9d55e1420ep+1 3.46612040609066 69610631070099771022796630859375
// 3.46612040609066 7
什么是pow(x,y)
数学上,15.0344652846920 858735…3.46612040609066 696106…是:
0x1.77a55d084d419 802...p+13 12020.67042599064 2798519464085... extended math
CCD_ 11为
0x1.77a55d084d419 p+13 12020.67042599064 1888347454369... Clang Linux
0x1.77a55d084d419 p+13 12020.67042599064 1888347454369... GCC Linux
0x1.77a55d084d41a p+13 12020.67042599064 3707336857915... Clang MAC
0x1.77a55d084d419 80c p+13 12020.67042599064 2803171226660... powl(x, y) double args
0x1.77a55d084d41a 640...p+13 12020.67042599064 4417576265463... extended math with code's constants
两个不同的pow()
答案相差1.0 ULP
数学答案大约在两个可能的double
结果之间的50.05%处
对于较宽的powl()
,如果结果为double
,则结果仍略高于50%ULP
在这种情况下,两个double
中的较大者(Clang MAC(是更好的答案
即使使用原始代码常量,Clang MAC也更好。
为什么在不同的上下文中调用pow((有时会产生不同的结果?
审查表制造商的困境
像pow()
这样的先验计算(试图近似数学xy(遇到了一个难题:内部计算必须有多精确才能得到最佳答案?对于xy,通常的方法是用一些额外的数字进行计算,但不要太多,因为这会减慢性能,但只会略微增加每个额外数字的最佳答案的出现率。
简单地说,由于使用了更高的精度、更好的算法或选择值,一些pow()
计算在几乎一半的情况下比另一种计算表现得更好(另一种方法可能更经常提供更好的答案-这只是一种情况。(
一致性
对于好的math.h库,应该期望的是与最佳答案相差1.0 ULP以内的结果。在这里,两个答案都在最佳答案的0.5005 ULP以内。总是期望在0.5000内得到答案…ULP对于xy来说是一个不断增加且具有挑战性的问题,目前还不应该被期望。