Floating point 在Fortran中比较浮点加法和临时加法的存储结果

Floating point 在Fortran中比较浮点加法和临时加法的存储结果,floating-point,fortran,precision,gfortran,Floating Point,Fortran,Precision,Gfortran,我注意到浮点类型的==-运算符的一些行为对我来说似乎很奇怪。我知道我不能期望像0.1+0.2==0.3这样的东西是.true。由于浮点表示的限制,因此,浮点比较通常应该用abs(x-y)

我注意到浮点类型的
==
-运算符的一些行为对我来说似乎很奇怪。我知道我不能期望像
0.1+0.2==0.3这样的东西是
.true。
由于浮点表示的限制,因此,浮点比较通常应该用
abs(x-y)
这样的东西来完成。然而,我仍然希望这个最小的程序在任何情况下都能输出
T

program main
    integer, parameter :: dp = kind(0d0)
    real(kind=dp) :: a, b, c

    a = 4.4090680619790817d+002
    b = 1.0000000000000000d-004
    c = (a + b)

    print *, (c == (a + b))
end program
在64位Manjaro Linux上使用gfortran 7.3.1编译此程序时

gfortran -o a.out minimal_example.F90 && a.out
事实上,我确实得到了输出
T
。但是,在编译和执行32位可执行文件时

gfortran -m32 -o a.out minimal_example.F90 && a.out
结果是
F
。在我看来,存储加法结果似乎会略微改变其值,因为差值
abs(c-(a+b))
大致为
2.5E-014
。我真的不明白为什么,因为所有变量都是同一类型的,所以临时
a+b
是否应该具有相同的精度,从而适合
c
而没有任何转换错误

对于
a
b
在区间[0,1]内尝试使用几个随机生成的值重复了这一观察结果。64位可执行文件中的比较总是
.true.
,而使用32位可执行文件进行的尝试中有25%的结果是
.false.

这种行为的原因是什么?特别是,为什么64位和32位可执行文件之间存在差异?

首先,建议不要在reals上使用==(或.eq.,对于怀旧的FORTRAN程序员)。编译器在这样做时倾向于打印警告(请尝试编译器选项-Wall for gfortran!)

当然,当你这样做的时候,你可能仍然想知道计算机内部到底发生了什么。FORTRAN的优点之一是,只要结果符合FORTRAN标准,编译器就可以自由地改变计算、改变计算顺序、优化某些变量等等。正如@Eric Postdischil指出的:其中之一可能发生的情况是,双精度变量在计算过程中转换为更高的精度,并且只有在计算完成后才转换回双精度

在你的例子中,我的猜测是(a+b)是以更高的精度计算的,而c已经转换为双精度,因此是不同的。我认为不同的编译器(ifort?PGI编译器?)和不同的编译器选项(-fpexact,-O3等等)会有不同的行为

简而言之,我建议使用如下函数进行测试

 function same(a,b) result(eq)
 implicit none
     real, intent(in) :: a, b
     logical :: eq
     real, parameter :: very_small = 1e-10 ! or another very small value
     eq = abs(a-b) < very_small * abs(a)
 end function same
从gfortran的在线文档中,我发现了一些 我想这可以解释观察结果:

-零售店:

不要将浮点变量存储在寄存器中,并禁止其他可能会改变是否使用浮点值的选项 从寄存器或存储器中取出

此选项可防止在诸如68000之类的机器上出现不必要的精度过剩,其中(68881的)浮动寄存器保留的精度更高 精度比双精度更高。对于x86也是如此 架构。对于大多数程序来说,过高的精度只会带来好处, 但是有一些程序依赖于IEEE浮动的精确定义 在将这些程序修改为 将所有相关的中间计算存储到变量中


我怀疑编译器正在以额外精度计算表达式,并在存储时丢弃该精度。某些语言(如C)允许这样做。但是,我不熟悉FORTRAN标准。其结果是
a+b
具有长双精度的额外精度,当四舍五入为双精度时,其值会发生变化存储在
c
中,因此
c
的值与
a+b
的值不同。如果希望在32位和64位模式之间获得类似的结果,则必须在32位模式中设置sse选项,例如-msse2;如上所述,32位默认值使用额外精度。额外精度通常会为您提供一些额外的精度,但您不需要ave控制它的使用位置(因此它将破坏一些算法,如Kahan求和)。由于-ffloat store选项运行速度非常慢,因此通常无法推荐,除非您想验证它是否给出与sse相同的结果。
a、b、c
在本例中都是双精度。@agentp您完全正确。感谢您指出这一点,我已相应地更改了答案。我知道如何正确比较reals,这是estion实际上是关于如果仍然使用
==
-运算符执行操作会发生什么。我编辑了这个问题以澄清这一点。您答案的第二部分非常有用,使用
-ffloat store
我确实得到了32位版本的
T
。标记的描述与我想要的解释完全相同。谢谢很多。这是Fortran,不是Fortran。旧Fortran中没有
==
,只有
.eq.
gfortran -m32  compare_reals.f90 && ./a.out
  F
gfortran compare_reals.f90 && ./a.out
  F
gfortran -m32 -ffloat-store compare_reals.f90 && ./a.out
 T
gfortran -m32 -O3 compare_reals.f90 && ./a.out
 TFloating point comparisons
gfortran -ffloat-store compare_reals.f90 && ./a.out
 T
gfortran -O3 compare_reals.f90 && ./a.out
 T