未注释掉未使用的语句时浮点异常?

发布于 2024-12-17 15:40:25 字数 2252 浏览 3 评论 0原文

当运行如下所示的程序时,它会产生 ok 输出:

 j=         0    9007199616606190.000000 = x
 k=         0    9007199616606190.000000 = [x]
 r=  31443101                   0.000000 = m*(x-[x]) 

但是当注释掉行 (ie //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;
 }

请注意,测试系统 = AMD Athlon 64 5200+ 系统,配备 Linux 2.6.35.14-96.fc14.i686(ie,启动以在 64 位硬件上运行 32 位操作系统)和 gcc (GCC) ) 4.5.1 20100924 (Red Hat 4.5.1-4)

更新 -- 一些几个小时前,我发表了一条评论,指出使用和不使用 if 语句生成的代码仅在堆栈偏移量和一些跳过的代码上有所不同。我现在发现这个评论并不完全正确; ie 对于非优化代码来说是这样,但对于我执行的 -O3 代码来说不是这样。

优化开关对问题的影响:

  • -O0 : 两个程序版本都运行正常
  • -O2 或 -O3 : 带注释的版本有错误,如上,其中 j=20000k=17285
  • -O1 :带注释的版本有 j=20000 (错误)和 k=0 (确定)

无论如何,看看-O3 -S 代码清单,这两种情况的主要区别在于跳过 if 代码和堆栈偏移量直到 call Floor 之前的行,此时 with-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
    ...

我还没有弄清楚差异的原因。

When the program as shown below is run, it produces ok output:

 j=         0    9007199616606190.000000 = x
 k=         0    9007199616606190.000000 = [x]
 r=  31443101                   0.000000 = m*(x-[x]) 

But when the commented-out line (i.e. //if (argc>1) r = atol(argv[1]);) is uncommented, it produces:

 j=     20000    9007199616606190.000000 = x
 k=     17285    9007199616606190.000000 = [x]
 r=  31443101                   0.000000 = m*(x-[x]) 

even though that line should have no effect, since argc>1 is false. Has anybody got a plausible explanation for this problem? Is it reproducible on any other systems?

 #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;
 }

Note, test system = AMD Athlon 64 5200+ system with Linux 2.6.35.14-96.fc14.i686 (i.e., booted to run a 32-bit OS on 64-bit HW) with gcc (GCC) 4.5.1 20100924 (Red Hat 4.5.1-4)

Update -- A few hours ago I posted a comment that code generated with and without the if statement differed only in stack offsets and some skipped code. I now find that comment was not entirely correct; i.e. it is true for non-optimized code, but not true for the -O3 code I executed.

Effect of optimization switch on problem:

  • -O0 : Both program versions run ok
  • -O2 or -O3 : Version with comment has error as above, where j=20000 and k=17285
  • -O1 : Version with comment has j=20000 (an error) and k=0 (OK)

Anyhow, looking at -O3 -S code listings, the two cases differ mostly in skipped if code and stack offsets up to the line before call floor, at which point the with-if code has one more fstpl than the without-if code:

    ...  ;; 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
    ...

I haven't figured out the reason for the difference.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

温柔女人霸气范 2024-12-24 15:40:25

在我的系统上没有重复,Win7 运行带有 gcc 4.3.4 的 CygWin。无论有没有 if 语句,j 的值都设置为零,而不是 20K。

我唯一的建议是使用 gcc -S 来查看汇编器输出。这应该能告诉你出了什么问题。

具体来说,将汇编器输出生成到两个单独的文件,每个文件对应工作变体和非工作变体,然后 vgrep 它们(并排观察它们)以尝试确定差异。


顺便说一句,这在您的环境中是一个严重的失败。 m 为 10000,这意味着 x - Floor(x) 必须等于 2。我一生都无法想到任何实数会是这样的:-)

Not duplicated on my system, Win7 running CygWin with gcc 4.3.4. Both with and without the if statement, the value of j is set to zero, not 20K.

My only suggestion would be to use gcc -S to get a look at the assembler output. That should hopefully tell you what's going wrong.

Specifically, generate the assembler output to two separate files, one each for the working and non-working variant, then vgrep them (eyeball them side by side) to try and ascertain the difference.


This is a serious failure in your environment by the way. With m being 10000, that means the x - floor(x) must be equal to 2. I can't for the life of me think of any real number where that would be the case :-)

全部不再 2024-12-24 15:40:25

我认为该行可能产生影响的原因有两个:

  • 如果没有该行,所有这些变量的值都可以(并且,恕我直言,很可能)在编译时确定;对于该行,计算必须在运行时执行。但显然,编译器的预先计算值应该与运行时计算的值相同,并且我倾向于将此视为不同观察行为的实际原因。 (不过,这肯定会在汇编器输出中显示出巨大的差异!)
  • 在许多机器上,执行浮点算术时使用的中间值中的位数多于实际存储在双精度浮点数中的位数。您的第二个版本通过创建两个不同的代码路径来设置x,基本上将x限制为可以存储在双精度浮点数中的内容- 点数,而您的第一个版本可以允许在计算后续值时,最初计算的 x 值仍然可用作带有额外位的中间值。 (无论所有这些值是在编译时还是在运行时计算,都可能出现这种情况。)

