Same logic, different result

So, this made me laugh and scratch my head at the same time.

I had a feedback from some colleagues stating they were having strange results running a given type of computation. Long story short, I then debugged the code using their input data, and realised that it was a bug of this type:

skip = 1
do while (skip <= NM_EFF__ .and. peak_ext_lims_(2, skip) <= bpw_ext_2)
    skip = skip + 1
enddo

In fact, their example was that the second conditon was never met for any index in subscript 2. But, to avoid overflow (which actually happened), I put the first guard condition, to bound the increasing of the index skip. But clearly, not meeting the first condition didn’t help avoiding evaluating the second peak_ext_lims_(2, skip) <= bpw_ext_2, which at that moment, was reading one-index-off the allowed range.

I mean, shouldn’t binary logical .and. avoid evaluating the next conditions as soon as it detects a 0-evaluated one?

The Fortran standard neither requires nor prohibits such short-circuiting, and if you want condition A to be evaluated before condition B, you must write

if (A) then
   if (B) then
      ! do something
   end if
end if

Short-circuiting logical expressions · Issue #19 · j3-fortran/fortran_proposals · GitHub is a discussion of possible extensions to the language.

Yes, now I see it very clear :smiley:
Is there a specific reason why is like that? How do the compiler choose which one to evaluate first?

In the cited discussion @sblionel wrote

The committee has discussed short-circuiting many times. The sentiment is generally against implicit short-circuiting, as the standard currently allows evaluation of any equivalent expression to any degree of completeness. Requiring short-circuiting would hinder some optimizations.

The programmer is required to write the expression such that it doesn’t matter which way the compiler chooses to evaluate it. This gives the compiler the maximum flexibility in the evaluation order, factoring out common subexpressions, lifting expressions outside of loops, and so on.

As noted above, this does not place any constraints on the programmer who might prefer a specific evaluation order. In this case, the programmer can use nested if statements to achieve that order.

In this sense, the fortran semantics choice is optimal, and one wonders why all languages don’t do it this way.

The same loop can be rewritten as:

do skip = 1, NM_EFF_
  if (peak_ext_lims(2, skip)>bpw_ext_2) exit
end do

And you can check if the condition have been met by looking at the value of the variable skip after the loop.
It will be equal to NM_EFF_ + 1 if the condition have not been met.

1 Like

@egio that’s how I changed, thanks!

1 Like

If peak_ext_lims(:,:) is an array, which I think it is from the earlier comments, then this might also also be done as an array expression.

skip = findloc( peak_ext_lims(2, 1:NM_EFF_)>bpw_ext_2, .true.)

This looks like it constructs an intermediate logical array and then searches for the location of the first .true. value, but most compilers will optimize away the intermediate array and inline the tests. In this case, skip would be zero if no element is found.

If you are only interested if there is a successful test somewhere in the array, but you don’t care about its array index, then

if ( any(peak_ext_lims(2, 1:NM_EFF_)>bpw_ext_2) ) then
   ! test successful, do whatever is appropriate...
endif

would work. The compiler would typically optimize away the intermediate logical array in this expression too.

I don’t think there is any performance differences in any of these choices, so the best option is the one that is the simplest and clearest code. That depends in part on what happens after this test and if/how the integer skip is used.

3 Likes

This looks interesting, since I do care about the index, if it exists.
And since the array is actually sorted in increasing order, a binary search would be best (which maybe findloc hides behind?), but didn’t put it in place since usually here we deal with small array sizes.
I apprecciate the hint, thanks!
Just got hit by “short-circuiting” assumption :upside_down_face:

With a conforming Fortran 2023 processor, one can expect to write something like the following if they so choose:

skip = 1
do while ( ( skip <= NM_EFF__ ? ( peak_ext_lims_(2, skip) <= bpw_ext_2 ? .true. : .false. ) : .false. ) )
    skip = skip + 1
end do
1 Like