mja*_*bse 3 floating-point precision fortran gfortran
我注意到==-operator对于浮点类型的某些行为对我来说似乎很奇怪。我知道,我不能指望像0.1 + 0.2 == 0.3要.true.因浮点表示的局限性,以及因此,浮点比较通常应该喜欢的东西做abs(x - y) < tolerance。但是,我仍然希望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
Run Code Online (Sandbox Code Playgroud)
在64位Manjaro Linux上使用gfortran 7.3.1编译该程序时,
gfortran -o a.out minimal_example.F90 && a.out
Run Code Online (Sandbox Code Playgroud)
我实际上确实得到了输出T。但是,使用以下命令编译和执行32位可执行文件时
gfortran -m32 -o a.out minimal_example.F90 && a.out
Run Code Online (Sandbox Code Playgroud)
结果是F。在我看来,存储加法结果似乎会稍微改变其值,因为两者之间的差值abs(c - (a + b))大致是2.5E-014。但是,我并不真正理解为什么,因为所有变量都属于同一类,所以临时变量应该a + b不具有相同的精度,因此适合c而没有任何转换错误吗?
使用间隔[0,1)中的几个随机生成的值尝试此操作,a然后b重复此观察。在64位可执行文件中进行的比较始终为.true.,而在32位可执行文件中进行的尝试的25%导致.false.。
这种行为的原因是什么?特别是为什么64位和32位可执行文件之间会有区别?
首先,建议不要在实数上使用==(或对于怀旧的FORTRAN编程器,请使用.eq。)。这样做时,编译器倾向于打印警告(尝试编译器选项-galltran的-Wall!)。
当然,当您这样做时,仍然可能会想知道计算机内部发生了什么。FORTRAN的优势之一是,只要结果符合FORTRAN标准,编译器就可以自由地进行计算的改组,更改其顺序,优化某些变量等。正如@Eric Postpischil指出的那样:可能发生的事情之一是,在计算过程中双精度变量会转换为更高的精度,而在计算完成后只会转换回双精度。
在您的情况下,我的猜测是(a + b)是按较高的精度计算的,而c已转换为双精度,因此并不相同。我期望不同的编译器(是否为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
Run Code Online (Sandbox Code Playgroud)
暂时还不能解决问题,所以我在lubuntu上测试了一些编译器选项。
奇怪的是,-m32似乎对结果影响很小:
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
Run Code Online (Sandbox Code Playgroud)
从gfortran的在线文档中,我发现了一些我认为可以解释这些发现的信息:
-float-store:
不要将浮点变量存储在寄存器中,并禁止其他可能更改浮点值是从寄存器还是从存储器获取的选项。
此选项可防止在诸如68000之类的机器上产生不必要的过高精度,例如6881中的浮动寄存器(68881)保持的精度比原先的两倍高。对于x86体系结构也是如此。对于大多数程序而言,多余的精度只会发挥作用,但是有些程序依赖于IEEE浮点数的精确定义。在修改这些程序以将所有相关中间计算存储到变量中之后,对这些程序使用-ffloat-store