Most Fortran programmers are familiar with the LOGICAL data type, or at least they think they are…. An object of type LOGICAL has one of only two values, true or false. The language also defines two LOGICAL constant literals
.FALSE., which have the values true and false, respectively. It seems so simple, doesn’t it? Yes… and no.
The trouble begins when you start wondering about just what the binary representation of a LOGICAL value is. An object of type “default LOGICAL kind” has the same size as a “default INTEGER kind”, which in Visual Fortran (and most current Fortran implementations) is 32 bits. Since true/false could be encoded in just one bit, what do the other 31 do? Which bit pattern(s) represent true, and which represent false? And what bit patterns do
.FALSE. have? On all of these questions, the Fortran standard is silent. Indeed, according to the standard, you shouldn’t be able to tell! How is this?
According to the standard, LOGICAL is its own distinct data type unrelated to and not interchangeable with INTEGER. There is a restricted set of operators available for the LOGICAL type which are not defined for any other type:
.NEQV.. Furthermore, there is no implicit conversion defined between LOGICAL and any other type.
“But wait,” you cry! “I use
.OR. on integers all the time!” And so you do – but doing so is non-standard, though it’s an almost universal extension in today’s compilers, generally implemented as a “bitwise” operation on each bit of the value, and generally harmless. What you really should be using instead is the intrinsics designed for this purpose:
IANY can also be used if you want to bitwise-OR more than two integers,
IALL for bitwise-AND.)
Not so harmless is another common extension of allowing implicit conversion between LOGICAL and numeric types. This is where you can start getting into trouble due to implementation dependencies on the binary representation of LOGICAL values. For example, if you have:
INTEGER I,J,K … I = J .LT. K
just what is the value of I? The answer is “it depends”, and the result may even vary within a single implementation. Compaq Fortran traditionally (since the 1970s, at least) considers LOGICAL values with the least significant bit (LSB) one to be true, and values with the LSB zero to be false. All the other bits are ignored when testing for true/false. Many other Fortran compilers adopt the C definition of zero being false and non-zero being true. (Visual Fortran offers the /fpscomp:logicals switch to select the C method, since PowerStation used it as well.) Either way, the result of the expression
J.LT.K can be any value which would test correctly as true/false. For example, the value 1 or 999 would both test as true using Compaq Fortran. Just in case you were wondering, Compaq Fortran uses a binary value of -1 for the literal
.TRUE. and 0 for the literal
The real trouble with making assumptions about the internal value of LOGICALs is when you try testing them for “equality” against another logical expression. The way many Fortran programmers would naturally do this is as follows:
IF (LOGVAL1 .EQ. LOGVAL2) …
but the results of this can vary depending on the internal representation. The Fortran language
defines two operators exclusively for use on logical values,
.EQV. (“equivalent to”) and
.NEQV. (“not equivalent to”). So the above test would be properly written as:
IF (LOGVAL1 .EQV. LOGVAL2) …
In the Doctor’s experience, not too many Fortran programmers use
.NEQV. where they should and get into trouble when porting software to other environments. Get in the habit of using the correct operators on LOGICAL values, and you’ll avoid being snared by implementation differences.
However, there is one aspect of these operators you need to be aware of… A customer recently sent us a program that contained the following statement:
DO WHILE (K .LE. 2 .AND. FOUND .EQV. .FALSE.)
The complaint was that the compiler “generated bad code.” What the programmer didn’t realize was that the operators
.NEQV. have lower precedence than any of the other predefined logical operators. This meant that the statement was treated as if it had been:
DO WHILE (((K .LE. 2) .AND. FOUND) .EQV. .FALSE.)
what was wanted instead was:
DO WHILE ((K .LE. 2) .AND. (FOUND .EQV. .FALSE.))
The Doctor’s prescription here is to always use parentheses! That way you’ll be sure that the compiler interprets the expression the way you meant it to! (And you therefore don’t have to learn the operator precedence table you can find in chapter 4 of the Language Reference Manual!)
(From Intel Developer Zone, copied with permission)