Search code examples
fortran

Unexpected Fortran logical comparison


When I run the following code:

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

I get the following output:

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

Why the last call of the subroutine evaluate gives the wrong output (i.e. match the first if condition not the thrid)? The code has been compiled with the command gfortran -Wall -fcheck=all foo.f90.


Solution

  • You have discovered that the order of precedence of logical operators in Fortran can be a bit confusing. Let's extend your program slightly and see more weirdness:

    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
    

    Hmmm, so not only [false,false] is [true,true] but [false,true] is [true, false]! How can this happen outside politics?

    Well the problem is the precedence of the .eqv. operator is lower than that of the .and. operator, and so .and. gets evaluated first. In fact the precedence of .eqv. and .neqv. are the lowest of any non-user defined operators in Fortran, so they will get evaluated last in any logical expression that only uses language defined operators. This is just the same as us evaluating 3 + 4 * 5 + 6 as 3 + (4*5) + 6 = 29 and not (3+4) * (5+6) = 77, because the precedence of * is higher than that of +.

    So you evaluate .false. .eqv. .true. .and. .false. .eqv. .true. as

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

    Hence the result you see. It is for this reason that I strongly recommend students to use brackets in long logical expressions - if we do this here we get what you expected:

    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
    

    That said as Martin explains in the other answer a lot of this is redundant. In fact I would argue that expressions like a .eqv. .true. are not good style, and in fact I can't remember when I last use .eqv. or .neqv. in a code.