使用 32 位编译器的编译与使用 64 位编译器的编译结果不同

时间:2021-06-09 13:57:16

标签: c

如果使用 32 位编译器(Virtual Pascal 2.1、Free Pasacal 3.2.2、Delphi 3、Delphi 7、GCC 3.4.2、GFortran 3.4.2)或 64 位编译器编译,则非常简单的速度测试程序会给出不同的结果位编译器(Free Pascal 3.2.0、GCC 9.2.0、GFortran 9.2.0)。 程序代码为:

#include <stdio.h>

double y,yy[1000000];
int    i,j;

int main()
{
 y=1.0; i=1;

// for (i=1; i<=100; i++)
  for (j=1; j<=1000000; j++)
   {y=y-1.0/(i+j*y);
    yy[j]=y;}

 for (j=1; j<=200; j++) printf("%7d %25.15f\n",j,yy[j]);

 j=j+20;
 printf("%7d %25.15f\n",j,yy[1000000]);

 return 0;
}

如果用32位编译器编译,输出的最后两行是

        200         1.105809961129190
        221         1.283238771529100

如果用64位编译器编译,输出的最后两行是

        200         2.529262035756635
        221         9.276179925149263

在编译期间没有应用优化。哪个结果是正确的?

3 个答案:

答案 0 :(得分:3)

您的程序尝试在 yy[1000000] 循环(当 for 为 1000000)和 j 中访问 printf。该数组有 1,000,000 个元素,因此最后一个元素的索引仅为 999,999,而不是 1,000,000。 C 标准没有定义超出 yy[999999] 的访问。这可能会损坏内存或导致其他影响,从而导致您观察到的输出。

要解决此问题,请将 yy 的定义更改为具有 1,000,001 个元素,或者将代码更改为使用索引 0 到 999,999,而不是使用索引 1 到 1,000,000。

答案 1 :(得分:0)

当我查看您的代码时,我发现了两个问题;

  1. 对数组的越界访问,@Eugene Sh 的评论和@Eric Postpischil 的回答已经解决了这个问题。我相信这不是你在这里观察到的来源。

  2. 程序执行的操作序列的性质。由于小数的减法(加法也一样),所以那里的运算顺序非常敏感。但这只是问题的间接根源。真正的问题很可能是在 64 位程序中,有更多的寄存器可用于局部变量和中间结果,这使得可以更精确地累加。当您使用 32 位程序时,可用于变量和中间结果的寄存器较少,从而迫使累加的中间结果以较低的精度存储回内存(或缓存)中。 为了更好地理解这一点,重要的是要说 CPU 几乎总是使用比变量精度更大的寄存器进行计算,然后在将结果存储回内存时将结果截断。

如果您可以从 y 的最后一个值开始累积(从您的模型来看这似乎并不容易),您将看到更少的差异。同样,如果您进行更少的迭代,您会看到更少的差异。

答案 2 :(得分:0)

看拆解。使用 gcc -m32,我明白部分:

    11ef:   db 45 f4                fildl  -0xc(%ebp)
    11f2:   dd 83 40 00 00 00       fldl   0x40(%ebx)
    11f8:   de c9                   fmulp  %st,%st(1)
    11fa:   de c1                   faddp  %st,%st(1)
    11fc:   d9 e8                   fld1   
    11fe:   de f1                   fdivp  %st,%st(1)
    1200:   de e9                   fsubrp %st,%st(1)
    1202:   dd 9b 40 00 00 00       fstpl  0x40(%ebx)

这是使用 x87 FPU 来计算结果。这些寄存器默认为 64 位精度的 80 位浮点数。

将其与 gcc 进行比较(没有 -m32 选项):

    118b:   f2 0f 59 cb             mulsd  %xmm3,%xmm1
    118f:   f2 0f 58 d1             addsd  %xmm1,%xmm2
    1193:   f2 0f 10 0d 7d 0e 00    movsd  0xe7d(%rip),%xmm1        # 2018 <_IO_stdin_used+0x18>
    119a:   00 
    119b:   f2 0f 5e ca             divsd  %xmm2,%xmm1
    119f:   f2 0f 5c c1             subsd  %xmm1,%xmm0
    11a3:   f2 0f 11 05 b5 2e 00    movsd  %xmm0,0x2eb5(%rip)        # 4060 <y>
    11aa:   00 
    11ab:   8b 05 d3 40 7a 00       mov    0x7a40d3(%rip),%eax        # 7a5284 <j>
    11b1:   f2 0f 10 05 a7 2e 00    movsd  0x2ea7(%rip),%xmm0        # 4060 <y>

这里使用的是 SSE2 寄存器。这些是具有 53 位精度的 64 位浮点数。

我没有完全分析这个,但这可以解释差异。

关于上次打印结果的其他评论仍然正确。

为了查看正确答案,我使用了以下最大值程序:

fpprec: 200; /* 200 digits of precision */
y: 1b0; /* bigfloat number */
for j:1 to 200 do (y: y - 1/(1+j*y), printf(true, "~d: ~m~%", j, y));

最后几个值是:

1: 5.0b-1
2: 0.0b0
3: - 1.0b0
4: - 6.6666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666667b-1
5: - 2.3809523809523809523809523809523809523809523809523809523809523809523809523809523809523809523809523809523809523809523809523809523809523809523809523809523809523809523809523809523809523809523809523809524b-1
...
199: 2.9134730438104486835684450669343781994684160775783702527498016474078580803950756073065445760100932978294411495273196565514143473753599906758526639607462515211042188884927808719790375365190366781832406b0
200: 2.9117598191464059260125139410787531875196505670673442081756913620541464298655107172850569895426676723484404517299155462989843760200675720715542525681413183321106093832508069466366241521724422505183095b0

Maxima 可以将精确值计算为有理数,但分数很快就会有大量数字,所以它非常慢,我不想等待。因此,大浮点数为 200 位。可以轻松完成更多的数字;我只是希望 200 位数字就足够了。