I recently started really learning Fortran (as opposed to just dabbling with existing code until it did what I wanted it to).
Here I document the surprises I found along the way.
If you want a quick start into Fortran, I’d suggest to begin with the tutorial Writing a commandline tool in Fortran [1] and then to come back here to get the corner cases right.
As reference: I come from Python, C++ and Lisp, and I actually started to like Fortran while learning it. So the horror-stories I heard while studying were mostly proven wrong. I uploaded the complete code as base60.f90 [2].
This is a code sample for calculating a base60 value from an integer.
The surprises are taken out of the program and marked with double angle brackets («surprise»). They are documented in the chapter Surprises.
program base60 ! first step: Base60 encode. ! reference: http://faruk.akgul.org/blog/tantek-celiks-newbase60-in-python-and-java/ ! 5000 should be 1PL implicit none <<declare-function-type-program>> <<function-test-calls>> end program base60
<<declare-function-type-function>> implicit none !!! preparation <<unchanged-argument>> <<parameter>> ! work variables integer :: n = 0 integer :: remainder = 0 ! result <<variable-declare-init>> ! actual algorithm if (number == 0) then <<return>> end if ! calculate the base60 string <<variable-reset>> n = number ! the input argument: that should be safe to use. ! catch number = 0 do while(n > 0) remainder = mod(n, 60) n = n/60 <<indizes-start-at-1>> ! write(*,*) number, remainder, n end do <<return-end>>
write(*,*) 0, trim(numtosxg(0)) write(*,*) 100000, trim(numtosxg(100000)) write(*,*) 1, trim(numtosxg(1)) write(*,*) 2, trim(numtosxg(2)) write(*,*) 60, trim(numtosxg(60)) write(*,*) 59, trim(numtosxg(59))
! I have to declare the return type of the function in the main program, too. character(len=1000) :: numtosxg
character(len=1000) function numtosxg( number )
Alternatively to declaring the function in its header, I can also declare its return type in the declaration block inside the function body:
function numtosxg (number) character(len=1000) :: numtosxg end function numtosxg
This even happens, when I initialize the variable when I declare it:
character(len=1000) :: res = ""
Due to that I have to begin the algorithm with resetting the required variable.
res = " " ! I have to explicitely set res to " ", otherwise it ! accumulates the prior results!
This provides a hint that initialization in a declaration inside a function is purely compile-time.
program accumulate implicit none integer :: acc write(*,*) acc(), acc(), acc() ! prints 1 2 3 end program accumulate integer function acc() implicit none integer :: ac = 0 ac = ac + 1 acc = ac end function acc
program accumulate implicit none integer :: acc write(*,*) acc(), acc(), acc() ! prints 1 1 1 end program accumulate integer function acc() implicit none integer :: ac ac = 0 ac = ac + 1 acc = ac end function acc
Defining a variable as parameter gives a constant, not an unchanged function argument:
! constants: marked as parameter: not function parameters, but ! algorithm parameters! character(len=61), parameter :: base60chars = "0123456789"& //"ABCDEFGHJKLMNPQRSTUVWXYZ_abcdefghijkmnopqrstuvwxyz"
An argument the function is not allowed to change is defined via intent(in):
! input: ensure that this is purely used as input. ! intent is only useful for function arguments. integer, intent(in) :: number
This feels surprisingly obvious, but it was surprising to me nontheless.
numtosxg = "0" return
The return statement is only needed when returning within a function. At the end of the function it is implied.
numtosxg = res end function numtosxg
For an algorithm like the example base60, where 0 is identified by the first character of a string, this requires adding 1 to the index.
! note that fortran indizes start at 1, not at 0. res = base60chars(remainder+1:remainder+1)//trim(res)
Also note that the indizes are inclusive. The following actually gets the single letter at index n+1:
base60chars(n+1:n+1)
In python on the other hand, the second argument of the array is exclusive, so to get the same result you would use [n:n+1]:
pythonarray[n:n+1]
It is necessary to get rid of trailing blanks (whitespace) from the last char to the end of the declared memory space, otherwise there will be huge gaps in combined strings - or you will get missing characters.
program test character(len=5) :: res write(*,*) res ! undefined. In the last run it gave me null-bytes, but ! that is not guaranteed. res = "0" write(*,*) res ! 0 res = trim(res)//"a" write(*,*) res ! 0a res = res//"a" write(*,*) res ! 0a: trailing characters are silently removed. ! who else expected to see 0aa? write(res, '(a, "a")') trim(res) ! without trim, this gives an error! ! *happy* write(*,*) res end program test
Hint from Alexey: use trim(adjustl(…)) to get rid of whitespace on the left and the right side of the string. Trim only removes trailing blanks.
Anhang | Größe |
---|---|
surprises.org [7] | 8.42 KB |
accumulate.f90 [8] | 226 Bytes |
accumulate-not.f90 [9] | 231 Bytes |
base60-surprises.f90 [10] | 1.6 KB |
trim.f90 [11] | 501 Bytes |
surprises.pdf [12] | 206.83 KB |
surprises.html [13] | 22.47 KB |
base60.f90 [2] | 2.79 KB |
Links:
[1] https://www.draketo.de/english/free-software/fortran
[2] https://www.draketo.de/files/base60.f90
[3] http://faruk.akgul.org/blog/tantek-celiks-newbase60-in-python-and-java/
[4] http://tantek.pbworks.com/w/page/19402946/NewBase60
[5] http://www.gnu.org/software/emacs/
[6] http://orgmode.org
[7] https://www.draketo.de/files/surprises.org
[8] https://www.draketo.de/files/accumulate.f90
[9] https://www.draketo.de/files/accumulate-not.f90
[10] https://www.draketo.de/files/base60-surprises.f90
[11] https://www.draketo.de/files/trim.f90
[12] https://www.draketo.de/files/surprises.pdf
[13] https://www.draketo.de/files/surprises.html