当运行如下所示的程序时,它会产生ok输出:
j= 0 9007199616606190.000000 = x
k= 0 9007199616606190.000000 = [x]
r= 31443101 0.000000 = m*(x-[x])
但是当注释掉的行(即 //if (argc>1) r = atol(argv[1]);
)被取消注释时,它会产生:
j= 20000 9007199616606190.000000 = x
k= 17285 9007199616606190.000000 = [x]
r= 31443101 0.000000 = m*(x-[x])
即使该行没有效果,因为argc>1
为false。有没有人对这个问题有合理的解释?它可以在任何其他系统上重现吗?
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int main(int argc, char *argv[]) {
int j, k, m=10000;
double r=31443101, jroot=sqrt(83), x;
//if (argc>1) r = atol(argv[1]);
x = r * r * jroot;
j = m*(x-floor(x));
k = floor(m*(x-floor(x)));
printf ("j= %9d %24.6f = x\n", j, x);
printf ("k= %9d %24.6f = [x]\n", k, floor(x));
printf ("r= %9.0f %24.6f = m*(x-[x]) \n", r, m*(x-floor(x)));
return 0;
}
注意,测试系统=带有Linux 2.6.35.14-96.fc14.i686的AMD Athlon 64 5200+系统(即,启动以在64位硬件上运行32位操作系统) gcc(GCC)4.5.1 20100924(Red Hat 4.5.1-4)
更新 - 几个小时前,我发表评论说,使用和不使用if
语句生成的代码仅在堆栈偏移和一些跳过的代码中有所不同。我现在发现评论并不完全正确; 即。对于非优化代码是正确的,但对于我执行的-O3代码则不然。
优化开关对问题的影响:
j=20000
和k=17285
j=20000
(错误)和k=0
(确定)无论如何,看看-O3 -S代码清单,这两种情况大多不同于跳过的if
代码和堆栈偏移,直到call floor
之前的行,此时if-if代码具有比无if代码多一个fstpl
:
... ;; code without comment:
fmul %st, %st(1)
fxch %st(1)
fstpl (%esp)
fxch %st(1)
fstpl 48(%esp)
fstpl 32(%esp)
call floor
movl $.LC2, (%esp)
fnstcw 86(%esp)
movzwl 86(%esp), %eax
...
... ;; versus code with comment:
fmul %st, %st(1)
fxch %st(1)
fstpl (%esp)
fxch %st(1)
fstpl 48(%esp)
fstpl 32(%esp)
fstpl 64(%esp)
call floor
movl $.LC3, (%esp)
fnstcw 102(%esp)
movzwl 102(%esp), %eax
...
我还没弄清楚差异的原因。
答案 0 :(得分:3)
在我的系统上没有重复,Win7使用gcc 4.3.4运行CygWin。无论有没有if
语句,j
的值都设置为零,而不是20K。
我唯一的建议是使用gcc -S
来查看汇编程序输出。这应该有希望告诉你出了什么问题。
具体来说,将汇编程序输出生成两个单独的文件,每个文件分别用于工作和非工作变体,然后对它们进行vgrep(并排观察它们)以尝试确定差异。
顺便说一句,这是您环境中的严重失败。当m
为10000时,这意味着x - floor(x)
必须等于2.我不能为我的生活想到任何实际数字的情况: - )
答案 1 :(得分:2)
取消注释该行可能影响结果的原因是,如果没有该行,编译器可以看到r
和jroot
在初始化后无法更改,因此它可以在编译时计算x
而不是运行时。取消注释该行时,r
可能会发生变化,因此必须将x
的计算推迟到运行时,这可能导致使用不同的精度(特别是在使用387浮点数学时) )。
您可以尝试使用-mfpmath=sse -march=native
将SSE单元用于浮点计算,这不会显示出过高的精度;或者您可以尝试使用-ffloat-store
开关。
你的减法x - floor(x)
表现出灾难性的取消 - 这是问题的根本原因要避免的事情;)。
答案 2 :(得分:2)
我认为这条线可能产生影响有两个原因:
x
,基本上将x
限制为可以存储在双精度浮点数中,而您的在计算后续值时,第一个版本可以允许x
的初始计算值仍然可用作具有额外位的中间值。 (可能是所有这些值都是在编译时或运行时计算的。)答案 3 :(得分:0)
编辑:
当我使用-O0,-O1,-O2和-O3在我的计算机上编译代码时,我也没有看到任何区别。
AMD Phenom Quad 64位。 gcc(Ubuntu 4.4.3-4ubuntu5)4.4.3我也尝试过3.0版本的clang(llvm),有没有相同的结果。
我同意编译器可以预先计算所有内容,如果是行,你肯定会在程序集输出中看到它。
浮点和C可能很讨厌,很多东西要知道才能让它真正起作用。强制int进行双重转换对于准确性是有好处的(编译器中的c库,即使已知fpu已经存在问题,并且它使用的编译器C库以及编译到程序中或由程序使用的C库可以/将不同的),但是从/浮动的int是FPU倾向于有它们的bug(我想我看到了TestFloat或类似的地方提到过)。可能会尝试在您的系统上运行TestFloat以查看您的FPU是否良好。在着名的奔腾浮点错误和PentiumIV之间,以及大多数处理器都有浮点错误,我所拥有的奔腾III是稳固的,但我所拥有的Pentium IV会失败。我很少使用浮点,所以不用费心去测试我的系统。
使用优化确实会根据您的编辑更改结果,因此这很可能是gcc问题或代码和gcc的组合(而不是硬件fpu问题)。然后在同一台计算机上尝试不同版本的gcc。例如,4.4.x而不是4.5.x。