I have come across a nested do
construct in an old code that I am using, and hoping to understand and modernize. It uses the same labelled action statement for termination of the do
loops, as well as go to
statement. Here is a simplified version, that illustrates the logic of the original code with some otherwise trivial operations:
subroutine original(lim)
k=0
do 10 i=1,4
do 10 j=1,3
k=k-2
if (i>lim) go to 10
k=k+i*j
10 k=k+1
write(*,*) k
return
end
After looking at other questions
on this site (and external resources), this is my
best effort at rewriting the logic of the original code, without obsolescent features (and go to
):
subroutine modern(lim)
integer, intent(in) :: lim
integer :: i, j, k
k=0
outer: do i=1,4
inner: do j=1,3
k=k-2
if (i>lim) then
k=k+1
cycle inner
end if
k=k+i*j
k=k+1
end do inner
end do outer
write(*,*) k
end subroutine modern
I tested the codes with the following program, including alternatives for triggering/not triggering the go to
statement:
write(*, '(a)') 'original:'
call original(2)
call original(5)
write(*, '(/,a)') 'modern:'
call modern(2)
call modern(5)
end
and it gives the same result for the original and my modern rewrite:
original:
6
48
modern:
6
48
The action statement complicated (for me) rewriting the do
loops, one can not simply replace it
with two end do
statements, and this is further complicated by the go to
. My rewrite required
duplicating the action statement (at the end of the inner
loop, and within the body of the if
statement). So my question is this:
modern
subroutine a correct rewrite of the original
one?original
code without duplicating the action statement (k=k+1
)?Your original
subroutine is surely the type of code that the Fortran standard had in mind when deleting non-block DO constructs:
The nonblock forms of the DO loop were confusing and hard to maintain. Shared termination and dual use of labeled action statements as do termination and branch targets were especially error-prone.
If we have a non-block DO with shared termination looking like
do 1
do 1
1 <action>
then we can write the equivalent
do 2
do 1
<action>
1 end do
2 end do
(where the labels here can be removed)
The action statement needs writing only once, and that's inside the innermost loop. Because it's a shared termination executing it once signals the end of the iteration of every DO construct sharing it.
If we branch to the action statement (with go to
) from the innermost1 construct, like
do 1
do 1
go to 1
1 <action>
we have the equivalent
do 3
do 2
go to 1
1 <action>
2 end do
3 end do
Our usual strategies for replacing go to
branching then are available.
Let's apply this to the original loop (ignoring any logic changes for the same effect and using redundant statement labels)
do 10 i=1,4
do 10 j=1,3
k=k-2
if (i>lim) go to 10
k=k+i*j
10 k=k+1
We can write this as
do 30 i=1,4
do 20 j=1,3
k=k-2
if (i>lim) go to 10
k=k+i*j
10 k=k+1
20 end do
30 end do
Coming to the go to
, we have (at least these) two simple approaches.
Negating the IF:
if (i<=lim) k=k+i*j
10 k=k+1
Using blocks:
nogoto: block
if (i>lim) exit nogoto
k=k+i*j
end block nogoto
10 k=k+1
As you can see, the "duplication of the action statement" comes from the use of the cycle
statement. In the rewritten loops you've had to duplicate the action statement because you don't reach the end of the loop where the action statement lives. The original loop doesn't have a cycle
and cycling changes the behaviour of the loop (the shared termination isn't executed when cycling but is when gone-to).
1 The situation is decidedly more complicated if the branch is not inside the innermost construct. For clarity of this answer I won't address that case here.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With