以下 3 行使用"gcc -Ofast -march=skylake"给出了不精确的结果:
int32_t i = -5;
const double sqr_N_min_1 = (double)i * i;
1. - ((double)i * i) / sqr_N_min_1
显然,sqr_N_min_1
gets25.
和在第 3 行(-5 * -5) / 25
应该变成1.
使得第 3 行的总体结果正好是0.
。事实上,编译器选项"gcc -O3 -march=skylake"确实如此。
但是使用“-Ofast”,最后一行产生-2.081668e-17
而不是0.
和与其他i
(-5
例如6
或7
)它得到其他非常小的正或负随机偏差0.
。我的问题是:这种不精确的根源究竟在哪里?
为了调查这一点,我用 C 编写了一个小测试程序:
#include <stdint.h> /* int32_t */
#include <stdio.h>
#define MAX_SIZE 10
double W[MAX_SIZE];
int main( int argc, char *argv[] )
{
volatile int32_t n = 6; /* try 6 7 or argv[1][0]-'0' */
double *w = W;
int32_t i = 1 - n;
const int32_t end = n - 1;
const double sqr_N_min_1 = (double)i * i;
/* Here is the crucial part. The loop avoids the compiler replacing it with constants: */
do {
*w++ = 1. - ((double)i * i) / sqr_N_min_1;
} while ( (i+=2) <= end );
/* Then, show the results (only the 1st and last output line matters): */
w = W;
i = 1 - n;
do {
fprintf( stderr, "%e\n", *w++ );
} while ( (i+=2) <= end );
return( 0 );
}
Godbolt 向我展示了由"-Ofast -march=skylake"与"-O3 -march=skylake"选项的"x86-64 gcc9.3"生成的程序集。请检查网站的五个栏目(1.源代码,2.带有“-Ofast”的程序集,3.带有“-O3”的程序集,4.第一个程序集的输出,5.第二个程序集的输出):
如您所见,程序集的差异很明显,但我无法弄清楚不精确的确切来源。那么,问题是,哪些汇编指令对此负责?
一个后续问题是:是否有可能通过重新编写 C 程序来避免使用“-Ofast -march=skylake”的这种不精确性?