【问题标题】:Is there any way to make sure the floating point arithmetic result the same in both linux and windows有什么方法可以确保浮点运算结果在linux和windows中都相同
【发布时间】:2013-04-30 00:34:04
【问题描述】:

我的程序在 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 中构建代码。 我使用 "/fp:precise /O2" 在 windows 中使用 vs2005 构建代码。

当我打印“d_value”和“p_value”时,“d_value”在 linux 和 windows 中都是一样的。但是“p_value”有时会有所不同。 例如,以十六进制格式打印“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)

我使用标志-ffloat-store,因为这里有人建议:Different math rounding behaviour between Linux, Mac OS X and Windows

【问题讨论】:

  • MPFR怎么样?
  • 对于i 的哪个值,您观察到给定的输出?
  • 语言和编译器并不是这里的全部控制,基本上这样的方法不会产生匹配的结果。例如,解析器将使用一些 C 或其他库将数字字符串和小数点转换为二进制浮点数。这些库在操作系统、编译器等方面特别不同。因此,即使代码相同,输入代码的值也可能会有所不同,因此输出也会有所不同。 IEEE754 在舍入方面也很讨厌。
  • 如前所述,语言规则或实现定义的选项可能会导致精度上下转换,从而影响结果。堆栈溢出充满了为什么这个浮点数不小于工作,答案是因为编译器进行了精度转换,因此四舍五入的值不小于

标签: linux windows math floating-point g++


【解决方案1】:

在 Windows 上使用 /fp:strict 告诉编译器生成严格遵循 IEEE 754 的代码,在 Linux 上使用 gcc -msse2 -mfpmath=sse 以获得相同的行为。

您所看到的差异的原因已在 StackOverflow 上讨论过,但最好的调查是 David Monniaux 的 article


我使用gcc -msse2 -mpfmath=sse 编译时获得的汇编指令如下。指令cvtsi2ssqdivssmulssaddss 是要使用的正确指令,它们会生成一个程序,其中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

【讨论】:

  • 感谢您的回答。但它不起作用。不同的仍然发生。我的 g++ 版本是“gcc 版本 4.4.5 (Debian 4.4.5-8)”。
  • 也许升级你的 GCC 会有所帮助。当前的 GCC 版本是 4.8。
  • @hdbean 42d5d1ec 是作为变量p_value 的值之一获得的正确值。我通过阅读我自己的 GCC 生成的程序集并确保它使用正确的指令来检查这一点。您的 Visual 编译器是否为浮点生成 SSE2 指令?如果没有这些指令,生成正确的计算是如此困难和昂贵,以至于不使用它们就不可能生成正确的计算(正如我的答案链接到的文章中所解释的那样)。
  • @Pascal Cuoq 如何确保我的 vs2005 编译器为浮点生成 SSE2 指令?我已经使用“/fp:strict”在我的 vs2005 中构建代码。
  • @Pascal Cuoq 这是我的 vs2005 命令行:'''/O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /FD /EHsc /MT /fp:strict /Yu"stdafx.h" /Fp"Release\math_clac_test.pch" /Fo"Release\\" /Fd"Release\vc80.pdb" /W4 /nologo /c /Wp64 /Zi /TP /errorReport:提示'''。有什么问题吗?
【解决方案2】:

您的代码的精确结果并未完全由 IEEE 和 C/C++ 标准定义。这就是问题的根源。

主要问题是,虽然您的所有输入都是浮点数,但 不是 意味着必须以浮点精度进行计算。如果需要,编译器可以决定对所有中间值使用双精度。这在为 x87 FPU 编译时往往会自动发生,但编译器(例如 VC++ 2010)即使在编译 SSE 代码时也可以显式执行此扩展。

这不是很好理解。几年前我在这里分享了我对此的理解:

http://randomascii.wordpress.com/2012/03/21/intermediate-floating-point-precision/

某些编译器允许您指定中间精度。如果您可以强制所有编译器使用相同的中间精度,那么您的结果应该是一致的。

【讨论】:

  • 我在出现精度过高问题的问题中提供了更多信息:stackoverflow.com/a/23518798/139746。正如我在那里所说,“C99 允许浮点表达式的额外精度,这在过去被编译器制造商错误地解释为使浮点行为不稳定的许可证”。这个问题专门针对 VS2005,它不符合 C99,因此唯一可行的策略是采取可以获取并运行的策略。可以得到的是“FLT_EVAL_METHOD=0”模式。忘记让任何其他中间精度工作,它不会。
  • 例如 C99 要求将十进制浮点常量计算为中间精度,而不是类型的精度。这是 GCC 所做的,因为这是标准所要求的,但 Clang 不这样做(Clang 开发人员不太关心 FLT_EVAL_METHOD=2 模式)。我也怀疑这就是VS2005所做的(但我没有这个编译器)。
  • 事实上,如果您可以访问其中的一些,我会很高兴获得有关 Microsoft 编译器的信息来补充这篇博文:blog.frama-c.com/index.php?post/2013/07/24/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-05-20
  • 2021-09-26
  • 2011-06-02
  • 1970-01-01
  • 2018-01-23
  • 2016-04-30
  • 1970-01-01
相关资源
最近更新 更多