# Surprising behaviour of Fortran (90/95)

## 1 Introduction

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.

## 2 Testing Skelleton

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>>
```

### 2.1 Helpers

```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))
```

## 3 Surprises

### 3.1 I have to declare the return type of a function in the main program and in the function

```! 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
```

### 3.2 Variables in Functions accumulate over several function calls

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
```

### 3.3 parameter vs. intent(in)

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
```

### 3.4 To return values from functions, assign the value to the function itself

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
```

### 3.5 Fortran array indizes start at 1 - and are inclusive

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]
```

### 3.6 I have to trim strings when concatenating

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.

Emacs 24.3.1 (Org mode 8.0.2)

AnhangGröße
surprises.org8.42 KB
accumulate.f90226 Bytes
accumulate-not.f90231 Bytes
base60-surprises.f901.6 KB
trim.f90501 Bytes
surprises.pdf206.83 KB
surprises.html22.47 KB
base60.f902.79 KB Willkommen im Weltenwald!
((λ()'Dr.ArneBab))