我的程序运行在Linux和Windows中,我必须确保浮点运算在不同的操作系统中得到相同的结果。
这里是代码:
for (int i = 0; i < 100000; ++i) { float d_value = 10.0f / float(i); float p_value = 0.01f * float(i) + 100.0f; }
我使用“ g ++ -m32 -c -static -g -O0 -ffloat-store ”在linux中构build代码。 我使用“/ fp:precise / O2”在vs2005的窗口中构build代码。
当我printf的“d_value”和“p_value”,“d_value”都在Linux和Windows相同。 但是“p_value”有时是不同的。 例如,打印hex格式的“p_value”:
windows: 42d5d1eb linux: 42d5d1ec
为什么要这样做呢?
我的g ++版本是
Configured with: ../src/configure -v --with-pkgversion='Debian 4.4.5-8' --with-bugurl=file:///usr/share/doc/gcc-4.4/README.Bugs --enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.4 --enable-shared --enable-multiarch --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.4 --libdir=/usr/lib --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-objc-gc --enable-targets=all --with-arch-32=i586 --with-tune=generic --enable-checking=release --build=i486-linux-gnu --host=i486-linux-gnu --target=i486-linux-gnu Thread model: posix gcc version 4.4.5 (Debian 4.4.5-8)
我使用flag -ffloat-store
,因为这里有人的build议: Linux,Mac OS X和Windows之间不同的math舍入行为
在Windows上使用/fp:strict
来告诉编译器生成严格遵循IEEE 754的代码,在Linux上使用gcc -msse2 -mfpmath=sse
来获得相同的行为。
您所看到的差异的原因已经在StackOverflow上进行了讨论,但最好的调查是David Monniaux的文章 。
使用gcc -msse2 -mpfmath=sse
编译时获得的汇编指令如下所示。 说明cvtsi2ssq
, cvtsi2ssq
, mulss
, addss
是正确的使用说明,它们产生一个程序,其中p_value
包含在一个点42d5d1ec
。
.globl _main .align 4, 0x90 _main: ## @main .cfi_startproc ## BB#0: pushq %rbp Ltmp2: .cfi_def_cfa_offset 16 Ltmp3: .cfi_offset %rbp, -16 movq %rsp, %rbp Ltmp4: .cfi_def_cfa_register %rbp subq $32, %rsp movl $0, -4(%rbp) movl $0, -8(%rbp) LBB0_1: ## =>This Inner Loop Header: Depth=1 cmpl $100000, -8(%rbp) ## imm = 0x186A0 jge LBB0_4 ## BB#2: ## in Loop: Header=BB0_1 Depth=1 movq _p_value@GOTPCREL(%rip), %rax movabsq $100, %rcx cvtsi2ssq %rcx, %xmm0 movss LCPI0_0(%rip), %xmm1 movabsq $10, %rcx cvtsi2ssq %rcx, %xmm2 cvtsi2ss -8(%rbp), %xmm3 divss %xmm3, %xmm2 movss %xmm2, -12(%rbp) cvtsi2ss -8(%rbp), %xmm2 mulss %xmm2, %xmm1 addss %xmm0, %xmm1 movss %xmm1, (%rax) movl (%rax), %edx movl %edx, -16(%rbp) leaq L_.str(%rip), %rdi movl -16(%rbp), %esi movb $0, %al callq _printf movl %eax, -20(%rbp) ## 4-byte Spill ## BB#3: ## in Loop: Header=BB0_1 Depth=1 movl -8(%rbp), %eax addl $1, %eax movl %eax, -8(%rbp) jmp LBB0_1 LBB0_4: movl -4(%rbp), %eax addq $32, %rsp popq %rbp ret
您的代码的精确结果不完全由IEEE和C / C ++标准定义。 这是问题的根源。
主要的问题是,虽然所有的输入都是浮点数,但这并不意味着必须以浮点精度进行计算。 如果需要,编译器可以决定对所有中间值使用双精度。 当编译x87 FPU时,这种情况往往会自动发生,但编译器(例如VC ++ 2010)可以在编译SSE代码时明确地进行这种扩展。
这不是很好理解。 几年前我在这里分享了我的理解:
http://randomascii.wordpress.com/2012/03/21/intermediate-floating-point-precision/
一些编译器让你指定中间精度。 如果您可以强制所有编译器使用相同的中间精度,那么结果应该是一致的。