c-为什么在不同的上下文中调用pow()有时会产生不同的结果



我想知道如何解释我今天观察到的浮点差异。这是一个错误,还是绊倒了一些未定义的行为?下面是代码。我希望能够理解这种行为,以便使其在编译器、处理器和平台之间保持一致。

#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的内联或其他调用。gccclang都在编译时计算它,并且它们的计算方式不同。差异在最低有效位。

您的两个操作数都不能精确地表示为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来说是一个不断增加且具有挑战性的问题,目前还不应该被期望。

最新更新