不运行valgrind时,gcc'-m32'选项会更改浮点舍入

时间:2015-08-12 18:48:14

标签: c++ gcc floating-point valgrind

我在不同的构建/执行场景下得到不同的浮点舍入。请注意下面第二轮中的2498 ...

   #include <iostream>
   #include <cassert>
   #include <typeinfo>
   using std::cerr;

void domath( int n, double c, double & q1, double & q2 )
   {
   q1=n*c;
   q2=int(n*c);
   }

int main()
   {
   int n=2550;
   double c=0.98, q1, q2;
   domath( n, c, q1, q2 );
   cerr<<"sizeof(int)="<<sizeof(int)<<", sizeof(double)="<<sizeof(double)<<", sizeof(n*c)="<<sizeof(n*c)<<"\n";
   cerr<<"n="<<n<<", int(q1)="<<int(q1)<<", int(q2)="<<int(q2)<<"\n";
   assert( typeid(q1) == typeid(n*c) );
   }

作为64位可执行文件运行...

$ g++ -m64 -Wall rounding_test.cpp -o rounding_test && ./rounding_test
sizeof(int)=4, sizeof(double)=8, sizeof(n*c)=8
n=2550, int(q1)=2499, int(q2)=2499

以32位可执行文件运行...

$ g++ -m32 -Wall rounding_test.cpp -o rounding_test && ./rounding_test
sizeof(int)=4, sizeof(double)=8, sizeof(n*c)=8
n=2550, int(q1)=2499, int(q2)=2498

在valgrind下运行为32位可执行文件...

$ g++ -m32 -Wall rounding_test.cpp -o rounding_test && valgrind --quiet ./rounding_test
sizeof(int)=4, sizeof(double)=8, sizeof(n*c)=8
n=2550, int(q1)=2499, int(q2)=2499

为什么在使用-m32进行编译时会看到不同的结果,为什么在运行valgrind时结果会再次不同?

我的系统是Ubuntu 14.04.1 LTS x86_64,而我的gcc是版本4.8.2。

编辑:

为了响应反汇编的请求,我稍微重构了代码,以便我可以隔离相关部分。 -m64-m32之间采用的方法显然有很大不同。我不太关心为什么这些给出不同的舍入结果,因为我可以通过应用round()函数来解决这个问题。最有趣的问题是:为什么valgrind会改变结果?

rounding_test:     file format elf64-x86-64 
                                  <