I think there are two reasons why that line could have an effect:

  • Without that line, the values of all of these variables can be (and, IMHO, most likely are) determined at compile-time; with that line, the computations have to be performed at run-time. But obviously, the compiler's precomputed values are supposed to be the same as values computed at run-time, and I'm inclined to discount this as the actual reason for the different observed behavior. (It would certainly show up as a huge difference in the assembler output, though!)
  • On many machines, floating-point arithmetic is performed using more bits in intermediate values than can actually be stored in a double-precision floating-point number. Your second version, by creating two different code-paths to set x, basically restricts x to what can be stored in a double-precision floating-point number, whereas your first version can allow the initially-calculated value for x to still be available as an intermediate value, with extra bits, when computing subsequent values. (This could be the case whether all of these values are computed at compile-time or at run-time.)
轻许诺言 2024-12-24 15:40:25

取消注释该行可能会影响结果的原因是,如果没有该行,编译器可以看到rjroot在初始化后无法更改,因此它可以在编译时而不是运行时计算x。当该行取消注释时,r 可能会发生变化,因此 x 的计算必须推迟到运行时,这可能会导致以不同的精度完成计算(特别是如果 387正在使用浮点数学)。

您可以尝试使用 -mfpmath=sse -march=native 使用 SSE 单元进行浮点计算,这样不会表现出过高的精度;或者您可以尝试使用 -ffloat-store 开关。

你的减法x - Floor(x)表现出灾难性的取消 - 这是问题的根本原因需要避免的事情;)。

The reason that uncommenting that line might affect the result is that without that line, the compiler can see that r and jroot cannot change after initialisation, so it can calculate x at compile-time rather than runtime. When the line is uncommented, r might change, so the calculation of x must be deferred to runtime, which can result it in being done with a different precision (particularly if 387 floating point math is being used).

You can try using -mfpmath=sse -march=native to use the SSE unit for floating point calculations, which doesn't exhibit excess precision; or you can try using the -ffloat-store switch.

Your subtraction x - floor(x) exhibits catastrophic cancellation - this is the root cause of the problem something to be avoided ;).

森末i 2024-12-24 15:40:25

编辑:

当我使用 -O0、-O1、-O2 和 -O3 在我的计算机上编译代码时,我也没有看到任何差异。

AMD Phenom 四核 64 位。
gcc (Ubuntu 4.4.3-4ubuntu5) 4.4.3

我还尝试了 3.0 版中的 clang (llvm),无论是否有相同的结果。

我同意编译器可以在没有 if 行的情况下预先计算所有内容,但您肯定会在汇编输出中看到这一点。

浮点和 C 语言可能很麻烦,需要了解很多东西才能让它真正发挥作用。强制 int 到 double 转换有利于准确性(编译器中的 C 库,即使 fpu 很好,也已知存在问题,并且它使用的编译器 C 库以及编译到程序中或由程序使用的 C 库可以/将不同),但 int 到/从 float 是 FPU 往往存在错误的地方(我想我在 TestFloat 或类似的地方看到了这一点)。可以尝试在您的系统上运行 TestFloat 来查看您的 FPU 是否良好。从著名的奔腾浮点错误到奔腾 IV 以及之后的大多数处理器都有浮点错误,我的奔腾 III 很稳定,但我的奔腾 IV 会出现故障。我很少再使用浮点,所以不用费心去测试我的系统。

进行优化确实根据您的编辑改变了您的结果,因此这很可能是 gcc 问题或您的代码和 gcc 的组合(而不是硬件 fpu 问题)。然后在同一台计算机上尝试不同版本的 gcc。例如,4.4.x 而不是 4.5.x。

EDITED:

I also do not see a difference when I compile your code on my computer using the -O0, -O1, -O2 and -O3.

AMD Phenom Quad 64 bit.
gcc (Ubuntu 4.4.3-4ubuntu5) 4.4.3

I also tried clang (llvm) from release 3.0 with and without the if same results.

I agree that the compiler can pre-compute everything without that if line, you would definitely see that in the assembly output though.

Floating point and C can be nasty, lots of stuff to know to get it to really work. Forcing the int to double conversions is good for accuracy (c libraries in the compiler, even if the fpu is good have been known to have problems and the compilers C library it uses and the C library compiled into or used by your program can/will differ), but int to/from float is where FPU's tend to have their bugs (I think I saw that mentioned with TestFloat or somewhere like that). Might try running TestFloat on your system to see if your FPU is good. Between the famous pentium floating point bug and into the PentiumIV and beyond days most processors had floating point bugs, the pentium III I had was solid but the Pentium IV I had would fail. I rarely use floating point anymore so dont bother to test my systems.

Playing with optimization did change your results according to your edit so this is most likely a gcc problem or a combination of your code and gcc (and not a hardware fpu problem). Then try a different version of gcc on the same computer. 4.4.x instead of 4.5.x for example.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文