gpt4 book ai didi

c++ - gcc '-m32' 选项在不运行 valgrind 时更改浮点舍入

转载 作者:搜寻专家 更新时间:2023-10-31 01:00:04 24 4
gpt4 key购买 nike

我在不同的构建/执行场景下得到不同的浮点舍入。注意下面第二次运行中的 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

最佳答案

编辑: 看起来,至少在很久以前,valgrind 的浮点计算不如“真实”计算准确。换句话说,这可以解释为什么你会得到不同的结果。参见 this在 valgrind 邮件列表上问答。

Edit2:当前的 valgrind.org 文档在其“核心限制”部分有它 here - 所以我希望它确实“仍然有效”。换句话说,valgrind 的文档说预计 valgrind 和 x87 FPU 计算之间存在差异。 “你被警告了!” (正如我们所见,使用 sse 指令执行相同的数学运算会产生与 valgrind 相同的结果,确认这是“从 80 位舍入到 64 位”的差异)

浮点计算将根据计算的具体执行方式略有不同。我不确定您想得到什么答案,所以这里有一个长篇大论的“某种答案”。

Valgrind 确实以各种方式改变了程序的确切行为(它模拟某些指令,而不是实际执行真正的指令——这可能包括保存计算的中间结果)。此外,众所周知,浮点计算“不精确”——计算结果是否精确只是运气/运气不佳的问题。 0.98 是许多无法用浮点格式精确描述的数字之一 [至少不是常见的 IEEE 格式]。

通过添加:

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

我们看到输出是 c=0.979999999999999982236431605997(是的,实际值是 0.979999...99982 或类似的数字,剩余的数字只是剩余值,因为它不是“偶数”二进制数,总会有剩余的。

这是由 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 的答案。这表明错误是由某种方式“存储中间结果”引起的。

同样,在 gcc 中优化为 -O1 将在所有地方给出 2499 - 但这是巧合,而不是某些“聪明的想法”的结果。如果您想要正确舍入计算的整数值,您最好自己舍入,因为迟早 int(someDoubleValue) 会出现“一个短”。

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

关于c++ - gcc '-m32' 选项在不运行 valgrind 时更改浮点舍入,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31973139/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com