000000000040090d <_Z6domathidRdS_>:               <
  40090d:   55                      push   %rbp       <
  40090e:   48 89 e5                mov    %rsp,%rbp      <
  400911:   89 7d fc                mov    %edi,-0x4(%rbp <
  400914:   f2 0f 11 45 f0          movsd  %xmm0,-0x10(%r <
  400919:   48 89 75 e8             mov    %rsi,-0x18(%rb <
  40091d:   48 89 55 e0             mov    %rdx,-0x20(%rb <
  400921:   f2 0f 2a 45 fc          cvtsi2sdl -0x4(%rbp), <
  400926:   f2 0f 59 45 f0          mulsd  -0x10(%rbp),%x <
  40092b:   48 8b 45 e8             mov    -0x18(%rbp),%r <
  40092f:   f2 0f 11 00             movsd  %xmm0,(%rax)   <
  400933:   f2 0f 2a 45 fc          cvtsi2sdl -0x4(%rbp), <
  400938:   f2 0f 59 45 f0          mulsd  -0x10(%rbp),%x <
  40093d:   f2 0f 2c c0             cvttsd2si %xmm0,%eax  <
  400941:   f2 0f 2a c0             cvtsi2sd %eax,%xmm0   <
  400945:   48 8b 45 e0             mov    -0x20(%rbp),%r <
  400949:   f2 0f 11 00             movsd  %xmm0,(%rax)   <
  40094d:   5d                      pop    %rbp       <
  40094e:   c3                      retq              <

      | rounding_test:     file format elf32-i386

                                  > 0804871d <_Z6domathidRdS_>:
                                  >  804871d:   55                      push   %ebp
                                  >  804871e:   89 e5                   mov    %esp,%ebp
                                  >  8048720:   83 ec 10                sub    $0x10,%esp
                                  >  8048723:   8b 45 0c                mov    0xc(%ebp),%eax
                                  >  8048726:   89 45 f8                mov    %eax,-0x8(%ebp
                                  >  8048729:   8b 45 10                mov    0x10(%ebp),%ea
                                  >  804872c:   89 45 fc                mov    %eax,-0x4(%ebp
                                  >  804872f:   db 45 08                fildl  0x8(%ebp)
                                  >  8048732:   dc 4d f8                fmull  -0x8(%ebp)
                                  >  8048735:   8b 45 14                mov    0x14(%ebp),%ea
                                  >  8048738:   dd 18                   fstpl  (%eax)
                                  >  804873a:   db 45 08                fildl  0x8(%ebp)
                                  >  804873d:   dc 4d f8                fmull  -0x8(%ebp)
                                  >  8048740:   d9 7d f6                fnstcw -0xa(%ebp)
                                  >  8048743:   0f b7 45 f6             movzwl -0xa(%ebp),%ea
                                  >  8048747:   b4 0c                   mov    $0xc,%ah
                                  >  8048749:   66 89 45 f4             mov    %ax,-0xc(%ebp)
                                  >  804874d:   d9 6d f4                fldcw  -0xc(%ebp)
                                  >  8048750:   db 5d f0                fistpl -0x10(%ebp)
                                  >  8048753:   d9 6d f6                fldcw  -0xa(%ebp)
                                  >  8048756:   8b 45 f0                mov    -0x10(%ebp),%e
                                  >  8048759:   89 45 f0                mov    %eax,-0x10(%eb
                                  >  804875c:   db 45 f0                fildl  -0x10(%ebp)
                                  >  804875f:   8b 45 18                mov    0x18(%ebp),%ea
                                  >  8048762:   dd 18                   fstpl  (%eax)
                                  >  8048764:   c9                      leave  
                                  >  8048765:   c3                      ret    

2 个答案:

答案 0 :(得分:3)

编辑:看起来,至少在很长一段时间内,valgrind的浮点计算并不像&#34;真正的&#34;计算。换句话说,这可以解释为什么你得到不同的结果。请参阅valgrind邮件列表中的this问题和答案。

Edit2:目前的valgrind.org文档中包含了它的核心限制&#34; here部分 - 所以我希望它确实是#34;仍然有效&#34;。换句话说,valgrind的文档说要期望valgrind和x87 FPU计算之间存在差异。 &#34;你被警告了!&#34; (正如我们所看到的,使用sse指令进行相同的数学运算会产生与valgrind相同的结果,确认它是从80位到64位的四舍五入的差异&#34;

浮点计算会略有不同,具体取决于计算的执行方式。我不确定你想要得到什么答案,所以这是一个漫长的漫无边际的答案#34;

Valgrind确实以各种方式改变程序的确切行为(它模拟某些指令,而不是实际执行实际指令 - 这可能包括保存计算的中间结果)。此外,浮点计算是众所周知的,并不是精确的&#34; - 如果计算精确或不准确,那只是运气/运气不好的问题。 0.98是许多很多数字中的一个,它们不能以浮点格式精确描述[至少不是常见的IEEE格式]。

添加:

cerr<<"c="<<std::setprecision(30)<<c <<"\n";

我们看到输出为c=0.979999999999999982236431605997(是的,实际值为0.979999 ... 99982或某些此类,剩余数字只是剩余值,因为它不是&#34;即使是二进制数,也总是会留下一些东西。

这是由gcc生成的代码的n = 2550;c = 0.98q = n * c部分:

movl    $2550, -28(%ebp)       ; n
fldl    .LC0
fstpl   -40(%ebp)              ; c
fildl   -28(%ebp)
fmull   -40(%ebp)
fstpl   -48(%ebp)              ; q - note that this is stored as a rouned 64-bit value.

这是代码的int(q)int(n*c)部分:

fildl   -28(%ebp)             ; n
fmull   -40(%ebp)             ; c 
fnstcw  -58(%ebp)             ; Save control word
movzwl  -58(%ebp), %eax
movb    $12, %ah
movw    %ax, -60(%ebp)        ; Save float control word.
fldcw   -60(%ebp)
fistpl  -64(%ebp)             ; Store as integer (directly from 80-bit result)
fldcw   -58(%ebp)             ; restore float control word.
movl    -64(%ebp), %ebx       ; result of int(n * c)


fldl    -48(%ebp)             ; q
fldcw   -60(%ebp)             ; Load float control word as saved above.
fistpl  -64(%ebp)             ; Store as integer.
fldcw   -58(%ebp)             ; Restore control word.
movl    -64(%ebp), %esi       ; result of int(q)

现在,如果中间结果在其中一个计算中间从内部80位精度存储(并因此舍入),则结果可能与计算结果略有不同,而不保存中间值。

我从g ++ 4.9.2和clang ++ -mno-sse得到相同的结果 - 但如果我在clang情况下启用了sse,它会得到与64位构建相同的结果。使用gcc -msse2 -m32可以在任何地方找到2499答案。这表明错误是由&#34;存储中间结果&#34;以某种方式。

同样,将gcc优化到-O1将在所有地方提供2499 - 但这是巧合,而不是某些&#34;聪明的思考&#34;的结果。如果你想要计算得到正确的舍入整数值,那么你自己四舍五入会好得多,因为int(someDoubleValue)迟早会出现#34;一个简短的&#34;。

编辑3:最后,使用g++ -mno-sse -m64也会产生相同的2498答案,在同一个二进制文件上使用valgrind会生成2499回答。

答案 1 :(得分:1)

32位版本使用X87 floating point instructions。 X87内部使用80位浮点数,这会在数字转换为其他精度时产生麻烦。在您的情况下,0.98的64位精度近似值略小于真实值。当CPU将其转换为80位值时,您将得到完全相同的数值,这是一个同样糟糕的近似值 - 有更多位不能让您获得更好的近似值。然后,FPU将该数字乘以2550,得到的数字略小于2499.如果CPU使用的是64位数字,它应该完全计算2499,就像在64位版本中一样。