Consider some of the following properties of floating-point arithmetic. Arithmetic using the floating point number system has two important properties that differ from those of arithmetic using real numbers. Floating point arithmetic is not associative. This means that in general for floating point numbers x, y, and z:
(x + y) + z != x + (y + z)
(x . y) . z != x . (y . z)
(x . y) / z != x . (y / z)
Floating point arithmetic is also not distributive. This means that in general:
x . (y + z) != (x . y) + (x . z)
In short, the order in which operations are carried out can change the output of a floating point calculation. So consider the following example in Fortran:
program numericerror integer, parameter :: dp = selected_real_kind(15, 307) real (kind=dp) :: result1, result2, x, y, z x = 2.49334_dp y = 3.712358_dp z = 1.377416_dp result1 = (x * y) / z result2 = x * (y / z) print *, result1 print *, result2 end program numericerror
When this program runs, one would assume that the value of result1 would be the same as result2. But this is not the case. Here are the two values:
6.7199529377617218 6.7199529377617226
]]>
Unfortunately, most decimal fractions cannot be represented exactly as binary fractions. A consequence is that, in general, the decimal floating-point numbers we enter are only approximated by the binary floating-point numbers actually stored in the machine. Now 1/10 cannot be represented accurately because no matter how many base 2 digits you’re willing to use, the decimal value 0.1 cannot be represented exactly as a base 2 fraction. In base 2, 1/10 is the infinitely repeating fraction:
0.0001100110011001100110011001100110011001100110011…
You can see a repeating base of 1001. Stop at any finite number of bits, and you get an approximation. This is why we see things like:
0.10000000000000001 or 0.0999999999999998
The consequence of this is that summing ten values of 0.1, may not exactly yield 1.0. Consider the following example in Fortran (using double precision reals):
program one_tenth implicit none integer, parameter :: dp = selected_real_kind(15, 307) real (kind=dp) :: frac, sum integer :: i frac = 1.0_dp / 10.0_dp print *, frac do i = 1,10 sum = sum + frac end do print *, sum end program one_tenth
Now here is the output produced:
0.10000000000000001 0.99999999999999989
Case in point.
]]>
real :: x
data x/3.7/
It is a very handy method of initializing arrays:
integer, dimension(10) :: aray
data aray/1,2,3,4,5,6,7,8,9,10/
There is nothing inherently wrong with the data statement, but it can easily be replaced with an array initializing statement of the form:
aray = (/1,2,3,4,5,6,7,8,9,10/)
It is not inherently a legacy feature though.
]]>Now this could be stored in a two character string as 5A, or in a two integer array as 5 and 10.
To convert a whole word, the string is converted one character at a time. Here is an example with the word bubble = 6 characters
CHAR = B u b b l e ASCII = 66 117 98 98 108 101 HEX = 42 75 62 62 6c 65
Now the hex numbers could be stored in two types of array, as characters, or integers. The most common way of storing these are as an integer array, where each component of the hex number is stored in a separate element. Here is an example in Fortran:
integer, dimension(0:31) :: h h = (/ 4, 2, 7, 5, 6, 2, 6, 2, 6, 12, 6, 5 /)
10 read (*,1,end=20) value 1 format(f3.0) sumgrd = sumgrd + value sumsqs = sumsqs + value**2 n = n + 1 go to 10
This can be replaced with a do/end do pair. This then looks like this:
do read (*,1,end=20) value 1 format(f3.0) sumgrd = sumgrd + value sumsqs = sumsqs + value**2 n = n + 1 end do
This removes all the “jumps” except for one – the label 20 in the read statement.
read (*,1,end=20) value
Here the program will read input until there is no more to read, and then pass control to the statement at label 20. This is still a valid statement, and therefore will be ignored.
The remainder of the program needs very like in the way of reengineering. Apart from the label 20, there are four other labels, but they are associated with format statements, and therefore cannot be removed. Maybe the output could be formatted better.
]]>gfortran -std=f2008 -Wall stdev.f08
The first error to pop up will be to do with the comments:
First thing to do it change the comment delimiter from C to !. The program as is will now compile, and run. To give it a modern look, we will encapsulate the program with a header, and program terminator, and remove the stop. Here is what the program looks like now:
! computing the mean and standard deviation program mean_stdev real low data sumgrd,sumsqs,n/2*0.,0/ 10 read (*,1,end=20) value 1 format(f3.0) sumgrd = sumgrd + value sumsqs = sumsqs + value**2 n = n + 1 go to 10 20 avg = sumgrd/n write(*,2) n,avg 2 format(5x,'n = ',i3,5x,'value of mean ',f8.2) std = sqrt((sumsqs-sumgrd**2/n)/(n-1)) write(*,3) std 3 format(5x, 'the standard deviation is ',f8.2) low = avg - std high = avg + std write(*,4) low,high 4 format(5x,'most values fall between ',f8.2,' and ',f8.2) end
Now the code is now technically free-format, but we have left the 6-spaces for now.
The next thing to do is suppress implicitly declared variables. This is achieved by adding implicit none, under the program header. Compiling it is now will lead to a bunch of errors, so it is best to gather together the list of variables and decide what they are.
Error: Symbol ‘sumgrd’ at (1) has no IMPLICIT type
This is somewhat easy to do, because intrinsically defined variables specify undeclared variables and arguments beginning with I through N as integers, and all other undeclared variables and arguments as real.
reals → grade, low, high, sumgrd, cumsqs, avg, std
integers → n
These can then be declared at the top of the program:
real :: value, low, high, sumgrd, sumsqs, avg, std integer :: n
The data statement merely initializes three variables to 0, something which can be done inline when they are declared.
real :: value, low, high, sumgrd=0.0, sumsqs=0.0, avg, std integer :: n=0
The variable names are actually okay, so we will leave them as is. Here is the program now, with formatting modified to make it look better:
! computing the mean and standard deviation program mean_stdev implicit none real :: value, low, high, sumgrd=0.0, sumsqs=0.0, avg, std integer :: n=0 10 read (*,1,end=20) value 1 format(f3.0) sumgrd = sumgrd + value sumsqs = sumsqs + value**2 n = n + 1 go to 10 20 avg = sumgrd/n write(*,2) n,avg 2 format(5x,'n = ',i3,5x,'value of mean ',f8.2) std = sqrt((sumsqs-sumgrd**2/n)/(n-1)) write(*,3) std 3 format(5x, 'the standard deviation is ',f8.2) low = avg - std high = avg + std write(*,4) low,high 4 format(5x,'most values fall between ',f8.2,' and ',f8.2) end
]]>
Bubble → 42 75 62 62 6C 65
Converting from a character to a hexadecimal in Fortran can be achieved using the write statement with an appropriate Z format. Here is a small snippet of code:
character :: c
character (len=2) :: tempH
write(*,*) 'Enter a character'
read(*,*) c
write(tempH,'(Z2)') c
This code reads in a character from standard input, and uses write to write the contents of c to tempH (which is a string). This is similar to how sprintf() works in C. During the process, the contents of c are converted to a hexadecimal of length 2, and stored in tempH. So if c=z, then tempH will contain “7A”.
Now sometimes we may want to store the hexadecimal characters in an integer array. Using the same example, 7A could be stored as the integers 7 and 10 (where A=10, B=11,…, F=15). So now we can use the read statement in a similar way to sscanf().
Now, taking the following example a step further, the hexadecimal string is converted to an integer string (stored in the array hex). Each of the to elements of the string is converted individually. The first element of tempH (which is specified as element (1:1), because it is a string, not a character array), is read, as a single hexadecimal character, and converted to an integer and stored in the first element of the integer array, hex(1). The same is done for the second element of tempH.
integer, dimension(1:2) :: hex read(tempH(1:1),'(Z1)') hex(1) read(tempH(2:2),'(Z1)') hex(2)
So the end result is that hex(1) will contain the value 7, and hex(2) will contain the value 10. Hexadecimal conversions can be checked using an online converter.
Note that for some reason, this code causes a runtime error when compiled with gfortran using -std=f95. An alternative is to convert each character to its ASCII value, and then to its hexadecimal value (see next post).
]]>
Attached is a small Fortran program (Fortran IV?) which calculates the mean and standard deviation of a list of numbers. The numbers are input by means of an ASCII file of numbers (which is input by ./a.out <data.txt, or entering numbers and specifying end-of-input with CTRL-D). The program reads each number and adds it to two lists: one list is the sum of all numbers, the second is the sum of each number squared. It then calculates the mean, standard deviation, and the range of values most number fall in between.
C COMPUTING THE MEAN AND STANDARD DEVIATION REAL LOW DATA SUMGRD,SUMSQS,N/2*0.,0/ 10 READ (*,1,END=20) GRADE 1 FORMAT(F3.0) SUMGRD = SUMGRD + GRADE SUMSQS = SUMSQS + GRADE**2 N = N + 1 GO TO 10 20 AVG = SUMGRD/N WRITE(*,2) N,AVG 2 FORMAT(5X,'N = ',I3,5X,'VALUE OF MEAN ',F8.2) STD = SQRT((SUMSQS-SUMGRD**2/N)/(N-1)) WRITE(*,3) STD 3 FORMAT(5X, 'THE STANDARD DEVIATION IS ',F8.2) LOW = AVG - STD HIGH = AVG + STD WRITE(*,4) LOW,HIGH 4 FORMAT(5X,'MOST VALUES FALL BETWEEN ',F8.2,' AND ',F8.2) STOP END
Here is a sample output:
N = 11 VALUE OF MEAN 80.45 THE STANDARD DEVIATION IS 11.74 MOST VALUES FALL BETWEEN 68.71 AND 92.20
The program has a number of legacy issues associated with it, and the task here is to migrate the code to Fortran 95 standards. Here are the legacy issues:
This is not a difficult program to re-engineer, but illustrates some of the core issues.
]]>float i, j; j = 1.0/100.0; for (i=0; i!=1; i=i+j) { }
Now theoretically, this loop should iterate 100 times, but it won’t. Why? Because floating point numbers are not precise, and as such the condition i!=1 will likely never become true. The loop will loop forever, to infinity and beyond. The loop would work if i!=1 were modified to i<=1, but honestly one should not used floating point array indices. They aren’t needed. C won’t stop you. Other languages don’t allow ludicrous loop-ending conditions, that’s what non-for loops are for. Fortran allowed them once, and technically the Gnu version of Fortran flags it as a warning, but still allows their use. If we wrote a similar piece of code in Fortran:
real :: i, j j = 1.0/100.0 do i=0,1,j end do
If compiled using normal gfortran we would get two warnings:
do i = 0,1,j 1 Warning: Deleted feature: Loop variable at (1) must be integer infinite.f95:9:15: do i = 0,1,j 1 Warning: Deleted feature: Step expression in DO loop at (1) must be integer
But it still compiles. Compile if using the –std=f95 (or any later Fortran standard), and there is a clear error:
Error: Deleted feature: Loop variable at (1) must be integer
Now, you will ask why anyone would create a 100 iteration loop using the value 0.01 as the loop increment, but people do, and exactly because C allows it (and they don’t know any better).
]]>
To remove the equivalence statement for arrays requires use of the function reshape(). In the example below, the contents of the 25 element 2D array a are reshaped into a 5×5 array, and assigned to array b.
program reshapeEG
integer, dimension(25) :: a
integer, dimension(5,5) :: b
integer :: i,j
do i = 1,25
a(i) = i
end do
b = reshape(a,(/5,5/))
do i = 1,5
print *, (b(i,j), j=1,5)
end do
end program reshapeEG
Here is the output:
1 6 11 16 21 2 7 12 17 22 3 8 13 18 23 4 9 14 19 24 5 10 15 20 25
To change the order of the reshaping from the column-first default, one has to add the order parameter. For example to produce a row-first reshaping:
b = reshape(a,(/5,5/),order=(/2,1/))
This will produce:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
]]>