意外的Fortran逻辑比较



当我运行以下代码时:

program foo
implicit none
logical :: a(2)
a = [.true., .true.]
print *, 'a = ', a
call evaluate(a)
a = [.true., .false.]
print *, 'a = ', a
call evaluate(a)
a = [.false., .false.]
print *, 'a = ', a
call evaluate(a)
contains
subroutine evaluate(a)

logical, intent(in) :: a(2)

if (a(1) .eqv. .true. .and. a(2) .eqv. .true.) then
print *, 'TT'
elseif (a(1) .eqv. .true. .and. a(2) .eqv. .false.) then
print *, 'TF'
elseif (a(1) .eqv. .false. .and. a(2) .eqv. .false.) then
print *, 'FF'
endif
end subroutine evaluate
end program

我得到以下输出:

a =  T T
TT
a =  T F
TF
a =  F F
TT

为什么子程序evaluate的最后一次调用给出了错误的输出(即,如果条件不是第三次,则匹配第一次(?该代码是使用命令gfortran -Wall -fcheck=all foo.f90编译的。

您发现Fortran中逻辑运算符的优先级可能有点混乱。让我们稍微扩展一下你的程序,看看更奇怪的地方:

ijb@ijb-Latitude-5410:~/work/stack$ cat eqv_2.f90
program foo
implicit none
logical :: a(2)
a = [.true., .true.]
print *, 'a = ', a
call evaluate(a)
a = [.true., .false.]
print *, 'a = ', a
call evaluate(a)
a = [.false., .true.]
print *, 'a = ', a
call evaluate(a)
a = [.false., .false.]
print *, 'a = ', a
call evaluate(a)
contains
subroutine evaluate(a)

logical, intent(in) :: a(2)

if (a(1) .eqv. .true. .and. a(2) .eqv. .true.) then
print *, 'TT'
elseif (a(1) .eqv. .true. .and. a(2) .eqv. .false.) then
print *, 'TF'
elseif (a(1) .eqv. .false. .and. a(2) .eqv. .true.) then
print *, 'FT'
elseif (a(1) .eqv. .false. .and. a(2) .eqv. .false.) then
print *, 'FF'
endif
end subroutine evaluate
end program
ijb@ijb-Latitude-5410:~/work/stack$ gfortran -std=f2008 -Wall -Wextra -fcheck=all -g -O eqv_2.f90
ijb@ijb-Latitude-5410:~/work/stack$ ./a.out
a =  T T
TT
a =  T F
TF
a =  F T
TF
a =  F F
TT

嗯,所以不仅[假,假]是[真,真],[假,真]也是[真,假]!这怎么会发生在政治之外?

问题是.eqv.算子的优先级低于.and.算子的优先级,因此首先对.and.进行求值。事实上,.eqv..neqv.的优先级是Fortran中所有非用户定义运算符中最低的,因此在任何只使用语言定义运算符的逻辑表达式中,它们将最后求值。这与我们将3 + 4 * 5 + 6评估为3 + (4*5) + 6 = 29而不是(3+4) * (5+6) = 77是一样的,因为*的优先级高于+

所以您将.false. .eqv. .true. .and. .false. .eqv. .true.评估为

.false. .eqv. (.true. .and. .false.) .eqv. .true. = 
( .false. .eqv. .false. ) .eqv. .true. = 
.true. .eqv. .true. = 
.true.

这就是你看到的结果。正是出于这个原因,我强烈建议学生在长逻辑表达式中使用括号——如果我们在这里这样做,我们就会得到你所期望的:

ijb@ijb-Latitude-5410:~/work/stack$ cat eqv.f90
program foo
implicit none
logical :: a(2)
a = [.true., .true.]
print *, 'a = ', a
call evaluate(a)
a = [.true., .false.]
print *, 'a = ', a
call evaluate(a)
a = [.false., .false.]
print *, 'a = ', a
call evaluate(a)
contains
subroutine evaluate(a)

logical, intent(in) :: a(2)

if ( (a(1) .eqv. .true.) .and. (a(2) .eqv. .true.)) then
print *, 'TT'
elseif ((a(1) .eqv. .true.) .and. (a(2) .eqv. .false.)) then
print *, 'TF'
elseif ( (a(1) .eqv. .false.) .and. (a(2) .eqv. .false.)) then
print *, 'FF'
endif
end subroutine evaluate
end program
ijb@ijb-Latitude-5410:~/work/stack$ gfortran -std=f2008 -Wall -Wextra -fcheck=all -g -O eqv.f90
ijb@ijb-Latitude-5410:~/work/stack$ ./a.out
a =  T T
TT
a =  T F
TF
a =  F F
FF

正如Martin在另一个答案中所解释的那样,很多这都是多余的。事实上,我认为像a .eqv. .true.这样的表达式不是很好的风格,而且事实上我不记得上次在代码中使用.eqv..neqv.是什么时候了。

我不清楚为什么要检查a(1)a(2)truefalse。根据定义,它们的值为truefalse,因此可以直接使用,而无需将它们与truefalse:进行比较

if (a(1) .and. a(2)) then
print *, 'TT'
elseif (a(1) .neqv. a(2)) then
print *, 'TF'
elseif (a(1) .eqv. a(2)) then
print *, 'FF'
endif

案例包括:

  1. 两个值均为true=a(1).and.a(2)=TT
  2. 值不相等=a(1).neqv.a(2)=TF
  3. 值等于=a(1).eqv.a(2)=FF

在第三种情况下,它们必须是FF,因为实际上已经涵盖了所有其他情况,所以else而不是elseif就足够了。但是,为了清楚起见,我保留了原来的代码。

输出:

a =  T T
TT
a =  T F
TF
a =  F F
FF

最新更新