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 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.
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:
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]:
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.
The European Copyright directive threatens online communication in Europe.
But thanks to massive shared action earlier this year, the European parliament can still prevent the problems. For each of the articles there are proposals which fix them. The parliamentarians (MEPs) just have to vote for them. And since they are under massive pressure from large media companies, that went as far as defaming those who took action as fake people, the MEPs need to hear your voice to know that your are real.
If you care about the future of the Internet in the EU, please Call your MEPs.