我在不同的构建/执行场景下得到不同的浮点舍入。请注意下面第二轮中的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
答案 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.98
和q = 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位版本中一样。