Point one.
Most uses of logical expressions do not produce any canonical
form at all. Depending on hardware, the condition may be a bit
embedded in a flags register, a property of a value (zero, NaN,
etc.), or many other things. The production of any canonical form
should be avoided unless it's really needed. Unlike foolish
advice from others, I do not recommend doing that work unless
necessary. The claim that 0 vs. 1 requires no more work at
all is only true of one hardware platform (then new Crays) and
only when the conditions are generated by the hardware instructions
involved. In all other cases, creating 0/1 is time consuming and
to be avoided when unnecessary.
Point two.
Logical values are unordered. The use of MAXLOC or MINLOC
to search a logical array for the first instance of .true. or .false.
(respectively) will produce the *wrong* answer if the condition
being sought is not present - that's even assuming the unnecessary
choice that .true. is greater than .false. Searching for the max value
in array of all 0's will return the position of the first 0 if there are
no non-0 entries in the array. If an array is all .false. and you are
looking for the first .true., the correct answer is to return a recognizably
out of bounds index (like SCAN or VERIFY do, which return zero).
The additional detail that MAXLOC and MINLOC actually search the
entire array even if the first element happens to be the answer is also
ignored by the people that recommend it. (Most ordered types have
more than two possible values and MAXLOC and MINLOC are
defined to accomodate that.)
Searching for a match to the specific value you're interested in is a
better choice than searching for a maximum or minimum.
Point three.
The rarely needed canonical form for internally storing .true. or
.false. should be designed to be efficient to use (even at the expense
of an extra step in producing it). This is because of the likelyhood
that there will be more than one reference to a given stored logical
value in the rest of the program. Choosing a canonical form that
requires further processing in the most common uses of such a flag
is not best design.
For example, suppose the condition being stored in LOGVAR is the
sign of a numeric value (ie. X<0 is .true.). To process this into a
canonical form requires that you pick up the sign bit (or the sign
flag in a flags register, do some masking, shifting etc. Suppose the
next use of LOGVAR is in an expression: (LOGVAR .and. Y<0).
Here, all the processing required to produce a canonical form
in the first case must be repeated in the second on the result of Y<0,
*unless* your stored canonical form of .true. is all-ones. In that
latter case, you can skip any shifts to line up the second conditional
with the canonical form of the first. All-ones is already lined-up with
all the positions of the machine word. Indeed, there exist machines
where the implementation of the scalar form of the MERGE intrinsic
consists of loading the three operands and issuing a single scalar
merge instruction - but only if the canonical form of .true. is all-ones.
This may save only a little bit on each use of a stored logical value,
but it also costs, at most, only a little bit (comparable amount) to
produce. At worst, it merely breaks even with the less desirable
canonical forms. If there *are* multiple uses of a stored logical
it almost always wins.
Any language design that's not based on these three points is a bad
language design is respect to it handling of LOGICALs.
--
J. Giles
"I conclude that there are two ways of constructing a software
design: One way is to make it so simple that there are obviously
no deficiencies and the other way is to make it so complicated
that there are no obvious deficiencies." -- C. A. R. Hoare
|